DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Eclipse-NotFound /

Eclipse-NotFound/dsh-incident-studio

Verified

Screen frames and log lines on one millisecond timeline for DeepSeek Harness: record, mark, box, and hand your agent a keyframe strip + GIF + aligned log slice.

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@971ed2b6

Incident Studio · dsh-incident-studio

English | 中文

A right-sidebar plugin for DeepSeek Harness that puts screen frames and log lines on one millisecond timeline. Record, mark the seconds that went wrong, drag boxes on the frame, type one sentence — then hand the agent a keyframe strip, an animated GIF, the aligned log slice and your description, straight into the composer.

Nothing here is game- or project-specific: log sources are configured, any program that writes a file works, and programs can push events directly.

Incident Studio — frames, timeline and log lines share one wall clock

The problem it solves

A hands-on bug report ("I picked up the chest and the character floats") is obvious on screen and obscure in the log. You write a sentence, the agent has to guess which of the last 900 log lines was "that moment".

This plugin ties three things to one clock:

Evidence Where it comes from Precision
Picture Host-side frame capture (three paths, below) 6 fps by default, every frame carries wall-clock time
Events Log file tailing, markers, HTTP ingest line-write time, tens of ms; ingest can be exact
Your words boxes drawn on the frame + one sentence attached to the selected range

Every delivered log line is prefixed with [00:12.340] relative to the recording start — the same origin as the frames — so the agent never has to guess.

Using it

Record ──> frames + log lines, one clock
   │
Mark (⚑) when it breaks ──> Stop
   │
Review: drag the range, box the problem, one sentence
   │
Build artifacts ──> frames.png (6-frame strip) + clip.gif, saved in the workspace
   │
Fill composer ──> text + attachments in the input box (or Send now)
  1. Open the panel: the + in the right sidebar (guide card "Incident Studio"), or the 🎥 Capture button under the composer. On first mount it opens itself once and remembers that in localStorage.
  2. Configure log sources (once): panel → Settings → absolute path (or workspace-relative). The file may appear later — start recording first, launch the game afterwards.
  3. Record: pick a capture source → ● Start recording. Hit ⚑ Mark when it breaks (as often as you like).
  4. Review: the default range is the last mark ±3 s; drag the ends yourself or use All / Last 8s / ±3s around mark. Moving the playhead moves the log list; clicking a line seeks the picture.
  5. Annotate: drag a box on the frame; each box takes a note.
  6. Hand off: Build artifacts → Fill composer. The strip is attached by default, the GIF is optional; press Enter yourself or use Send now.

The ingest endpoint (for programs)

Any local process (a mod, a test script, a dev server) can push events instead of writing a file:

curl -X POST http://127.0.0.1:<dsh-port>/dsh-incident-studio/api/ingest \
  -H "content-type: application/json" \
  -H "x-incident-token: <token shown in the panel's Settings>" \
  -d '{"text":"entered the raiders room","src":"RConnect","level":"info"}'

The token is per host process and visible in the panel; the same-origin panel needs no token. Pass wall (epoch ms) to place an event at an exact time; otherwise arrival time is used.

What you get on disk

Into the current session workspace, .dsh-incidents/<recording-id>/:

.dsh-incidents/20261104-193045-a1b2c3/
  frames-20261104193045.png   six-frame strip with timestamps and red boxes
  clip-20261104193045.gif     animated GIF of the range (460 px wide)
  bundle.md                   full note: description, box coordinates, every event line
  bundle.json                 the same, machine-readable

An excerpt of bundle.md:

# Character floats after picking up a chest

- **Captured at**: 2026-11-04 19:30:45 (+08:00)
- **Range**: `00:12.340` → `00:16.780` (00:04.440)
- **Capture path**: GDI screen grab 6fps

## What went wrong
Picked up the chest, the character levitates and never returns to the walk animation

## Frames
**Keyframe strip** — 6 of 26 frames, with the annotation boxes burned in
![Keyframe strip](.../frames-20261104193045.png)

## Annotations
- Region #1: normalized x=0.412, y=0.308, w=0.096, h=0.104; note: floating, no shadow; at 00:14.000

## Event stream (in range)
```log
[00:12.210] (host) RConnectGame: moveTest -> 966,900
[00:14.250] (host) ReferenceError: chest state mismatch (sost:LEVITATING)
[00:15.000] ⚑ problem

What lands in the composer is the **compact** version: phenomenon, range, artifact paths, box notes, and at most 80 log lines (errors and markers kept first); the full record is one path away.

## Three capture paths

| What the host process can reach | Path | Notes |
|---|---|---|
| `electron.desktopCapturer` (Electron main process) | timed JPEG grabs | 6 fps / 960 px / q62 by default; can pick a **window** |
| Node only (Electron utility process, plain `dsh web` host) | **Windows GDI grabber** (`tools/grab-frames.ps1`) | whole virtual desktop (all monitors), one long-lived PowerShell writing JPEGs; the host deletes each temp file as it ingests it |
| A real browser on `dsh web` | `getDisplayMedia` + `MediaRecorder` | needs screen-sharing permission; frames are extracted locally afterwards |

All three produce "frames with wall-clock time", so annotation, export and hand-off are identical.

> **Measured, not assumed**: the DSH desktop host cannot reach `desktopCapturer` (`/state` reports `nativeReason: "desktopCapturer 不可用"`), so the desktop app actually takes the GDI path. GDI uses `System.Drawing` + `Graphics.CopyFromScreen`: at 320 px / 4 fps a frame is ~5 KB, 1.4 s yields 5 frames with strictly increasing timestamps and the temp folder is gone after stop (`node test/live-capture.mjs`). The cost is that you cannot pick a single window.

Frame buffering is capped (900 frames / 96 MB / 5 minutes by default); the oldest frames are dropped, so a long recording cannot bloat the host.

## Configuration

The panel's Settings edits `<DSH_HOME>/storages/dsh-incident-studio/profiles.json` (`DSH_HOME` defaults to `~/.dsh`; on Windows `%USERPROFILE%\.dsh`). You can edit it directly:

```json
{
  "version": 1,
  "activeId": "my-project",
  "profiles": [
    {
      "id": "my-project",
      "name": "My project",
      "note": "main instance and test instance",
      "sources": [
        { "id": "main", "label": "main", "color": "#6aa9ff", "kind": "file",
          "path": "C:/Users/you/AppData/Roaming/MyApp/logs/main.log", "enabled": true, "preRoll": true },
        { "id": "test", "label": "test", "color": "#7bd88f", "kind": "file",
          "path": "C:/Users/you/AppData/Roaming/MyApp-test/logs/main.log", "enabled": true, "preRoll": true }
      ]
    }
  ]
}
  • path does not expand %VAR% or $HOME; use an absolute path or one relative to the workspace.
  • preRoll (on by default) takes the last 96 KB / 800 lines when recording starts and marks them "before the recording"; those lines have no per-line timestamp, so they get ordinal placeholders (-N ms) to keep their order.
  • kind is file today; ingest and marker events are generated by the plugin itself.

Optional host config, on the profile's patch row:

- id: incident-studio
  config:
    artifactsDirName: .dsh-incidents   # artifact folder, relative to the session workspace
    captureFps: 6                      # frame rate
    captureWidth: 960                  # frame width
    captureQuality: 62                 # JPEG quality

A copy-paste example: Remains (Adobe AIR) mod logs

AIR games write mod logs under File.applicationStorageDirectory, i.e. %APPDATA%\<app-id>\Local Store\:

C:/Users/<you>/AppData/Roaming/pfe/Local Store/<ModName>.log     ← main instance
C:/Users/<you>/AppData/Roaming/pfe2/Local Store/<ModName>.log    ← second test instance

Put those two absolute paths in two log sources. These logs are append-only: only new lines are captured, preRoll brings the tail in as "before the recording", and since the lines carry no timestamps the alignment is "the moment this line hit the file".

Install

Requires DeepSeek Harness 0.2.1+ (it uses the 0.2.1 Connection service and sidebar tab API).

# straight from this repo — client.js is committed, there is no build step
dsh plugin --profile <profile> add https://github.com/Eclipse-NotFound/dsh-incident-studio

# once it is on npm
dsh plugin --profile <profile> add dsh-incident-studio

Desktop profiles are managed by the app: install from Settings → Plugins with the package spec, not by pointing the CLI at profiles/web (that will not show up in the desktop app). A new host half needs one app restart; the browser half is republished and re-applied on the page.

Disable it by removing dsh-incident-studio from dsh.profile.bundles, or with a profile patch:

- id: incident-studio
  disabled: true

Privacy and boundaries

  • Frames are captured only after you press Start recording, and stop when you stop. The GDI path grabs the whole virtual desktop — move sensitive windows away first.
  • Frames, GIFs and notes are written only into the session workspace's .dsh-incidents/; profiles.json and the two receipts live in $DSH_HOME/storages/dsh-incident-studio/.
  • No network calls, no telemetry. Data reaches a model only after you press Fill composer / Send — through DSH's normal message path, exactly like typing it yourself.
  • Every /dsh-incident-studio/api/* route goes through the host's same-origin check; other local processes can only push events with the per-process x-incident-token. /open may only open paths inside this session's .dsh-incidents/.
  • The self-check receipts record counts and stages only (client-boot.json: how far the page got; capture-capability.json: how many screens/windows exist). No window titles, no pixels.

Develop

npm ci                        # dev deps: type references plus jsdom / react-dom / gifenc for tests
node tools/build-client.mjs   # concatenate src/client/*.js into client.js, with a syntax check
node test/run.mjs             # 250 assertions: host routes, client pure functions, seat registration, jsdom click-through, GIF/strip, capture polling
node test/live-capture.mjs    # real GDI capture (grabs ~1.4 s of screen at 320px, deletes it immediately; keep it out of CI)
node tools/check-live.mjs --fetch   # check a running DSH: routes, module graph, served bundle, capture capability, page receipts

client.js is a generated artifact — do not edit it by hand. src/vendor/gifenc.js is generated from node_modules/gifenc by tools/vendor-gifenc.mjs (MIT).

index.js                             host half: log tailing, frame capture, 17 routes, artifacts
src/client/00-head.js … 90-tail.js   browser half, concatenated into client.js in order
tools/grab-frames.ps1                Windows GDI screen grabber
tools/check-live.mjs                 live self-check (routes / module graph / served bundle / receipts)
test/host.test.mjs                   real HTTP server exercising every route
test/pure.test.mjs                   loads the shipped client.js and tests the pure helpers
test/client-apply.test.mjs           runs apply() against a fake client context, then the hand-off chain
test/capture.test.mjs                capture polling, stop, failure reporting, API error mapping
test/dom.test.mjs                    clicks through the whole panel inside jsdom
test/live-capture.mjs                real-machine GDI capture

check-live.mjs answers "is it actually loaded": 403 means a route is registered and rejecting external callers by design (404 means it is not registered); the module graph must contain this plugin and the served client.js must be byte-identical to your build (plus one source-map trailer); client-boot.json must show apply → seat → panel.

Known limitations

  • Publishing timing: the host half is assembled at boot (editing index.js needs a DSH restart); the browser bundle follows live updates and re-runs apply when the host republishes. Measured: a real content change to the profile's cordis.patch.yml triggers a host recompose, editing files inside the plugin folder does not.
  • Alignment precision: tailing sees a line once it is written, so expect tens of milliseconds; use /ingest with wall when it must be exact.
  • The desktop path is a frame sequence, not a video: 6 fps reads gradient/interpolation glitches fine, single-frame animation curves less so. Raising captureFps to 10–15 costs memory and JPEG work.
  • GDI grabs the whole desktop: no per-window capture, all monitors merged.
  • GIF uses one 128-colour palette (fixed from the first frame): small files and a clean picture for screen content; large gradients may band slightly.
  • Browser webm extraction relies on seeking: MediaRecorder output has no index, so a trailing frame can be lost in extreme cases.
  • No voice: descriptions are typed and boxed.
  • Artifacts live in the workspace: add .dsh-incidents/ to your .gitignore.

License

MIT. The product instinct is inspired by AGIHunt/blurt (a macOS menu-bar "talk to your agent" tool); this is a general-purpose incident panel inside DSH, with a different implementation and setting.

—/ 5

No ratings yet

Verified DSH bundle

Commit 971ed2b63baf

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout