DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

9087 /

9087/dsh-shell-progress

Verified

This plugin has no description yet.

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

dsh-shell-progress

English | 中文

npm license node CI harness matrix release

A DeepSeek Harness plugin that shows where a multi-step shell command is right now, as a segmented progress bar inside the tool-call card.

One call, one card — photographed mid-run, not drawn by hand:

The card while a four-step command runs: a partly filled segmented bar with the step in flight and its clock, the finished step's output beneath it, and the steps still to come

The real card, expanded, with 1/4 done and step 2 in flight.

The question it answers is "is this command still working, or is it stuck?" — so the live position is the feature, not a bonus. The bar lives in the collapsed card: a glance at the transcript is the whole point, and hiding it behind a disclosure would make it a two-step interaction.

Install

dsh plugin --profile web add dsh-shell-progress

Or mount it by hand in the profile's cordis.patch.yml:

- insert:
    - id: shell-progress
      name: dsh-shell-progress

Then restart dsh web (the host loads the plugin once, at start-up).

What the model sees

Nothing new. shell_progress registers a tool whose model-visible text is byte-identical to what the pwsh tool returns for the same command — same stdout, same [stderr] section, same [exit code: N] markers. Step data never enters the conversation; it travels in the tool result's presentationMeta, which the harness persists on the tool/result event.

There is no nonce, no marker and no reordering anywhere the model can read.

Configuration

- insert:
    - id: shell-progress
      name: dsh-shell-progress
      config:
        enabled: true
        pollIntervalMs: 120
        maxStepOutputChars: 8192
        maxSteps: 400
        liveRetentionMs: 300000
Field Default Meaning
enabled true Register the shell_progress tool.
pollIntervalMs 120 How often the host reads the running process's output.
maxStepOutputChars 8192 Per-step output kept in the persisted progress.
maxSteps 400 A command split into more steps than this runs unmodified.
liveRetentionMs 300000 How long a settled call's live record is kept for a late poll.

When a step's output passes maxStepOutputChars, the lines that do not fit are dropped from the CARD's copy, and that step's output is followed by the number of characters that are missing — in the step list and in the segment's hover card alike. Without it a truncated step reads exactly like a quiet one. The budget is the card's memory, not the tool result: the model-visible stdout comes from the shell seam and is unaffected by it (the seam's own truncated / spillPath reporting applies as it always does).

How it works

  1. Split. The command is cut into top-level steps: non-empty lines that start at column 0, do not start with }, and sit at brace depth 0. Brace counting skips single-quoted strings, double-quoted strings (with the backtick escape) and # comments. Loop bodies are never steps, so one iteration cannot produce a marker.
  2. Instrument. Each step's own line gets an inlined marker prefixed to it — never a new line, so the line count and every other line stay untouched.
  3. Run. The instrumented text goes through ctx.shell, exactly as the pwsh tool runs a command, with the same timeout, sandbox policy, managed environment and cancellation.
  4. Follow. While the process runs, the host reads its incremental output and turns each marker into "the current step", stamping a monotonic timestamp.
  5. Persist. The final per-step timings and output ride output.presentationMeta, so a reloaded session rebuilds the whole card from the session log alone.

The marker must stay inlined

src/instrument.ts carries the long version; the short one is that a PowerShell function call is itself a command, and running it overwrites $?. A marker written as a helper therefore clobbers the automatic variable it was asked to preserve — and the caller's $? afterwards reports the helper's success, so even a helper that captures and restores the value internally cannot fix it. Measured on this host (Windows PowerShell 5.1, which is what the shell seam resolves to when PowerShell 7 is not installed):

Marker shape $? after a failed command
none (control) False ✅
helper function, capture + restore inside True ❌
inlined capture + restore False ✅

tests/powershell.spec.ts runs both shapes against a real shell and asserts the difference, so a future refactor into a helper fails the suite.

When it does not track

The command runs unmodified — and no marker is written — when any of these hold:

  • the splitter meets something it cannot decide (unterminated quote, block comment, here-string, line continuation, unbalanced braces or parentheses);
  • the command already contains a step-marker shape, or already uses $__ok;
  • the resolved sandbox mode is read-only;
  • the command splits into more than maxSteps steps.

A refused call's card is the plain one: the command and everything it printed, with no bar, no counter and no step rows. There is nothing to draw — no marker was written, so no step has a time or an output of its own, and the planner's best-effort step list is dropped rather than rendered as rows that could only ever read "pending". The reason for the refusal goes to the host log (shell-progress: running the command unmodified — …), not onto the card.

A command with one step is uninstrumented too — there is no order to keep, so there is nothing to mark — but its card is not degraded: the host stamps that step's own start and end, so the bar draws its single segment and it goes pending → running → done with the command's clock beside it.

None of these is an error: the model gets exactly what pwsh would have given.

Layout

Half Source Runs in
Host src/index.ts (+ the pure modules beside it) the DSH host process (Node)
Client src/client/* (React) the browser

The two halves share src/types.ts and src/split.ts and nothing else.

The live channel is a poll, not a push. A plugin installed outside the DSH tree cannot receive host events — the forwarded-event allowlist is a static array inside the DSH build — so the browser polls /shell-progress (an authenticated request/response channel this plugin owns) every 250ms — the store's default cadence — while a call is in flight. The settled state never needs the poll: it is already in the session log.

Development

pnpm install
pnpm run typecheck
pnpm test
pnpm run build

The three workflow badges at the top are these:

Workflow What it does
ci.yml Typechecks, builds and runs the unit suite against the harness versions pinned in devDependencies — ONE host version, the one this repository declares.
harness-matrix.yml Re-runs the three suites that cross the plugin/host seam (the registered tool driven through apply, the pwsh planner, the bash planner) once per published harness version — the only real answer to "will this still work after the host upgrades". A version inside the declared peer range blocks the build; the newest published version rides along as a non-blocking probe.
release.yml On a v* tag: the same gate, then npm publish --provenance through Trusted Publishing (OIDC — no token anywhere), then a GitHub Release.

See DEVELOP.md and PUBLISH.md.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit 30dea8b79dbc

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