dsh-any-agent 0.4.0 → 0.5.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 (46) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/DESIGN.md +9 -0
  3. package/README.md +49 -676
  4. package/README.zh.md +45 -690
  5. package/USAGE-NOTES.en.md +4 -3
  6. package/USAGE-NOTES.md +4 -3
  7. package/bin/dsh-any-agent.mjs +66 -2
  8. package/demo/mcp-client.mjs +130 -0
  9. package/demo/run.mjs +326 -0
  10. package/demo/transcript.json +47 -0
  11. package/docs/brand/README.md +58 -0
  12. package/docs/brand/header.jpg +0 -0
  13. package/docs/en/architecture.md +43 -0
  14. package/docs/en/compatibility.md +51 -0
  15. package/docs/en/demo.md +90 -0
  16. package/docs/en/install.md +257 -0
  17. package/docs/en/limits.md +27 -0
  18. package/docs/en/locking.md +24 -0
  19. package/docs/en/reference.md +42 -0
  20. package/docs/en/release.md +33 -0
  21. package/docs/en/security.md +66 -0
  22. package/docs/en/team.md +74 -0
  23. package/docs/en/tests.md +97 -0
  24. package/docs/en/usage.md +78 -0
  25. package/docs/zh/architecture.md +43 -0
  26. package/docs/zh/compatibility.md +49 -0
  27. package/docs/zh/demo.md +90 -0
  28. package/docs/zh/install.md +274 -0
  29. package/docs/zh/limits.md +30 -0
  30. package/docs/zh/locking.md +24 -0
  31. package/docs/zh/reference.md +42 -0
  32. package/docs/zh/release.md +38 -0
  33. package/docs/zh/security.md +66 -0
  34. package/docs/zh/team.md +71 -0
  35. package/docs/zh/tests.md +96 -0
  36. package/docs/zh/usage.md +76 -0
  37. package/package.json +5 -1
  38. package/scripts/check-release.mjs +73 -0
  39. package/src/config.mjs +90 -23
  40. package/src/host.mjs +1 -1
  41. package/src/hub.mjs +423 -69
  42. package/src/ids.mjs +21 -0
  43. package/src/mcp.mjs +15 -7
  44. package/src/redact.mjs +34 -0
  45. package/src/review.mjs +199 -0
  46. package/src/tools.mjs +184 -12
package/CHANGELOG.md CHANGED
@@ -7,6 +7,86 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.1] — 2026-11-04
11
+
12
+ ### Security
13
+
14
+ - **Path-safe ids before any state-file join.** `conversation_id` and `run_id`
15
+ are now validated as single path segments before building sentinel, review,
16
+ metadata or tombstone paths. Values containing `/`, `\`, `:`, `..` or other
17
+ separators are rejected instead of being passed to `path.join`; this closes
18
+ a traversal in `dsh_get`'s sentinel read-back and in `dsh_review`'s
19
+ `<state>/reviews/<conv>/<run>.review.json` write.
20
+ - **Filesystem-root `cwd` is rejected for writable tiers.** Choosing `/` or
21
+ `C:\` with `workspace-write` made the sandbox root the whole disk; symlinked
22
+ paths are resolved before the home/root guard, so a symlink to the root or
23
+ home cannot bypass it.
24
+ - **`title_hint` is capped at 200 characters** (schema + runtime). It is
25
+ persisted in the registry and sidecar on every turn; an unbounded value was
26
+ a stable disk/memory amplifier.
27
+ - **`dsh_doctor` checks `reviews/` writability**, alongside `runs/` and
28
+ `locks/`.
29
+ - **MCP tool dispatch only accepts own handler properties**, so names such as
30
+ `constructor`, `toString` or `__proto__` are reported as unknown tools
31
+ instead of reaching the prototype chain.
32
+
33
+ ### Features
34
+
35
+ - **Caller review (`dsh_review`).** Every completed turn now carries a
36
+ `review` ticket in its result, `structuredContent`, `dsh_get` output and
37
+ async sentinel: a checklist derived from the turn (workspace changed? normal
38
+ stop? which tools ran?), the evidence needed to check it, and a `dsh_review`
39
+ entry. The calling agent can record `pass` / `partial` / `fail` with notes
40
+ and findings; the verdict is written atomically to
41
+ `<state dir>/reviews/<conversation_id>/<run_id>.review.json` and written back
42
+ into the in-memory run and the sentinel (so an async caller no longer sees a
43
+ stale `pending`). `fail`/`partial` require a reason; running/unknown runs are
44
+ refused. Default `advisory`: the ticket is loud but **does not block** the
45
+ next `dsh_send`, and a failed review is not fed back to DSH automatically.
46
+ Set `DSH_MCP_REVIEW=off` (or pass `review: "off"`) to disable tickets.
47
+ Review records share the sentinel's 7-day TTL and credential redaction,
48
+ including checklist items interpolated from `result.error` (Codex review
49
+ finding on #2).
50
+
51
+ ## [0.5.0] — 2026-11-03
52
+
53
+ ### Process model
54
+
55
+ - **The shared ACP process is now the default.** Sessions with the same `(cwd, permission)`
56
+ land on one DSH process: a second session costs ≈300 ms instead of a ≈4 s cold start, and the
57
+ 300–480 MB is paid once per group instead of once per conversation.
58
+ `DSH_MCP_SHARED_PROCESS=0` restores one process per conversation (worth it when you need hard
59
+ isolation). Different workspaces or permission tiers still get separate processes —
60
+ `dsh-base` keeps the sandbox root and the approval policy process-wide.
61
+ - **`dsh_takeover` no longer kills a shared process blind.** When the PID it is about to kill
62
+ still carries other sessions, it refuses unless `force=true` and reports them as
63
+ `shared_sessions`; with `force` it says how many went down with it.
64
+ - **`dsh_doctor` de-duplicates RSS by PID** and reports the shared host count, so "live processes"
65
+ and "RSS total" stop multiplying by the number of sessions riding one process.
66
+ - Stale comments corrected: `DSH_MCP_IDLE_TTL_MS=0` is no longer the default (10 minutes since 0.4.0).
67
+
68
+ ### Team mode and defaults
69
+
70
+ - **Agent Teams is now opt-in.** `DSH_MCP_TEAM` defaults to off. Enable with
71
+ `dsh-any-agent team on` (writes `<DSH_HOME>/.dsh-any-agent/settings.json`),
72
+ disable with `team off`, inspect with `team status`; no client config editing.
73
+ When on, the profile switches to `dsh-team`, the legacy subagent tools are
74
+ disabled, and `dsh_send` adds the "form a team when needed" line.
75
+ - **The default session permission is now `workspace-write`** (was
76
+ `danger-full-access`). `permission=danger-full-access` additionally requires
77
+ `DSH_MCP_ALLOW_FULL_ACCESS=1` on the machine, so a bare `dsh_start` can no
78
+ longer hand the caller whole-machine write access.
79
+ - `dsh_doctor` has a dedicated `team` check: on / off / requested-but-unavailable.
80
+
81
+ ### Demo
82
+
83
+ - **`npm run demo`** — a five-minute, end-to-end case: create a conversation, dispatch a task with
84
+ `wait=false`, interject twice while it runs, collect the sentinel, then verify the artifacts on
85
+ disk (including `node --test`) instead of trusting the model's own summary. It runs against an
86
+ isolated registry / sentinel / lock directory, so it never touches your daily
87
+ `~/.dsh/.dsh-any-agent`. `--replay` walks a recorded transcript with no DSH and no tokens;
88
+ `--keep` keeps the workspace. Ships as `demo/` in the npm package.
89
+
10
90
  ## [0.4.0] — 2026-11-03
11
91
 
12
92
  ### Memory
package/DESIGN.md CHANGED
@@ -238,6 +238,7 @@ Profile = `~/.dsh/profiles/<name>/`,由 `package.json` 的 `dsh.profile.bundle
238
238
  | B8 | 上下文注入 | `contentBlocks` 支持文本与图片;支持 DSH 的 `@路径` 引用语法把文件带进会话 |
239
239
  | B9 | 异步完成哨兵 | `wait=false` 的 run 终止(done/error/cancelled)时,往 `<RUNS_DIR>/<conversation_id>/<run_id>.json` 原子写出含 `status`+`result` 的文件;调用方(Claude/Codex)用后台任务 `until [ -f ]` 等它出现即被唤醒,无需轮询、也不占轮次 |
240
240
  | B10 | 写锁抢占 | 写锁是跨进程内核信号量(持有者活着不过期、**无夺锁 API**),所以"抢"= 杀掉持有者。本服务登记自己拉起的子进程(`<状态目录>/locks/<会话id>.json`:pid + token + 心跳),据此把持有者分成四类,只对**判定为本服务残留的孤儿**自动动手;无法识别的一律不杀(见下) |
241
+ | B11 | 调用方审核 | 每个完成的回合在 `result.review` 带一张审核单(核验清单 + 证据摘要 + `dsh_review` 入口);调用方独立核验后提交 `pass`/`partial`/`fail`,裁决落盘 `<状态目录>/reviews/<会话id>/<run_id>.review.json`,并回写内存 run 与哨兵。默认 advisory:**不阻塞**后续派活,也不自动回灌给 DSH |
241
242
 
242
243
  **B9 设计要点(并发正确性)**:本服务在 MCP 协议上只应答请求、**不主动推通知**,"跑完通知"由哨兵文件 + 调用方后台等待任务共同实现。
243
244
  - **按会话分目录**:`run_id` 序号是每会话独立计数的,不同会话可能同毫秒生成同名 `run_id`;`<conversation_id>/` 前缀使其各写各的、不串台。会话内 `dsh_send` 忙时直接拒绝、序号单调,故会话内唯一。
@@ -249,6 +250,14 @@ Profile = `~/.dsh/profiles/<name>/`,由 `package.json` 的 `dsh.profile.bundle
249
250
  - **给调用方的便利**:返回值同时给出 `sentinel_file_posix`(`D:\a\b` → `/d/a/b`)。手工转换 Windows 路径是等待任务白等到超时最常见的原因。
250
251
  - **落点**:`tools.dsh_send` 的 `wait=false` 分支在 `.then`/`.catch` 各调一次 `hub.writeRunSentinel()`(覆盖所有终止态);`createRun` 起跑前 `clearRunSentinel()` 防御历史残留。
251
252
 
253
+ **B11 设计要点(调用方审核)**
254
+ - **为什么放进返回载荷**:返回值是"另一个 agent 的自述"。调用方最常见的失败模式是直接转述"已完成/已修好"。审核要求必须和结果出现在同一条消息里(`content.text`、`structuredContent`、哨兵 `result.review`),才可能改变调用方的下一步动作;单独一个"记得审核"的约定会被忽略。
255
+ - **审核单形状**:`{review_id, mode, status, run_id, prompt_preview, checklist, evidence, submit:{tool:"dsh_review", arguments}, instructions}`。清单随回合形状变化(有没有改工作区、有没有错误、用了哪些工具);`evidence` 只放核对所需的最小事实(stop_reason、error、changed_files、工具名)。
256
+ - **默认 advisory,不阻塞**:这是刻意的产品选择。强制门禁会打断"审核失败 → 派修复回合"的自然流程,也会破坏既有调用方;advisory 只把审核请求显性化。`review=off` 可按会话/单次派活关闭;即使 off,`dsh_review` 仍可对旧 run 显式补审核。
257
+ - **裁决落盘与回写**:`reviews/<会话id>/<run_id>.review.json` 原子写入(tmp + rename,唯一 tmp 名),含裁决、理由/findings、清单与证据。`submitReview` 依次从内存 run → 哨兵 → 已有审核记录取事实,所以服务重启、run 被挤出内存后仍可审核。提交后 **原子重写哨兵里的 `result.review`**,避免异步调用方下次读文件看到与事实相反的 `pending`。
258
+ - **隐私**:审核单不含思考正文;理由、findings、任务预览、证据**与每个 checklist 项**在落盘前都走 `redact.sanitizeApprovalContext`(与审批上下文同一套规则)——checklist 会拼接 `result.error`,漏掉它就会成为第二个明文泄漏点。审核记录与哨兵共用 `DSH_MCP_SENTINEL_TTL_MS` 的 7 天清理。
259
+ - **失败语义**:fail/partial 必须有 notes 或 findings;对 `running`/`queued` 的 run 提交会被拒绝;写盘失败直接抛错(审核本身就是这次操作,静默失败比报错更糟)。
260
+
252
261
  **B10 设计要点(写锁与抢占)**
253
262
  - **为什么需要**:DSH 的 `SessionWriteLease` 是 Windows 命名内核信号量,**持有者活着就永不过期**,且**没有 API 能从活进程手里夺走** —— 唯一手段是杀掉持有者进程。既然要杀,就必须先判定那是谁。
254
263
  - **登记**:一个会话一个文件 `<状态目录>/locks/<会话id>.json`,内容 `{mcp_pid, mcp_token, child_pid, cwd, spawned_at, heartbeat_at}`。`mcp_token` 防 PID 复用;`heartbeat_at` 由 reaper 周期刷新。