# dsh-terminal-tabs [English](README.md) | [中文](README.zh.md) Turn the background commands your model starts into something visible and closable inside the DeepSeek Harness web UI: a **Terminals view tab** next to Chat / Trajectory whose label tracks how many background shells and subagents the current session has running (`命令行1`, `命令行×3`). Open it for a management page — one row per running command with a Chinese purpose title, a collapsible one-line command body, start clock plus ticking duration, a live output pane, and a Close button that actually kills the process. ## Features - **Real view tab** — registered through the official `conversation.view` seat, sibling of Chat / Trajectory; selection is managed by the framework. - **Live label** — the tab text re-resolves on every projection from the host-pushed `jobsBySession` mirror, scoped to whichever session you are viewing. Zero observation RPC. - **Purpose titles** — a local heuristic dictionary maps each command to a short Chinese phrase (`cd` → 打开文件夹 "Open folder", `git status` → 查看 Git 状态 "Git status", `Start-Sleep` → 等待 "Wait", `npm run dev` → 启动开发服务 "Start dev server"). Subagents show as 子代理任务 "Subagent task"; unknown commands fall back to 执行命令 "Run command" while the raw text stays fully visible. - **Collapsible body** — the command renders one ellipsised line; the chevron flips down/up and unwraps the full text. - **Live output** — expanding a row opens a dark scrollback fed by a private tee on the spawned process: newest ~10 lines auto-follow, wheel up browses history, returning to the bottom resumes following. - **Timing** — start wall-clock (HH:MM:SS) and elapsed time (秒 → 分秒 → 时分) tick every second. - **One-click close** — kills the job through the node half's HTTP route; the jobs registry ownership fence still applies, so cross-session requests are rejected. - **Stable numbering** — rows keep their number while open; re-entering the page renumbers compactly. Background subagents are included, marked with an indigo dot. ## How live output works (boundaries) The browser never touches the model's consuming cursor. The node half wraps `ctx.shell.start()` at the instance level: every successfully spawned process gets an internal pump (300 ms) that drains the pipe into a per-process ring buffer (48 KiB cap). The wrapper is the process's single consuming cursor; a model-side read returns everything accumulated since *its* previous read, byte-for-byte — only the cadence changes. Consequences: - Background jobs started **before** the wrapper mounted have no captured output ("(暂无输出)"). - Persistent PTY terminal sessions (`ctx.terminals`) do not flow through this channel. - The wrapper understands the current `ShellProcessRead` shape (`{ delta }`); if that contract changes upstream, output display degrades to empty while close/kill keeps working. ## Configuration (cordis.yml `config` row; all optional) | Key | Default | Meaning | |---|---|---| | `routePath` | `/dsh-terminal-tabs/close` | Absolute pathname of the kill route | | `tailPath` | `/dsh-terminal-tabs/tail` | Absolute pathname of the output-tail route | | `includeKinds` | `["bash","pwsh","subagent"]` | Job kinds shown in the UI | ## Install ```sh dsh plugin --profile web add github:bbboy31/dsh-terminal-tabs ``` Pure-JavaScript dual-plane plugin (node half `index.js`, browser half `client.js`) — no build step. After installing, refresh the web page. ## Uninstall ```sh dsh plugin --profile web remove dsh-terminal-tabs ``` Or use 插件市场 → installed list → 卸载 (two-step confirm). Uninstalling removes the routes, tab, and stylesheet with the fiber — no residue. > Development note: the Loader caches imported modules by package name — editing the node half requires a package rename or a process restart to take effect; client-half edits apply on page refresh. ## License [MIT](LICENSE)