@dsh-xhl/dsh-live-inspector
v1.0.0
Published
Git Tree and live diff inspector for the DeepSeek Harness web surface: watch every file the agent touches, review changes turn by turn, and undo a specific turn or a single hunk — with the filesystem reached through the plugin's own Host HTTP routes.
Downloads
121
Readme
@dsh-xhl/dsh-live-inspector
DeepSeek Harness (DSH) Web plugin that provides a lightweight, real-time Git Tree & Live Changes Inspector in the right sidebar (sidebar.right tab), with turn-scoped and hunk-level undo.
Features
- Single Tab Architecture (Zero RAM Bloat): Instead of opening heavy editor/viewer tabs for every accessed file, it keeps everything inside a single, high-performance Git Tree tab (
~10 KBmemory footprint). - Live File Status Badges:
[M](Amber): Modified files (edit,str_replace_editor).[A](Emerald): Created / added files (write,write_file).[R](Sky Blue): Read / inspected files (read).[D](Red): Deleted in this session by an undo.
- Real-Time Active Indicator: Displays
⚡ Agent active on: <path>with a pulsing live status on the exact file currently being touched. - Interactive Controls:
- Filter by
All,Changes (M/A), orReads (R). - Instant text filter search.
- Optional
Viewbutton to open a single file preview only when explicitly desired. Clearbutton to reset the session file list.
- Filter by
Undo
Undo is scoped to a turn. There is no file-level or session-level undo: undoing turn N restores the state each file entered turn N with, so earlier turns always survive.
Only the newest turn is undoable
Undo controls appear only while the newest turn is selected. On any older turn they are absent — not greyed out — because an older turn has already been reviewed, and a visible dead button would imply an action the panel is going to refuse.
Today's turn is the default, so the panel opens ready to undo what the agent just did.
The selected turn scopes the whole panel:
- the file list shows only the files that turn changed (
All (n)counts that scope), - the diff inspector follows, re-selecting a file when the turn changes,
- the
⟲ Undo Turn N (n)button undoes that turn across every file it changed.
All turns is available in the selector for reading history across the whole session; it is a
view rather than an undo scope, so no undo controls appear while it is selected.
- Each file row shows its turns (
Turn 3, orTurn 1–4) and carries↶ Undo Turn N. - A file's detail header carries
↶ Undo change, which reverts just the selected hunk. - Older turns' change counts are labelled
reviewed, and hunks outside the selected turn are dimmed and not individually revertible.
The turn snapshot
A file's text is captured immediately before that turn's first mutation of it, so the snapshot
is exactly what the file inherited — including every earlier turn's work. Writing it back is
what makes a turn-scoped undo unable to reach past its own turn. A read before the first
mutation counts as untouched too, which is what catches a write-after-read.
Line / diff-level undo
Reverts one contiguous change — one tool call's hunk — leaving the file's other changes in place.
- Every hunk the diff view shows is listed as a
#nchip above the diff, each with its own↶ Undo changebutton. - A hunk is undone by replacing its recorded post-change text with its recorded original text, so unrelated edits in the same file survive.
Deleting and restoring
Deleting is the only destructive action, so it is the only button rendered as a solid red
fill. Every restorative action — hunk undo, turn undo, ⤴ Restore — is amber, because it
writes content back rather than removing it.
Two kinds of deletion are covered:
- A file the agent created and you delete with
🗑 Delete file. The row becomes a strike-through[D]and⤴ Restorewrites it back from the recorded content. - A file deleted by a shell command (
rm,del, …). The file tools have no delete verb, so the panel cannot see the operation directly. After any shell call it re-probes the paths it already knows about, and a path that vanished becomes a[D]row you can restore from the newest recorded content — or from the turn snapshot if the file was only read.
A deleted file with no recorded content cannot be restored, and its ⤴ Restore says so rather
than failing on click.
Safety rules
Undo refuses rather than guesses:
- Drift: if the file changed since the edit, the recorded text no longer matches and the undo is refused.
- Ambiguity: if the recorded text now occurs more than once, the undo is refused.
- No artifact: a change with no exact recorded before/after pair (replayed history, or a
tool other than
edit/write) is never offered as individually revertible. The panel says so instead of showing a button that would corrupt the file. - Older turn: undo is not offered at all outside the newest turn.
- Mid-write: a file the agent is currently writing is not undoable until the step ends.
- Oversized: files above 512 KB get no text snapshot. They are still recognized by digest, but a restore is refused.
- Roots: paths that resolve to a filesystem root, a drive root, or a UNC share root are rejected outright.
Destructive actions need a second click (the button switches to a ⚠ Confirm … state).
Architecture
lib/index.js— Host half. Owns real filesystem access and publishes a Typert Remote namespace namedinspectorFswith four methods:readText,writeText,deleteFile, andstatPath(the last detects deletions performed outside the file tools). Paths resolve throughctx.fs.processPathFromHostPathwhen the backend provides a mapping, and by joining the session working directory otherwise.lib/client.js— Browser half. Records file activity, diff artifacts, and per-turn snapshots from session events, then renders the panel and undo controls.
Why a Remote namespace, not host.call
host.call(method, args) is an API for dynamic packages — the ones an agent authors as a
code string. There, host is injected as a closure parameter by the runner.
An installed bundle's Client module runs under window.__ModuleLoader__, which resolves only a
fixed seed list (react, react-dom, @deepseek-ai/cordis, @deepseek-ai/dsh-client-store,
the dsh-client-ui-* primitives) and traps require itself. host does not exist there, so
host.call throws host is not defined. A bundle reaches its Host half through a Typert Remote
namespace consumed as ctx.inject(['remote.<namespace>']).
This package has no build step, so lib/index.js writes the protocol's Remote-method descriptor
onto the service prototype by hand instead of using the build-time @Remote decorator. The
Gateway's source-mode (src-json) fallback accepts it: parameter names come from the function
signature and arguments travel as plain JSON, so no generated schema is required.
Method failures reject across the boundary. The Browser half converts a rejection into the
{ ok: false, error } shape it reasons about, so a refused path surfaces as one toast rather
than an unhandled throw.
Per-turn snapshots are keyed by file and turn. A snapshot is read immediately before that turn's first mutation of the file, so it captures exactly what the file inherited — including every earlier turn's work. That is what makes a turn-scoped undo unable to reach past its own turn.
The pre-edit baseline read is authorized by this plugin's own approval/request listener,
which allows fs-read only; every other request is left untouched for the normal
approval chain.
Installation
Published to npm as @dsh-xhl/dsh-live-inspector:
dsh plugin --profile web add @dsh-xhl/dsh-live-inspectorFrom a checkout, dsh plugin --profile web add <path-to-this-directory> installs it as a
local link, which is what the test profile does.
Manifest notes
dsh.bundle.patchpoints atcordis.patch.yml, whose row inserts the plugin under the iddsh-live-inspectorwithname: '@dsh-xhl/dsh-live-inspector'.dsh.clientdeclaresplatform: weband the two sidebar Client packages this UI orders itself against.lib/client.jsregisters intowindow.__ModuleLoader__under anidthat must equal the package name. The loader derives the expected module id from the manifest and reportsloaded without registering "<id>"when the two disagree, so renaming the package without updating that id silently drops the panel.filesships onlylib/plus the patch and README. The former root-levelclient.jsandindex.jscopies were removed;exportshas pointed atlib/for some time, and keeping two near-identical copies on disk invited editing the wrong one.
