# dsh-cmdwatch — Command Window Real-time visibility into the commands dsh launches and their output, so you can watch foreground and background execution progress without pausing the conversation. [中文](README.md) | [English](README.en.md) **DSH Target**: `>=0.1.1-rc.2 <0.3.0` (verified on 0.1.1-rc.2 and 0.2.x) > DSH is in developer preview; breaking changes are expected. The compatibility > range and version tracking for this plugin live in `CHANGELOG.md`. ## Install ### One-command install (recommended) ```powershell dsh plugin --profile web add github:GavinQiEr/dsh-cmdwatch ``` > If `dsh` is not on your PATH, use `npx '@deepseek-ai/dsh' plugin --profile web add ...`. Restart the web profile (`dsh web`) after installing; a "命令监视" panel appears above the composer. ### Alternative installs | Method | Command | | --- | --- | | npm package | `dsh plugin --profile web add dsh-cmdwatch` | | git repo (full URL) | `dsh plugin --profile web add https://github.com/GavinQiEr/dsh-cmdwatch.git` | | specific branch/commit | `dsh plugin --profile web add github:GavinQiEr/dsh-cmdwatch#main` | | local directory (dev) | `dsh plugin --profile web add /path/to/dsh-cmdwatch` | | packed tarball | `dsh plugin --profile web add ./dsh-cmdwatch-0.5.15.tgz` | ## Features - **Background jobs** (`run_in_background: true`): **live progress stream** — command line, progress, status and timestamps scroll line by line; click a row to expand the full output. - **Foreground commands** (pwsh/bash): the command appears the moment it is issued and **streams live too** — the plugin injects `Tee-Object`/`tee`, polls the log file, and keeps the original pipeline semantics (when the command has `Select-Object -Last`/`Out-File`/… pipes, the Tee is inserted **before** them, so the tool result is unchanged). - **Per-session isolation**: the panel only shows **this session's** commands (foreground + background). Other sessions never leak in — the right sidebar lives in a root-scope slot (`shell.overlay`) with no session share, so the panel relays the session id from a session-scoped mount; with no id it stays empty instead of degrading to "show everything". **Switching sessions clears and reloads the panel immediately**, and switching back still shows that session's history. - **Live stream**: enabled by default — the plugin polls background job output every 500ms. - **Live-first ordering / auto-expand / archive**: running/stopping records are pinned to the top and the **topmost one expands automatically** (output area grows to 40vh; the original command is collapsed by default and expands on click). Finished records stay in the main list for 5s, then fold into the "已完成 (N)" group below (collapsed by default). - **Auto-scroll**: the output area scrolls to the newest line automatically. - **Panel position**: bottom (below the composer) or a floating right sidebar, toggled with `↕` in the header (persisted in localStorage). The right sidebar can be collapsed with the `»` tab on its left edge; when collapsed, a `«` tab at the screen edge reopens it. - **Long-command shortening**: whitespace is flattened, long commands show first 60% + … + last 40%; hover for the full text. - **Command sanitizing**: when dsh issues a command with a collecting pipeline (`| Select-Object -Last N`, `| tail -n N`), an output redirect (`| Out-File`, `>`), a filter (`Select-String`, `grep`, `Where-Object`, `Format-*`, `ConvertTo-*`, `Out-String`, `wc`) or an early-terminating pipe (`-First`, `head`), the plugin inserts `Tee-Object`/`tee` **before the earliest such segment** and injects `PYTHONUNBUFFERED`. The panel gets the full progressive output while **the original pipeline semantics are preserved** (`-Last N` still returns the last N lines, `Out-File` still writes the file). Multi-statement / multi-line commands are covered **per top-level statement** (`;` or newline). Only real problems surface as a yellow `⚠`; routine notes ("Tee inserted", "live capture", "env injected") live in the hover tooltip and one grey line in the expanded row. ## Foreground vs background: which to use | Execution mode | Live progress | Notes | | --- | --- | --- | | **Background job** `run_in_background: true` | ✅ streaming | Without collecting/redirect segments the plugin reads the official `jobs` channel; with them it inserts a Tee and polls the file. **Recommended for long commands (pytest, builds, scripts)** | | **Foreground command** (default) | ✅ streaming (Tee capture) | The plugin inserts `Tee-Object`/`tee` and polls the log; Tee goes before collecting/redirect segments, so **the tool result is unchanged** (e.g. `-Last 3` still returns the last 3 lines) | Both modes work for live progress: ```powershell # Foreground (default): the plugin inserts a Tee, the panel scrolls live, # and dsh still receives the original result. python -m pytest tests/... -q ``` > ⚠️ The plugin injects `Tee-Object`/`tee` before execution; output is written to > `.cmdmon-.log` in the workdir (or the temp dir) and polled for live > display; the file is deleted when the command finishes. Statements that cannot > be safely piped (assignments/`export`, control flow, `if`/`foreach` blocks) are > skipped — such calls get their full output filled in from the tool result at > completion. Set `rewrite: false` (background) or `foregroundStream: false` > (foreground) to disable wrapping. ## Configuration | Key | Default | Description | | --- | --- | --- | | `rewrite` | `true` | Insert a Tee for background commands with collecting/redirect segments | | `pythonUnbuffered` | `true` | Inject `PYTHONUNBUFFERED` when python is detected | | `foregroundStream` | `true` | Insert a Tee for foreground commands and stream live | | `warn` | `true` | Show panel warnings for collecting/redirect/terminating pipelines | | `debug` | `false` | Print `[cmdmon]` diagnostic logs to the host terminal (troubleshooting) | Add `config` to the plugin entry in `cordis.patch.yml`: ```yaml - insert: - id: dsh-cmdwatch name: dsh-cmdwatch config: rewrite: false # example: warn only, never rewrite ``` ## Precise wait API (`/cmdmon/wait`) A main process waiting on a child process usually has to guess a duration. `job_output`'s `wait: true` is event-driven but only available to the **job owner** and capped at 30s by default. This endpoint turns "zero-estimate waiting" into a primitive any process/session can use: ``` GET /cmdmon/wait?key=&timeout_ms=N[&session=|&all=1] GET /cmdmon/wait?timeout_ms=N[&session=] # no key: wait until nothing is running ``` - Returns **immediately** once the record reaches a terminal status (`completed`/`failed`/`killed`); keeps waiting if the record does not exist yet (can wait for a job that is about to start) - `timeout_ms` defaults to 600000 (10 min), capped at 3600000 (1 h); on timeout the body has `timedOut: true` - Same session-isolation rules as `/cmdmon/snapshot`; polling stops when the client disconnects - The body includes `record` (same fields as the panel: `status`, `finishedAt`, `output`, …) ```powershell # Zero-estimate wait (key looks like job:pwsh-3 or tool:call_xxx) $r = Invoke-RestMethod 'http://127.0.0.1:/cmdmon/wait?key=job:pwsh-3&timeout_ms=600000' if (-not $r.timedOut) { "done: $($r.record.status) ($($r.record.detail))" } ``` ## Build (developers) ```sh npm install npm run build:client # esbuild bundles client/index.jsx → client/client.js npm test # unit + host integration tests (244 cases: sanitizer 108 / owner registry 32 / session 25 / panel list 42 / host timing 28 / precise wait 9) npm pack # produces dsh-cmdwatch-0.5.15.tgz ``` Publish to npm (optional): ```sh npm login npm publish ``` ## Notes - With "live stream" on, the plugin consumes background job output deltas, so dsh's `job_output` reads empty increments (`(no new output)`) — a deliberate trade-off: the plugin watches the output so you don't have to pause the conversation. Turn the live-stream switch off if dsh must read the output. - Records live in host-process memory and are cleared on restart (live monitor, not a persistent log). - Sanitizing happens at `tools/execute`, after the `tools/pre-execute` permission gate — it only inserts a Tee pipe and injects env vars, never changing the program, arguments, or the original pipeline semantics. - **A background job shows a single "task" row**: the parent tool row (whose output is just `started background job ...`) is hidden while the task row exists, so the same command never appears twice. Warnings/original command/ notes are inherited by the task row, and the parent row reappears if the task row is cleared. - Troubleshooting: set `debug: true` for `[cmdmon]` logs in the host terminal, or read `diagEvents` from `/cmdmon/snapshot?all=1`; the expanded row also shows diagnostics. - Session isolation (enforced on the host): `/cmdmon/snapshot` without `session` returns no session-owned records (`all=1` is diagnostics-only); `/cmdmon/clear` does nothing without an explicit `{ session }` or `{ all: true }`. ## License MIT