dsh-any-agent 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +143 -1
  2. package/USAGE-NOTES.en.md +3 -1
  3. package/USAGE-NOTES.md +9 -2
  4. package/bin/dsh-any-agent-workspaces.mjs +12 -5
  5. package/bin/dsh-any-agent.mjs +27 -2
  6. package/bin/restore.mjs +1 -1
  7. package/docs/en/compatibility.md +27 -6
  8. package/docs/en/reference.md +4 -1
  9. package/docs/en/release.md +4 -0
  10. package/docs/en/security.md +1 -0
  11. package/docs/en/tests.md +59 -15
  12. package/docs/en/usage.md +22 -0
  13. package/docs/zh/compatibility.md +25 -6
  14. package/docs/zh/live-view-design.md +192 -0
  15. package/docs/zh/reference.md +4 -1
  16. package/docs/zh/release.md +3 -0
  17. package/docs/zh/security.md +1 -0
  18. package/docs/zh/tests.md +59 -15
  19. package/docs/zh/usage.md +22 -0
  20. package/package.json +4 -1
  21. package/scripts/detect-dsh.mjs +55 -0
  22. package/scripts/lint.mjs +595 -0
  23. package/scripts/sync-test-counts.mjs +699 -0
  24. package/src/acp-session.mjs +63 -0
  25. package/src/acp.mjs +14 -5
  26. package/src/config.mjs +39 -111
  27. package/src/conversation.mjs +1714 -0
  28. package/src/doctor.mjs +203 -0
  29. package/src/dsh-locate.mjs +560 -0
  30. package/src/git-snapshot.mjs +196 -0
  31. package/src/hub-store.mjs +108 -0
  32. package/src/hub.mjs +33 -1972
  33. package/src/live-cli.mjs +115 -0
  34. package/src/live-dashboard.html +535 -0
  35. package/src/live-dashboard.mjs +176 -0
  36. package/src/live-view.mjs +592 -0
  37. package/src/live.mjs +186 -0
  38. package/src/mcp.mjs +24 -75
  39. package/src/residue.mjs +282 -0
  40. package/src/sentinel.mjs +97 -0
  41. package/src/tool-defs.mjs +399 -0
  42. package/src/tool-render.mjs +262 -0
  43. package/src/tools.mjs +18 -767
  44. package/src/workspace-guard.mjs +89 -0
  45. package/src/workspace.mjs +295 -59
package/CHANGELOG.md CHANGED
@@ -7,6 +7,143 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.1] — 2026-11-08
11
+
12
+ ### Added
13
+
14
+ - **Live call-chain view of dispatched work.** The server now appends each
15
+ conversation's activity — run start/end (with how it was dispatched: send,
16
+ queue or interject, and which MCP client sent it), tool calls on status change,
17
+ approvals and their decisions — to `<state dir>/live/<conversation_id>/events.jsonl`.
18
+ Two new read-only subcommands show it without going through the calling agent:
19
+ `dsh-any-agent watch` (terminal tree) and `dsh-any-agent dashboard` (local page on
20
+ `127.0.0.1` with a one-time token and Host check, no controls). Both run as
21
+ separate processes, never touch the write lock, and merge conversations from
22
+ every client. `DSH_MCP_LIVE=detail|meta|off` (default `detail`: redacted
23
+ previews, the same exposure as sentinels) and `DSH_MCP_LIVE_DIR`. Reasoning is
24
+ never written; write failures never affect a run; files follow the sentinel
25
+ TTL and are deleted on `forget`. Design: `docs/zh/live-view-design.md`.
26
+
27
+ ## [0.6.0] — 2026-11-07
28
+
29
+ ### Added
30
+
31
+ - **Zero-config DSH discovery, including the desktop app.** The server now finds
32
+ the DeepSeek Harness desktop app on its own (Windows uninstall registry via
33
+ `reg export`, plus default install paths on every platform), reads the CLI's
34
+ version and `engines` straight out of `resources/app.asar` with plain Node,
35
+ and launches it on the app's bundled Electron runtime in Node mode
36
+ (`ELECTRON_RUN_AS_NODE=1`). Previously only npx caches and npm global installs
37
+ were searched, so with the desktop app on 0.2.2-rc.1 the server silently ran a
38
+ stale 0.2.1-rc.5 from an npx cache. Candidates are ranked runnable-first →
39
+ newest → desktop > npm global > npx. `DSH_BIN` stays as an explicit override
40
+ (it may point inside an `.asar`); new `DSH_RUNTIME` overrides the launcher,
41
+ both with `DSH_BIN` and on its own for auto-discovered npm/npx installs (whose
42
+ `engines.node` is then re-checked against it); the desktop app always keeps its
43
+ bundled runtime.
44
+ - `dsh_doctor`: `dsh_bin` shows the source and runtime; new `dsh_runtime`
45
+ (launcher's Node vs DSH's `engines.node` — 0.2.2 needs Node ≥ 24.9),
46
+ `dsh_desktop` (warns when the GUI and this server run different DSH versions
47
+ over the shared `~/.dsh`), and `dsh_candidates`.
48
+ - Background receipts on Windows carry `sentinel_read_powershell`, a reader that
49
+ passes `-Encoding UTF8` (Windows PowerShell 5.1 otherwise misreads the BOM-less
50
+ sentinel and `ConvertFrom-Json` fails on CJK text).
51
+ - `npm run lint` (`scripts/lint.mjs`, no npm dependencies): `node --check` on every
52
+ `.mjs`, relative import paths that resolve with exact case, named imports that
53
+ are real exports (including `await import()` destructuring and `node:`
54
+ builtins), and unused named imports. Runs first in every CI offline cell and at
55
+ the Node 22.15 floor; `test/lint.mjs` is its regression suite. Its first run
56
+ removed nine unused imports.
57
+ - **Test counts in the docs are generated, not hand-edited.** `test/run.mjs`
58
+ writes a machine-readable report (`.state/test-report.json` by default,
59
+ `--report <path>`; per suite: passed/failed/skipped/duration, plus platform,
60
+ Node, DSH and client-fixture context), every suite prints a standard summary
61
+ line, and `npm run docs:counts` rewrites the per-suite counts, totals and the
62
+ CI scope comment from it; `npm run docs:counts:check` fails on any mismatch.
63
+ The CI offline job runs the check on the platform-independent suites, and the
64
+ release checklist includes it. Several hand-maintained numbers were wrong and
65
+ are corrected (e.g. failure-terminal 10, not 11).
66
+
67
+ ### Changed
68
+
69
+ - **Internal: `src/hub.mjs` and `src/tools.mjs` are split into cohesive modules**
70
+ with no behaviour change. From `hub.mjs`: `conversation.mjs` (the `Conversation`
71
+ class), `git-snapshot.mjs`, `hub-store.mjs` (meta sidecar and forget
72
+ tombstones), `sentinel.mjs`, `workspace-guard.mjs`, `acp-session.mjs`. From
73
+ `tools.mjs`: `tool-defs.mjs`, `tool-render.mjs`, `doctor.mjs`. Both files
74
+ re-export every name they exported before, so existing import paths still work.
75
+ - `model-suites` workflow finds DSH through the project's own `resolveDsh()`
76
+ (`scripts/detect-dsh.mjs`) instead of `command -v dsh`, so a self-hosted runner
77
+ with only the desktop app installed works without secrets; `DSH_BIN` stays an
78
+ optional override.
79
+ - **`dsh-any-agent-workspaces --purge-test-sessions --ids …` now refuses
80
+ sessions whose cwd is not a test directory**, even when named explicitly, and
81
+ reports them as skipped. The purge also removes every artifact of a test
82
+ session (projection cache, registry entry, sentinels, reviews, locks), not
83
+ just its session directory.
84
+ - Compatibility matrix: DSH 0.2.1-rc.5 and 0.2.2-rc.1 are **verified** (full
85
+ suites, 2026-11-06); 0.2.3+ is unverified.
86
+ - Server `instructions` cut from ~4.4k to ~1.5k characters with the hard rules
87
+ first. Clients truncate long instructions (Claude Code dropped the old tail,
88
+ including the "don't" rules); `smoke` now enforces a 2400-character cap.
89
+ - `node test/run.mjs` runs **every** suite and lists all failures at the end;
90
+ `--bail` restores stop-at-first-failure. One stale assertion used to hide the
91
+ eleven model-backed suites after it.
92
+
93
+ ### Fixed
94
+
95
+ - **Test runs left residue in the real `~/.dsh`.** Purging a test session removed
96
+ only its session directory; the DSH projection cache
97
+ (`storages/session_projcache/sessions/<id>.json`) was never removed, so about
98
+ 400 orphan files piled up, and the project's own `runs/`, `reviews/`, `meta/`,
99
+ `locks/` and registry entries leaked too (eight suites kept their state loose
100
+ under `%TEMP%`, where nothing cleaned it). Every model-backed suite now purges
101
+ exactly the sessions it created (plus DSH-spawned subagent sessions) with every
102
+ artifact in its own `finally`; `test/cleanup.mjs` is only the backstop and also
103
+ sweeps old orphans. The purge refuses any session whose cwd is not a test
104
+ directory, even when named by id, and no longer re-registers every temp session
105
+ on disk into the GUI workspace list as a side effect. New offline suite
106
+ `residue-purge` proves both halves in a sandboxed `DSH_HOME`. With
107
+ `DSH_MCP_KEEP_EVIDENCE=1`, `cleanup.mjs` now skips the whole sweep, so the
108
+ sessions and state directories that `acceptance` / `workspace-effect` keep for
109
+ inspection survive the end-of-run cleanup.
110
+ - **`dsh_read` merged different tool calls into one entry.** Consecutive tool
111
+ updates in a turn were concatenated (`read [in_progress]read [completed]pwsh …`)
112
+ under the first call's `tool_call_id`. Each tool call now has its own entry and
113
+ a status update replaces it in place; only streamed text is still merged, so an
114
+ empty turn's start/end markers also stay separate. Because an in-place update
115
+ can give an earlier entry a higher revision, `dsh_read` now pages in revision
116
+ order (entries in a page are still returned in time order) so a small `limit`
117
+ can never skip an unread entry, and `gap` is computed from entries actually
118
+ evicted from the ring buffer instead of assuming revisions grow along it.
119
+ - **`answer` ran message segments together.** Text DSH sends before and after a
120
+ tool call is two messages, but the collected answer joined them with no
121
+ separator (`…where possible.1) package.json…`). A blank line now separates a
122
+ segment that follows a tool call (none is added when the previous segment
123
+ already ends with a newline, or before the first text); `dsh_read` entries are
124
+ unchanged.
125
+ - **A clean repository is a 0-change snapshot, not `null`.** `null` now only means
126
+ "no snapshot" (not a git repo, git unavailable, disabled). A turn that removed
127
+ the last change used to report `workspace_changed=true` with `diff_stat=null`.
128
+ - **A cold `session/resume` that DSH never answers is retried once.** A live
129
+ `test/multi.mjs` run once hit `ACP request timed out: session/resume` (60 s of
130
+ silence from a freshly spawned DSH); a rerun passed and it did not reproduce in
131
+ ~70 controlled resume cycles, 40 of them under concurrent load. The write lock is
132
+ ruled out (DSH takes it with a zero-timeout wait and refuses instantly), so it
133
+ looks like a rare stall inside the DSH process. `resume` is idempotent and the
134
+ failure path already kills the process and waits for it to exit, so `ensureAlive`
135
+ now rebuilds the process and retries exactly once — only for a timeout, never for
136
+ a write-lock refusal or invalid params, and not on a shared host that still carries
137
+ other conversations. `AcpProcess` timeout errors now carry `code=ACP_TIMEOUT` and
138
+ `method`, and the DSH stderr tail at the moment of a timeout goes to the opt-in
139
+ `DSH_MCP_LOG_STDERR` log. New offline suite `resume-retry` (fake ACP server).
140
+ - Tests: `timeout` asserts that a negative `timeout_ms` is rejected (the 0.4.0
141
+ behaviour) instead of the old "wait forever" semantics, and no longer passes on
142
+ an error result; `list` asserts caching via `probe_cached` instead of a 500 ms
143
+ wall-clock threshold; `integration`'s queue check uses a code token instead of
144
+ requiring the model to echo a phrase verbatim; `capability` prints the tool
145
+ error text when a call fails instead of an empty `err=`.
146
+
10
147
  ## [0.5.2] — 2026-11-05
11
148
 
12
149
  ### Fixed
@@ -1016,7 +1153,12 @@ First public release, published to npm as [`dsh-any-agent`](https://www.npmjs.co
1016
1153
  profiles, sessions and client configs. The product name changed; the on-disk
1017
1154
  contract did not. See the README FAQ.
1018
1155
 
1019
- [Unreleased]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.9...HEAD
1156
+ [Unreleased]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.6.0...HEAD
1157
+ [0.6.0]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.5.2...v0.6.0
1158
+ [0.5.2]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.5.1...v0.5.2
1159
+ [0.5.1]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.5.0...v0.5.1
1160
+ [0.5.0]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.4.0...v0.5.0
1161
+ [0.4.0]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.9...v0.4.0
1020
1162
  [0.3.9]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.8...v0.3.9
1021
1163
  [0.3.8]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.7...v0.3.8
1022
1164
  [0.3.7]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.6...v0.3.7
package/USAGE-NOTES.en.md CHANGED
@@ -24,7 +24,7 @@ Practical gotchas and trade-offs you *will* hit when letting Claude / Codex driv
24
24
 
25
25
  **1.4 Never register dsh-any-agent into DSH's own MCP list.** DSH is an MCP client, this server is an MCP server; pointing one at the other recurses forever.
26
26
 
27
- **1.5 `DSH CLI entry not found`.** Set `DSH_BIN` explicitly (auto-detection scans the npx cache, npm global, and common paths).
27
+ **1.5 `DSH CLI entry not found`.** Auto-detection finds the DeepSeek Harness desktop app (Windows uninstall registry plus default install paths on every platform; the CLI inside `app.asar` runs on the app's bundled runtime), npm global installs and the npx cache. Check `dsh_candidates` in `dsh_doctor`; for portable or custom locations set `DSH_BIN` explicitly (it may point inside `app.asar`).
28
28
 
29
29
  ## 2. Cost and budget
30
30
 
@@ -94,6 +94,8 @@ Practical gotchas and trade-offs you *will* hit when letting Claude / Codex driv
94
94
 
95
95
  **6.3 Temp directories are skipped by default** (`DSH_MCP_REGISTER_WORKSPACE=project`) so test runs don't flood your GUI. Use `all` to include them.
96
96
 
97
+ **6.4 Running the tests must not leave anything in `~/.dsh`.** Symptom: the session count under `~/.dsh` keeps growing, and `storages/session_projcache/sessions/` holds many orphan files whose session directories are long gone. Cause: model-backed tests use your real `~/.dsh` (the credentials live there, so it cannot be isolated), and the old cleanup removed the session directory but neither the projection cache nor this project's own `runs/reviews/meta/locks`. Now: every model-backed suite removes the sessions it created, with every artifact, in its own `finally`, and `node test/cleanup.mjs` is the global backstop. **Only sessions whose cwd is a test directory are ever deleted** — naming an id does not bypass that. See `docs/en/tests.md`.
98
+
97
99
  ## 7. Privacy
98
100
 
99
101
  **7.1 Silent by default** — not one byte. MCP's stdout is the protocol channel, and stderr lands in Claude/Codex's logs.
package/USAGE-NOTES.md CHANGED
@@ -37,8 +37,10 @@
37
37
 
38
38
  ### 1.5 `DSH_BIN` 探测失败
39
39
  - **现象**:启动即报 `DSH CLI 入口未找到`。
40
- - **原因**:DSH 装在非常规位置(本服务按 npx 缓存 → npm 全局 → 常见路径的顺序通配搜索)。
41
- - **对策**:显式设置 `DSH_BIN=/path/to/node_modules/@deepseek-ai/dsh/lib/bin.js`。
40
+ - **原因**:DSH 装在非常规位置。本服务自动识别 DeepSeek Harness 桌面版(Windows 卸载注册表 +
41
+ 各平台默认安装位置,用桌面版自带运行时跑 asar 里的 CLI)、npm 全局安装和 npx 缓存。
42
+ - **对策**:先跑 `dsh_doctor` 看 `dsh_candidates`;便携版/自定义位置再显式设置
43
+ `DSH_BIN=/path/to/node_modules/@deepseek-ai/dsh/lib/bin.js`(可指进 `app.asar` 内部)。
42
44
 
43
45
  ---
44
46
 
@@ -226,6 +228,11 @@
226
228
  - **原因**:跑测试会在 `%TEMP%` 下建一堆工作区,全登记会把你 GUI 灌满。
227
229
  - **对策**:默认模式 `project` 会跳过临时目录。要连临时目录一起登记就设 `DSH_MCP_REGISTER_WORKSPACE=all`。
228
230
 
231
+ ### 6.4 跑测试不该在 `~/.dsh` 里留东西
232
+ - **现象**:`~/.dsh` 里会话数越攒越多;`storages/session_projcache/sessions/` 里有大量会话目录早已不在的孤儿文件。
233
+ - **原因**:真机测试用的是你真实的 `~/.dsh`(凭据在那里,没法隔离);旧版清理只删会话目录,不删投影缓存,也不清本项目自己的 `runs/reviews/meta/locks`。
234
+ - **现状**:每个真机套件在 `finally` 里清掉自己造的会话及全部产物,全局 `node test/cleanup.mjs` 兜底;**只有 cwd 是测试目录的会话才会被删**,点名 id 也绕不过。详见 `docs/zh/tests.md`。
235
+
229
236
  ---
230
237
 
231
238
  ## 七、隐私与日志
@@ -15,8 +15,11 @@
15
15
  * node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions [--dry-run] [--ids=a,b]
16
16
  *
17
17
  * 只有 --purge-test-sessions 会动磁盘上的会话文件,且规则很死:只删
18
- * "cwd 在 tmpdir()/dsh-any-agent-* 下" 或 "cwd 在 Desktop\dsh-any-agent-test-*" 的会话,
19
- * 外加 --ids 显式指定的。**不会**删除用户在 %TEMP% 里自己的真实项目会话。
18
+ * "cwd 在 tmpdir()/dsh-any-agent-* 下"、"cwd 在 Desktop\dsh-any-agent-test-A|B" 或本仓库
19
+ * ".state/ws-probe-*" 的会话。**不会**删除用户在 %TEMP% 里自己的真实项目会话。
20
+ * --ids=a,b 只清点名的那几个,但**点名也绕不过护栏**:cwd 不是测试目录的会被拒绝并列出。
21
+ * 每个被删会话的全部产物一并清掉(日志、投影缓存、工作区登记、墓碑,以及本项目的
22
+ * 注册表/元数据/哨兵/审核/锁登记);另外清掉投影缓存里的测试孤儿。
20
23
  */
21
24
 
22
25
  import {
@@ -24,10 +27,10 @@ import {
24
27
  listWorkspaces,
25
28
  pruneEmptyWorkspaces,
26
29
  pruneTempWorkspaces,
27
- purgeTestSessions,
28
30
  WORKSPACE_FILE,
29
31
  SESSIONS_ROOT,
30
32
  } from '../src/workspace.mjs';
33
+ import { purgeTestResidue, sweepTestResidue } from '../src/residue.mjs';
31
34
 
32
35
  const argv = process.argv.slice(2);
33
36
  const has = (f) => argv.includes(f);
@@ -46,14 +49,18 @@ const ids = (val('--ids') ?? '').split(',').map((s) => s.trim()).filter(Boolean)
46
49
 
47
50
  // ── 清理测试会话(唯一会删文件的动作) ──────────────────────
48
51
  if (has('--purge-test-sessions')) {
49
- const res = purgeTestSessions({ dryRun, ids, testOnly: true });
52
+ // 点名(--ids)= 只清这几个;否则全盘扫荡。两条路都会连带清本项目侧的 per-run 数据。
53
+ const full = ids.length ? purgeTestResidue({ ids, dryRun }) : sweepTestResidue({ dryRun });
54
+ const res = full.sessions;
50
55
  console.log(`${flag}清理测试会话:${res.count} 个`);
51
56
  for (const v of res.victims) {
52
57
  console.log(` - ${v.id} [${v.reason}]${v.origin ? ` origin=${v.origin}` : ''} cwd=${v.cwd}`);
53
58
  if (v.error) console.log(` ⚠️ 删除失败: ${v.error}`);
54
59
  }
60
+ for (const s of res.skipped ?? []) console.log(` ✗ 拒绝 ${s.id}:${s.reason} cwd=${s.cwd}`);
55
61
  if (!res.count) console.log(' (没有命中任何会话)');
56
- if (!dryRun) console.log(' 已同步从工作区注册表摘除这些会话 id');
62
+ if (!dryRun) console.log(' 已同步清掉这些会话的投影缓存、工作区登记、墓碑与本项目 per-run 数据');
63
+ if (full.orphans) console.log(`${flag} 投影缓存测试孤儿:${full.orphans.count} 个`);
57
64
  console.log('');
58
65
  }
59
66
 
@@ -3,7 +3,8 @@
3
3
  * dsh-any-agent 入口:以 MCP stdio 服务端方式运行,用 DSH (ACP) 作为后端。
4
4
  *
5
5
  * 环境变量:
6
- * DSH_BIN DSH CLI 入口(默认探测 npx 缓存位置)
6
+ * DSH_BIN DSH CLI 入口(默认自动识别:桌面版 → npm 全局 → npx 缓存)
7
+ * DSH_RUNTIME 拉起 DSH 的可执行文件(默认:桌面版用其自带运行时,其余用本进程 node)
7
8
  * DSH_MCP_PROFILE 驱动用的 DSH profile(默认 dsh-mcp)
8
9
  * DSH_MCP_STATE 会话注册表路径(默认 $DSH_HOME/.dsh-any-agent/conversations.json)
9
10
  * DSH_MCP_PERMISSION 新建会话的默认权限档(默认 danger-full-access)
@@ -38,6 +39,8 @@ if (argv.includes('--help') || argv.includes('-h')) {
38
39
  ' dsh-any-agent run the MCP server on stdio\n' +
39
40
  ' dsh-any-agent team on|off enable/disable Agent Teams (writes settings.json)\n' +
40
41
  ' dsh-any-agent team status show the effective team mode\n' +
42
+ ' dsh-any-agent watch [conv] live call chain of dispatched runs in this terminal\n' +
43
+ ' dsh-any-agent dashboard same, as a read-only local web page (127.0.0.1 + token)\n' +
41
44
  ' dsh-any-agent --version print the version and exit\n' +
42
45
  ' dsh-any-agent --help this text\n\n' +
43
46
  'Register it with your client:\n' +
@@ -45,16 +48,29 @@ if (argv.includes('--help') || argv.includes('-h')) {
45
48
  ' dsh-any-agent-setup --yes apply\n\n' +
46
49
  'Key environment variables (all optional):\n' +
47
50
  ' DSH_BIN path to @deepseek-ai/dsh/lib/bin.js; auto-detected\n' +
51
+ ' (desktop app -> npm global -> npx cache)\n' +
52
+ ' DSH_RUNTIME executable that launches DSH; auto (desktop app\'s own runtime)\n' +
48
53
  ' DSH_MCP_PROFILE DSH profile to drive (default dsh-mcp)\n' +
49
54
  ' DSH_MCP_PERMISSION danger-full-access | workspace-write | read-only\n' +
50
55
  ' DSH_MCP_APPROVAL auto-allow | auto-deny | ask\n' +
51
56
  ' DSH_MCP_REVIEW advisory (default) | off — caller review ticket on results\n' +
52
- ' DSH_MCP_LOG silent (default) | info | debug\n\n' +
57
+ ' DSH_MCP_LOG silent (default) | info | debug\n' +
58
+ ' DSH_MCP_LIVE detail (default) | meta | off — live event stream for watch/dashboard\n\n' +
53
59
  'Docs: https://github.com/xmtlzzz/dsh-any-agent\n',
54
60
  );
55
61
  process.exit(0);
56
62
  }
57
63
 
64
+ /**
65
+ * `dsh-any-agent watch` / `dsh-any-agent dashboard`:只读查看 live 事件流(调用链)。
66
+ * 独立进程,不启动 Hub、不碰写锁、不连 DSH —— 必须在启动 Hub 之前分派。
67
+ */
68
+ if (argv[0] === 'watch' || argv[0] === 'dashboard') {
69
+ const cli = await import('../src/live-cli.mjs');
70
+ const code = argv[0] === 'watch' ? await cli.runWatch(argv.slice(1)) : await cli.runDashboard(argv.slice(1));
71
+ process.exit(code);
72
+ }
73
+
58
74
  /**
59
75
  * `dsh-any-agent team on|off|status`
60
76
  *
@@ -125,6 +141,15 @@ await hub.load();
125
141
  hub.startReaper();
126
142
  // 启动时清一次过期哨兵(哨兵内含正文,长期不清理会无声堆积)
127
143
  const prunedSentinels = hub.pruneSentinels();
144
+ // live 事件流与哨兵同一 TTL(单独调用,不改 pruneSentinels 的返回形状)
145
+ try {
146
+ const { pruneLive } = await import('../src/live.mjs');
147
+ const { SENTINEL_TTL_MS } = await import('../src/config.mjs');
148
+ const prunedLive = pruneLive(SENTINEL_TTL_MS);
149
+ if (prunedLive.pruned) log.info(`[dsh-any-agent] 启动清理:删除 ${prunedLive.pruned} 个过期 live 事件文件`);
150
+ } catch {
151
+ /* 清理失败不影响启动 */
152
+ }
128
153
  if (prunedSentinels.pruned || prunedSentinels.reviews_pruned) {
129
154
  log.info(
130
155
  `[dsh-any-agent] 启动清理:删除 ${prunedSentinels.pruned} 个过期哨兵、` +
package/bin/restore.mjs CHANGED
@@ -19,7 +19,7 @@
19
19
  import { chmodSync, existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
20
20
  import { basename, dirname, join } from 'node:path';
21
21
 
22
- import { atomicWrite, backupFile, clientList, resolveDirs, ENTRY_NAME } from '../src/clients.mjs';
22
+ import { atomicWrite, backupFile, clientList, resolveDirs } from '../src/clients.mjs';
23
23
 
24
24
  const argv = process.argv.slice(2);
25
25
  const has = (f) => argv.includes(f);
@@ -12,7 +12,8 @@ upgrading DSH, and run `dsh_doctor` afterwards.
12
12
  |---|---|---|---|
13
13
  | 0.5.x | `0.1.7-rc.1` or newer | ✅ Verified | ACP and the experimental Agent Teams packages are available; team mode is opt-in via `dsh-any-agent team on` |
14
14
  | 0.5.x | `0.1.5-rc.1` | ⚠️ Partial | ACP basics work; no team packages, so team mode stays off |
15
- | 0.5.x | `0.2.x` | ⚠️ Unverified | The ACP `session/*` surface may change; team packages must match the DSH version. Run `dsh_doctor` and the offline suite before upgrading |
15
+ | 0.5.x | `0.2.1-rc.5` – `0.2.2-rc.1` | ✅ Verified | Full suites run on 2026-11-06 (0.2.1-rc.5 from npm, 0.2.2-rc.1 on the desktop app's bundled runtime); from 0.2.2 DSH requires Node ≥ 24.9 |
16
+ | 0.5.x | `0.2.3` or newer | ⚠️ Unverified | The ACP `session/*` surface may change; team packages must match the DSH version. Run `dsh_doctor` and the offline suite before upgrading |
16
17
  | 0.4.x | `0.1.7-rc.1` | ✅ Verified | Team mode was on by default at that time |
17
18
  | 0.4.x | `0.1.5-rc.1` | ⚠️ Partial | No team packages |
18
19
 
@@ -22,22 +23,41 @@ upgrading DSH, and run `dsh_doctor` afterwards.
22
23
 
23
24
  ## Runtime requirements
24
25
 
25
- - **Node.js ≥ 22.15** (uses `zstdDecompressSync` from `node:zlib` to read DSH session headers);
26
+ - **Node.js ≥ 22.15** for this server (uses `zstdDecompressSync` from `node:zlib` to read DSH session headers);
26
27
  - DSH installed and `~/.dsh` already has a working provider and credentials (the GUI can chat);
28
+ - **DSH's own Node requirement** (`engines.node`, ≥ 24.9 from 0.2.2) is met by whatever runtime launches DSH:
29
+ the desktop app's bundled runtime when DSH comes from the desktop app (no local Node 24 needed), or this server's node for npm/npx installs;
27
30
  - Team mode also requires DSH `>= 0.1.7-0` and a profile that actually mounts both team packages.
28
31
 
32
+ ## Where DSH comes from (auto-detected, no configuration)
33
+
34
+ The server finds DSH on its own, in this order — **no need to set `DSH_BIN`**:
35
+
36
+ 1. **DeepSeek Harness desktop app**: on Windows the install directory comes from the uninstall
37
+ registry (HKCU/HKLM); default install locations are checked on every platform. The CLI lives
38
+ inside `resources/app.asar`, so the server runs it with the app's bundled Electron in Node mode
39
+ (`ELECTRON_RUN_AS_NODE=1`) — the same DSH and the same runtime the GUI uses;
40
+ 2. **npm global install** (`npm i -g @deepseek-ai/dsh`);
41
+ 3. **npx cache** (can vanish at any time; lowest priority when otherwise equal).
42
+
43
+ When several exist: candidates whose runtime satisfies `engines.node` first → higher version →
44
+ desktop > global > npx. `DSH_BIN` (may point inside an `.asar`) and `DSH_RUNTIME` (the executable
45
+ that launches DSH) are explicit overrides only.
46
+
29
47
  ## How to check
30
48
 
31
49
  ```bash
32
50
  dsh-any-agent --version # this server
33
- node <DSH_BIN> --version # DSH version (set DSH_BIN to pin it)
34
51
  dsh-any-agent team status # team switch and profile
35
52
  ```
36
53
 
37
54
  In an MCP client, call `dsh_doctor` and look at:
38
55
 
39
- - `dsh_bin`: DSH entry point and version; **warn** when it resolves from an npx cache (`_npx/<hash>/`), which disappears on cache cleanup — install DSH globally or pin `DSH_BIN`;
40
- - `dsh_version`: the version graded against the table above — `verified` is ok; `partial` / `unsupported` / `unverified` (e.g. 0.2.x) / unreadable are **warn**;
56
+ - `dsh_bin`: DSH entry point, version, source (desktop / npm global / npx cache / DSH_BIN) and runtime; **warn** when it resolves from an npx cache (`_npx/<hash>/`), which disappears on cache cleanup;
57
+ - `dsh_runtime`: whether the launching runtime's Node satisfies DSH's `engines.node`; **warn** if not;
58
+ - `dsh_desktop`: the desktop app's version; **warn** when it differs from the DSH this server uses (both share the `~/.dsh` session store);
59
+ - `dsh_candidates`: every DSH found and which one was picked;
60
+ - `dsh_version`: the version graded against the table above — `verified` is ok; `partial` / `unsupported` / `unverified` (e.g. 0.2.3+) / unreadable are **warn**;
41
61
  - `profile`: exists, drift, route source;
42
62
  - `team`: `on` / `off` / `requested but unavailable` (version or profile lacks the team package);
43
63
  - `config`: effective permission, idle reaping, shared process, and so on.
@@ -49,4 +69,5 @@ In an MCP client, call `dsh_doctor` and look at:
49
69
  3. If team mode will not turn on, check `dsh-any-agent team status` and `dsh_doctor`'s
50
70
  `team` check, then verify `~/.dsh/profiles/dsh-team/cordis.patch.yml` contains
51
71
  `@deepseek-ai/dsh-experimental-agent-team`;
52
- 4. To pin a DSH version, set `DSH_BIN` explicitly instead of letting the npx cache pick one.
72
+ 4. By default the server follows the desktop app: after the app updates, the next DSH launch picks
73
+ up the new version (discovery is cached for 60 s). Set `DSH_BIN` only to pin a specific version.
@@ -4,7 +4,8 @@
4
4
 
5
5
  | Variable | Default | Purpose |
6
6
  |---|---|---|
7
- | `DSH_BIN` | auto-detected | Path to `@deepseek-ai/dsh/lib/bin.js` |
7
+ | `DSH_BIN` | auto-detected (desktop app → npm global → npx cache) | Explicit path to `@deepseek-ai/dsh/lib/bin.js` (may point inside the desktop app's `app.asar`) |
8
+ | `DSH_RUNTIME` | auto | Explicit executable that launches DSH (e.g. a Node 24). Used with `DSH_BIN`, or on its own for auto-discovered npm global / npx installs (their `engines.node` is re-checked against it); the desktop app always keeps its bundled runtime (only it can read the asar). Unset: the desktop app's bundled runtime, otherwise this server's node |
8
9
  | `DSH_HOME` | `~/.dsh` | DSH home shared with the GUI (credentials, sessions, workspace registry). Point it elsewhere only for isolation/multi-account |
9
10
  | `DSH_MCP_PROFILE` | `dsh-mcp` (`dsh-team` when team is on) | DSH profile to drive; an explicit value wins |
10
11
  | `DSH_MCP_TEAM` | `0` (off) | Team mode, opt-in. `dsh-any-agent team on` writes the settings file; this env var overrides it. When on, uses the `dsh-team` profile and injects a "form a team when needed" line before `dsh_send`; per call use `dsh_send(team=false)` |
@@ -29,6 +30,8 @@
29
30
  | `DSH_MCP_SENTINEL_INCLUDE_REASONING` | unset | `1` writes `result.thinking` into the sentinel (**breaks the never-on-disk guarantee**); answer/error/title/tool inputs are still credential-redacted |
30
31
  | `DSH_MCP_REVIEW` | `advisory` | Caller-review policy. `advisory` (default) attaches a review ticket (checklist + evidence + `dsh_review` entry) to every result and sentinel **without blocking later sends**; `off` disables automatic tickets. Per-conversation/per-send overrides use the `review` argument |
31
32
  | `DSH_MCP_REVIEWS_DIR` | `<state file dir>/reviews` | Where review verdicts are written (`<this>/<conversation_id>/<run_id>.review.json`). Must be writable, or startup fails loudly |
33
+ | `DSH_MCP_LIVE` | `detail` | Live event stream level (the data behind `watch` / `dashboard`). `detail` adds redacted prompt / command / path previews; `meta` keeps tool names, status, timing and approval kinds only; `off` writes nothing. Reasoning is never written at any level |
34
+ | `DSH_MCP_LIVE_DIR` | `<state file dir>/live` | Live event stream directory (`<this>/<conversation_id>/events.jsonl`, rolled to `.1` at 5 MB). Write failures are skipped silently and never affect a run; pruned by `DSH_MCP_SENTINEL_TTL_MS` |
32
35
  | `DSH_MCP_PERMISSION` | `workspace-write` | Default tier for `dsh_start`. `danger-full-access` additionally requires `DSH_MCP_ALLOW_FULL_ACCESS=1` |
33
36
  | `DSH_MCP_ALLOW_FULL_ACCESS` | `0` | Whether `permission=danger-full-access` is allowed. Denied by default: it hands the caller whole-machine write access |
34
37
  | `DSH_MCP_REASONING_EFFORT` | `max` | Default reasoning effort |
@@ -13,6 +13,10 @@ npm version patch --no-git-tag-version
13
13
  node test/run.mjs
14
14
  node test/package-check.mjs
15
15
  npm run check:release # version / CHANGELOG / doc links / package contents agree
16
+ npm run docs:counts:check # check counts in tests.md and the CI comment = the report node test/run.mjs just wrote
17
+ # A mismatch means tests changed without the numbers being refreshed:
18
+ # node test/run.mjs --all --report .state/test-report.json && npm run docs:counts
19
+ # (model-suite numbers only move with a report made using --all; see "After changing tests" in docs/en/tests.md)
16
20
 
17
21
  # 3. Verify the tarball itself: builds, installs, starts.
18
22
  npm pack --dry-run
@@ -64,5 +64,6 @@ Silent by default — **not one byte is written**.
64
64
  - **The async completion sentinel persists the answer, usage and tool inputs** (it must, so a caller can collect offline) but **strips `result.thinking`** and always reports `thinking_omitted` (true when reasoning was removed, false when there was none). Tool inputs are passed through the same credential redaction as approvals, but the sentinel still contains commands and file contents — treat it as sensitive. A test asserts that even when `reasoning=full` is requested, no reasoning text appears in the sentinel — while `dsh_get(run_id)` still returns it from memory. Set `DSH_MCP_SENTINEL_INCLUDE_REASONING=1` to break this on purpose.
65
65
  - **Review records are the second intentional on-disk artifact.** `<state dir>/reviews/<conversation_id>/<run_id>.review.json` holds the verdict, the reviewer's `notes`/`findings`, the checklist and an evidence summary. `notes`/`findings`, the task preview, the evidence **and each checklist item** pass through the same credential redaction (the checklist can interpolate `result.error`); reasoning is never included. Treat review records as sensitive too (they can quote code or paths).
66
66
  - Sentinels **and review records** are pruned after 7 days by default (`DSH_MCP_SENTINEL_TTL_MS`; `0` keeps them forever).
67
+ - The live event stream (`<state dir>/live/`, read by `watch` / `dashboard`) carries **redacted** command, path and prompt previews by default — no more than the sentinels already hold — never reasoning, and is pruned on the same 7-day TTL. Set `DSH_MCP_LIVE=meta` to drop the previews or `off` to write nothing. `dashboard` binds `127.0.0.1` only, requires a one-time token, checks the Host header, and is read-only.
67
68
 
68
69
  > ⚠️ **DSH writes its own session log under `~/.dsh/sessions` and that log contains the full reasoning text** (even with `reasoning=hide`). It is what makes resume possible and is outside this server's control. The absolute "reasoning never reaches disk" claim applies to *this server's* artifacts only.