dsh-tower 1.2.0 → 2.0.0

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 (61) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/CONTRIBUTING.md +36 -0
  3. package/README.en.md +60 -23
  4. package/README.md +54 -21
  5. package/SECURITY.md +20 -0
  6. package/cordis.patch.yml +32 -0
  7. package/docs/README.md +17 -0
  8. package/docs/architecture/overview.md +70 -0
  9. package/docs/examples/first-mission.md +58 -0
  10. package/docs/faq.md +74 -0
  11. package/docs/guide/hot-reload.md +189 -0
  12. package/docs/guide/install-upgrade-migration.md +59 -0
  13. package/docs/guide/quickstart.md +97 -0
  14. package/docs/operations/runbook.md +80 -0
  15. package/docs/operations/security.md +62 -0
  16. package/docs/reference/blockers.md +58 -0
  17. package/docs/reference/config.md +114 -0
  18. package/docs/reference/doctor.md +31 -0
  19. package/docs/reference/errors.md +50 -0
  20. package/docs/reference/events.md +45 -0
  21. package/docs/reference/operations.md +52 -0
  22. package/docs/reference/testing.md +61 -0
  23. package/docs/reference/tools.md +75 -0
  24. package/docs/zh/README.md +58 -0
  25. package/docs/zh/faq.md +72 -0
  26. package/docs/zh/guide/hot-reload.md +178 -0
  27. package/docs/zh/guide/install-upgrade-migration.md +57 -0
  28. package/docs/zh/guide/quickstart.md +96 -0
  29. package/docs/zh/operations/security.md +42 -0
  30. package/docs/zh/reference/blockers.md +62 -0
  31. package/docs/zh/reference/config.md +113 -0
  32. package/docs/zh/reference/doctor.md +30 -0
  33. package/docs/zh/reference/errors.md +52 -0
  34. package/docs/zh/reference/events.md +46 -0
  35. package/docs/zh/reference/operations.md +48 -0
  36. package/docs/zh/reference/testing.md +60 -0
  37. package/docs/zh/reference/tools.md +75 -0
  38. package/index.js +293 -49
  39. package/lib/batch.js +578 -0
  40. package/lib/config.js +381 -0
  41. package/lib/doctor.js +165 -0
  42. package/lib/errors.js +111 -0
  43. package/lib/hmr.js +196 -0
  44. package/lib/leases.js +317 -0
  45. package/lib/legacy.js +413 -0
  46. package/lib/modeStore.js +47 -16
  47. package/lib/store.js +24 -1
  48. package/lib/tests.js +628 -0
  49. package/lib/tools.js +1594 -160
  50. package/lib/tower.js +1025 -101
  51. package/lib/workspaceLock.js +117 -0
  52. package/lib/worktreeOwnership.js +166 -0
  53. package/package.json +21 -4
  54. package/reviewer.js +40 -0
  55. package/scripts/check-docs.mjs +135 -0
  56. package/scripts/enable-dev-hmr.mjs +347 -0
  57. package/skills/tower-recovery.md +40 -0
  58. package/skills/tower-review.md +16 -0
  59. package/skills/tower-worker-protocol.md +15 -0
  60. package/skills.js +63 -0
  61. package/worker.js +7 -4
@@ -0,0 +1,178 @@
1
+ # 热重载(开发)
2
+
3
+ [English](../../guide/hot-reload.md) | **简体中文**
4
+
5
+ Tower 是 Host 插件:它的工具与提示段落在 DSH 启动时加载。DSH 已内置 `@deepseek-ai/dsh-hmr` 服务,
6
+ 它**在原位对账 profile**——重新读取 profile 的 patch 层,替换文件已变更的模块(包括 `dsh-tower`
7
+ 检出这样的 linked 包),重启受影响的 Loader 条目,并报告哪些情况需要完整重启。Tower 在开发中使用
8
+ 该服务,因此 Host 侧改动无需重启 DSH 即可生效。
9
+
10
+ 本页讲的是开发循环,使用 Tower 本身并不需要它。
11
+
12
+ ## HMR 做什么、不做什么
13
+
14
+ `dsh --profile <name> --dump-config` 会打印组合后的 hmr 行:
15
+
16
+ ```yaml
17
+ - id: hmr
18
+ name: '@deepseek-ai/dsh-hmr'
19
+ disabled: !!js '!ctx.get(''profileContext'')'
20
+ config:
21
+ root: []
22
+ ```
23
+
24
+ - `root: []` 是随包默认值:**不监视任何源码文件**。HMR 已挂载,但文件监视处于空闲状态,因此编辑
25
+ 检出后运行中的 DSH 不会自动重载。
26
+ - 仍然可以显式请求对账(`/tower reload`、`tower_reload` 或 `plugin_manager`);即使 `root: []`,
27
+ 它也会比较已加载模块与磁盘,并替换发生变化的模块。监视只是让保存后自动触发这次对账。
28
+ - HMR 只为 profile 启动挂载(没有 `profileContext` 时 `disabled`)。没有 profile 启动的进程会报告
29
+ reload 不可用(`dev.hotReload` 开启时 `tower_reload` 仍注册,因此返回 `reload-unavailable`,
30
+ 而不是消失)。
31
+ - profile patch 层在 DSH 启动时加载。编辑它——实际就是 hmr 的 `root` 列表——需要**重启一次 DSH**
32
+ 才生效。运行中的进程不会重新读取 HMR 行自身的配置。
33
+
34
+ ## 为检出开启监视
35
+
36
+ 在本仓库的检出目录中执行:
37
+
38
+ ```powershell
39
+ npm run dev:hmr
40
+ # equivalent to:
41
+ node scripts/enable-dev-hmr.mjs
42
+ ```
43
+
44
+ 脚本会向 profile 的用户 patch 层
45
+ `<DSH_HOME>/profiles/<profile>/cordis.patch.yml` 追加一个带标记的块(`DSH_HOME` 默认 `~/.dsh`;
46
+ Windows 上通常是 `C:\Users\<you>\.dsh`):
47
+
48
+ ```yaml
49
+ # dsh-tower:dev-hmr
50
+ - id: hmr
51
+ config:
52
+ root:
53
+ - 'D:/work/dsh-tower'
54
+ ```
55
+
56
+ 选项:
57
+
58
+ | 参数 | 含义 |
59
+ |---|---|
60
+ | `--profile <name>` | 要编辑的 profile。默认 `desktop`(桌面应用启动的 profile)。 |
61
+ | `--root <dir>` | 要监视的仓库目录。默认:当前工作目录的规范路径。 |
62
+ | `--dry-run` | 打印 patch 文件的 diff 后退出,不写入。 |
63
+ | `--remove` | 再次删除该标记块。 |
64
+
65
+ 说明:
66
+
67
+ - 脚本是幂等的:重复运行会替换标记块,且不触碰 patch 文件的其余行。请在仓库根目录运行,或传入
68
+ `--root D:\work\dsh-tower`。
69
+ - 写入的值是 root 的 `realpath`,使用正斜杠。**HMR 的 `root` 接受绝对路径**;相对路径会相对
70
+ profile 目录解析,很容易出错,因此脚本总是写入绝对路径。
71
+ - profile 必须已存在。脚本不会创建 profile,也不会在 `<DSH_HOME>/profiles/<profile>/` 之外创建目录。
72
+ - 选择桌面应用实际启动的 profile(默认 `desktop`)。desktop profile 由 Electron 应用管理,请退出并
73
+ 重新启动 DSH 来完成重启。
74
+
75
+ 写入该块后**重启一次 DSH**。重启之后:
76
+
77
+ - HMR 会监视检出(`node_modules`、点目录、`cache` 与 `data` 被随包 `ignored` 列表排除),并在 DSH
78
+ 保持打开时于编辑后对账 profile。
79
+ - `/tower reload`(以及注册后的 `tower_reload` 工具)可以按需对账,即使文件不在监视范围内。
80
+
81
+ 停止监视:运行 `npm run dev:hmr -- --remove`,再重启一次 DSH。
82
+
83
+ ## 编辑循环
84
+
85
+ 1. 编辑 `lib/*.js`、`index.js`、`worker.js`、`reviewer.js` 或 `skills.js`。
86
+ 2. 保存文件。监视器会去抖(DSH 默认 100 ms)并对账 profile;变更的模块被替换,受影响的 Loader
87
+ 条目重启。
88
+ 3. 观察 DSH 日志中的 `reload plugin at <path>` / `restart required: …` 消息。
89
+ 4. 可选:调用 `/tower reload` 或 `tower_reload` 立即对账,而不必等监视器。
90
+
91
+ 编辑仓库的 bundle patch(`cordis.patch.yml`)会改变插件声明本身;此类改动后请重启 DSH,不要假设
92
+ 热替换会生效。
93
+
94
+ 工具背后的桥接遵循 `dev` 配置组:
95
+
96
+ | 配置键 | 含义 |
97
+ |---|---|
98
+ | `dev.hotReload` | 注册 `tower_reload`;关闭时 `/tower reload` 也被禁用(默认 `true`)。无 HMR 服务时工具仍注册,以便返回 `reload-unavailable`。 |
99
+ | `dev.reloadDelayMs` | 计划中的对账运行前的延迟,单位毫秒(默认 `50`),让工具/命令结果先送达模型,再替换插件。 |
100
+ | `dev.reloadRequiredIds` | profile 必须保持活跃的 Loader 条目 id(默认 `tower`、`tower-skills`、`preset-tower-worker`、`preset-tower-reviewer`)。即使某个 required 条目早已处于非活跃状态,也会被报告为重载失败。 |
101
+
102
+ 重载是**调度而非等待**:`/tower reload` 与 `tower_reload` 立即返回 `scheduled`、`delayMs`、
103
+ `requiredIds`(以及存在待处理错误时的 `lastError`),随后 HMR 在延迟后对账 profile。这是有意为之——
104
+ 工具结果必须先送达,Tower 插件才能替换自身。两个后果:
105
+
106
+ - 延迟窗口内的一批重载请求会合并为一次对账;
107
+ - 对账失败会记录为 `lastError`,而不会从工具调用返回,因此重载后请检查 `tower_mode.hotReload`
108
+ 与 DSH 日志。
109
+
110
+ ### 阅读重载结果
111
+
112
+ 计划中的对账结果会出现在:
113
+
114
+ - **DSH 日志** —— 被替换模块的 `reload plugin at <path>`,以及 HMR 自身的告警与
115
+ `restart required: <reason>` 消息。
116
+ - **`tower_mode.hotReload`** —— `{available, requiredIds, delayMs, scheduled, lastRunAt, lastError}`;
117
+ `lastError` 保留最近一次失败,并在下次成功重载后清除。
118
+
119
+ 对账本身还会返回 `warnings`、`pending`(仍运行旧代码的模块)以及可选的 `restartReason`。由 Tower
120
+ 触发重载时,这些不会返回给模型,因此把“新代码没有出现”视为需要重启 DSH 的信号,而不是反复重试。
121
+
122
+ 没有 HMR 时,`/tower reload` 会回答不可用消息,且 `tower_reload` 根本不会注册(两者都在会话启动时
123
+ 由 HMR 服务决定是否启用)。
124
+
125
+ ## plugin_manager 与 linked 包
126
+
127
+ `plugin_manager` 操作(`install_bundle`、`set_plugin`、升级)会立即作用于运行中的 profile——它们
128
+ 自己会对账 profile,因此安装或更新 bundle 不需要重启。开发时安装 `dsh-tower` 的推荐方式就是如此:
129
+ 把检出作为 bundle 添加(`dsh plugin --profile <profile> add <repo-root>` 或 `install_bundle`),
130
+ 然后让 HMR 与重载工具接住代码编辑。
131
+
132
+ 检出作为 linked 包目标时,检出内的文件变更会在下一次 `reconcileProfile` 或 `plugin_manager` 操作时
133
+ 被替换,**无需重启**——即使 `root: []` 意味着该变更没有被监视。真正需要重启的是:
134
+
135
+ - hmr 行自身的配置(`root`),因为该列表在服务激活时读取;
136
+ - 报告在 `pending` 下或带 `restartReason` 的模块。
137
+
138
+ ## 故障恢复
139
+
140
+ | 症状 | 处理 |
141
+ |---|---|
142
+ | `[tower:reload-unavailable]` / “Tower reload is unavailable” | 该进程没有 HMR 服务(未带 profile 启动、profile 中 HMR 被禁用,或服务在关闭过程中已 dispose)。重启 DSH。要检查 hmr 行,在非 desktop profile 上运行 `dsh --profile <name> --dump-config`(desktop profile 由 Electron 应用管理,拒绝 dump)。 |
143
+ | `hotReload.lastError` 中出现 `[tower:reload-failed]` 且伴随语法/导入错误 | 修复文件后重启 DSH。替换失败会停掉旧插件实例,旧代码不再运行;重载本身不会回滚。 |
144
+ | `HMR is disposed` | 服务随进程关闭。重启 DSH。 |
145
+ | `HMR transactions cannot be nested` | 在另一个 HMR 事务内部请求了显式重载;请从普通工具回合调用。 |
146
+ | `pending` 非空或设置了 `restartReason` | 先重启 DSH,再相信重载结果。 |
147
+ | 已保存但没有任何重载 | 确认文件位于配置的 `root`(绝对路径)下,未被 `ignored`(`node_modules`、点目录、`cache`、`data`)排除,且 profile patch 包含标记块。运行 `/tower reload` 手动对账。 |
148
+ | 重载持续报告某个 required 条目非活跃 | 该 Loader 条目被禁用或启动失败:检查 profile 的插件/bundle(移除 Tower preset bundle 后 `preset-tower-*` 条目会缺失),修复后重启。 |
149
+
150
+ 离线桥接测试属于常规测试套件:
151
+
152
+ ```powershell
153
+ node --test tests/hmr.test.mjs
154
+ ```
155
+
156
+ ## Client bundle(`ui-bundle`)不同
157
+
158
+ HMR 替换的是 **Host** 插件与提示/工具代码。可选的 `ui-bundle` 是 Client 小部件:它的 JavaScript 由
159
+ Web 页面下载并执行,而不是 Host 进程。Host 重载或 Client bundle 安装/更新后,已打开的页面可能仍保留
160
+ 旧小部件——在认为改动失败之前,先刷新 Web 页面(如果工具 schema 有变化,再打开新会话)。Tower UI
161
+ 小部件只是一个提示,与热重载没有依赖关系。
162
+
163
+ 要在隔离 profile 上对重载路径做实机检查,使用
164
+ [`tests/live/hmr.cjs`](../../../tests/live/hmr.cjs),并把 `DSH_TEST_URL` 指向一次性服务器 URL
165
+ (见 [tests/README.md](../../../tests/README.md));它不会触碰 desktop profile 或在线会话。
166
+
167
+ ## 安全
168
+
169
+ HMR 会在 **DSH Host 进程内**执行它在被监视 root 中找到的代码,使用你的操作系统用户权限,并在保存时
170
+ 重载。请像对待 shell 启动文件一样对待 profile patch 层:
171
+
172
+ - 只把 `root` 指向你信任的仓库(本检出),并保持列表小而绝对;
173
+ - 不要加入主目录、下载目录或共享/网络路径;
174
+ - 被 patch 的 profile 属于同用户可信配置;能写它的人就能在你的 DSH 进程中执行代码;
175
+ - 不再需要监视时,移除该块(`--remove`)并重启一次。
176
+
177
+ 另见:[安装、升级与迁移](install-upgrade-migration.md)、
178
+ [配置参考](../reference/config.md)、[错误码](../reference/errors.md)。
@@ -0,0 +1,57 @@
1
+ # 安装、升级与迁移
2
+
3
+ [English](../../guide/install-upgrade-migration.md) | **简体中文**
4
+
5
+ ## 环境要求
6
+
7
+ - DSH `0.2.1-rc.2` 或更新版本
8
+ - Node.js 22+
9
+ - Git(本地 worktree;不需要网络访问)
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ dsh plugin --profile web add dsh-tower
15
+ ```
16
+
17
+ 或在 DSH 内执行:`plugin_manager({ action: "install_bundle", target: "dsh-tower" })`。从本地 checkout 安装:
18
+
19
+ ```bash
20
+ dsh plugin --profile web add github:louifinn/dsh-tower
21
+ ```
22
+
23
+ Host 插件在 DSH 启动时加载:**安装或更新后需要重启 DSH**。
24
+
25
+ ## 升级
26
+
27
+ 1. 阅读 [CHANGELOG.md](../../../CHANGELOG.md) 了解行为变化。
28
+ 2. 更新依赖(`dsh plugin --profile web add dsh-tower@latest` 或你固定的版本)。
29
+ 3. 重启 DSH;用 `tower_mode` 检查 `allowAgentControl` 是否符合预期。
30
+ 4. 既有 mission state 保持兼容:1.2/1.3 的状态 schema 未变。
31
+
32
+ ## 迁移说明
33
+
34
+ - **1.x → 2.0(未发布)**:不需要迁移状态(`schemaVersion` 保持 1;新字段都是可选的)。需要留意的
35
+ 行为变化:Tower 模式激活期间,`mode.denyTools` 现在还会拒绝 `workflow`、`ralph`、`subagent`、
36
+ `subagent_fork`、`wait_agent` 与 `team_task_list`(可用显式列表覆盖);`spawn.maxLiveWorkers`
37
+ 默认为 4;`mode.workspaceLock` 默认关闭。worker 不能再直接完成/放弃/扩大 mission 的 scope——
38
+ 请使用 `tower_complete`。新工具:`tower_interrupt`,以及 HMR 可用时的 `tower_reload`。升级后重启
39
+ 一次 DSH;见[热重载](hot-reload.md)。
40
+ - **1.3 → 1.4**:不需要迁移状态。新增:仓库级配置(`<git-common-dir>/dsh-tower/config.json`)、
41
+ 稳定的 `[tower:<code>]` 工具错误、`tower_findings`,以及 `id`/`missionId` 与 snake/camel 输入
42
+ 别名。既有调用保持可用;重启 DSH 以加载新工具面(`tower_findings` 与更新的 output schema)。
43
+ - **1.2 → 1.3**:reviewer 从共享的 `tower-worker` preset 迁移到新的只读 `tower-reviewer` preset。
44
+ 重新安装/重启,确保两个 preset 行都存在。`tower_spawn role=reviewer` 现在要求 mission 为
45
+ `completed`。
46
+ - **1.1 → 1.2**:合并失败新增 `merge-hook-failed`、`cleanup` 与对账行为;不需要迁移状态。
47
+ - **1.0 → 1.1**:包名从 `@local/dsh-tower` 改为 `dsh-tower`。先移除旧 bundle
48
+ (`remove_bundle` 目标 `@local/dsh-tower`),再安装 `dsh-tower`。
49
+
50
+ ## 卸载
51
+
52
+ ```text
53
+ plugin_manager({ action: "remove_bundle", target: "dsh-tower" })
54
+ ```
55
+
56
+ 重启 DSH。`<git-common-dir>/dsh-tower/` 下的 mission state 与
57
+ `<主检出父目录>/.dsh-tower-worktrees/` 下的 worktree 会保留;确认没有活跃 mission 后再手工清理。
@@ -0,0 +1,96 @@
1
+ # 快速开始
2
+
3
+ [English](../../guide/quickstart.md) | **简体中文**
4
+
5
+ 前置要求:DSH `0.2.1-rc.2`+、Node.js 22+、Git。安装 `dsh-tower` 并重启 DSH(见
6
+ [安装](install-upgrade-migration.md))。
7
+
8
+ ## 1. 进入 Tower 模式
9
+
10
+ ```
11
+ /tower
12
+ ```
13
+
14
+ 会话会报告 `Tower mode ON`。此时你是控制塔;goal/todo/team 委派工具保留给普通模式。
15
+
16
+ ## 2. 初始化与计划
17
+
18
+ ```
19
+ tower_init repoRoot=D:\work\repo
20
+ tower_plan repoRoot=D:\work\repo missions=[{"title":"Feature A","scope":["src/a/**"],"tasks":["implement the feature","commit the change"],"kind":"build"}]
21
+ ```
22
+
23
+ `tower_plan` 返回 mission id(`M1`)。每个 mission 在 spawn 时获得独立分支和 worktree。
24
+ Tower 工具同时接受 `missionId` 与 `id` 两种写法(见[输入别名](../reference/tools.md#输入别名))。
25
+
26
+ ## 3. 运行 worker
27
+
28
+ ```
29
+ tower_spawn repoRoot=D:\work\repo missionId=M1 role=worker
30
+ ```
31
+
32
+ 调用立即返回子代理 id。worker 在 mission worktree 内工作、提交、把任务标记完成,并调用
33
+ `tower_complete`;它的最终报告会送回本会话。用 `tower_roster` 观察活动,不要循环轮询。
34
+
35
+ ## 4. 审查
36
+
37
+ ```
38
+ tower_spawn repoRoot=D:\work\repo missionId=M1 role=reviewer
39
+ ```
40
+
41
+ reviewer 使用只读的 `tower-reviewer` preset。它查看 `tower_diff` 并提交 `tower_review`。只有
42
+ `clean` + `merge` 的结论才允许合并。
43
+
44
+ ## 5. 门禁、合并与清理
45
+
46
+ ```
47
+ tower_preflight repoRoot=D:\work\repo missionId=M1
48
+ tower_merge repoRoot=D:\work\repo missionId=M1
49
+ tower_teardown repoRoot=D:\work\repo
50
+ ```
51
+
52
+ 审查前运行 `tower_preflight` 预期报告 `no-review`;干净审查之后应为 `ok:true`。存在 blocker 时
53
+ `tower_merge` 拒绝合并。`tower_teardown` 移除已 merged/abandoned 的 worktree 与分支。
54
+
55
+ ```
56
+ /tower off
57
+ ```
58
+
59
+ ## 如果失败
60
+
61
+ 阅读 blocker code 并查阅 [blocker 参考](../reference/blockers.md)。最常见的情况:`stale-base`
62
+ (执行 `tower_rebase`)、`no-review`(spawn reviewer)、`merge-hook-failed`(重试,或调大
63
+ `timeoutMs`)、`tip-moved`(审查后分支已变化,需要重新审查)。
64
+
65
+ 工具抛出的错误带 `[tower:<code>]` 前缀;见[错误码](../reference/errors.md)(例如
66
+ `preset-missing` 表示安装 bundle 后需要重启 DSH,`not-initialized` 表示先运行 `tower_init`)。
67
+
68
+ ## 可选:仓库级配置
69
+
70
+ 不修改 DSH profile,仓库也能调整 review、merge、worktree 与 spawn 策略。把 JSON 文件放到
71
+ `<git-common-dir>/dsh-tower/config.json`(普通检出通常是 `.git/dsh-tower/config.json`):
72
+
73
+ ```json
74
+ {
75
+ "review": { "maxRoundsPerReviewer": 3 },
76
+ "merge": { "timeoutMs": 1800000 },
77
+ "worktrees": { "root": "D:/tower-worktrees" }
78
+ }
79
+ ```
80
+
81
+ 仓库配置层覆盖 profile 配置;单次工具参数又覆盖两者。完整键表与优先级见
82
+ [配置参考](../reference/config.md)。
83
+
84
+ ## 可选:finding
85
+
86
+ scope 之外的观察用 `tower_finding` 记录。之后可以列出或关闭它们,而不必改代码:
87
+
88
+ ```
89
+ tower_findings repoRoot=D:\work\repo
90
+ tower_findings repoRoot=D:\work\repo id=F1 status=fixed note="fixed in M2"
91
+ ```
92
+
93
+ 状态为 `open → triaged → fixed` / `wontfix`,且 `fixed`/`wontfix` 可重新打开为 `open`。只有控制塔
94
+ 或 worker 可以修改 finding;reviewer 只能列出和读取。
95
+
96
+ 完整可复制示例:[examples/first-mission.md](../../examples/first-mission.md)(英文)。
@@ -0,0 +1,42 @@
1
+ # 安全模型
2
+
3
+ [English](../../operations/security.md) | **简体中文**
4
+
5
+ Tower 提供的是**协作层面的保证**,不是沙箱。需要理解哪些约束由插件强制执行、哪些依赖部署环境。
6
+
7
+ ## 由插件强制执行的约束
8
+
9
+ - **审查绑定。** `tower_review` 会记录它检查的分支 tip;tip 变化后门禁会拒绝(`tip-moved`),除非
10
+ 一次干净且无冲突的 rebase 给予了豁免。reviewer 在 spawn 之后修改分支会被拒绝
11
+ (`reviewer must not modify the mission branch`)。
12
+ - **角色强制。** 调用方会根据 roster 解析。worker 的 mission 更新仅限于 task/blocker/note;
13
+ `completed`、`abandoned`、扩大 scope 与清除 blocker 仅限控制塔,完成(连同未读消息/提交门禁)必须
14
+ 走 `tower_complete`。仅限控制塔的工具会以 `[tower:not-authorized]` 拒绝 roster 中的 worker。
15
+ 每个 mission 只能有一个活跃 worker;接管已停止的 worker 必须显式声明。
16
+ - **只读 reviewer preset。** `tower-reviewer` preset 从工具面移除 `write`、`edit`、`pwsh`、
17
+ `tower_mission` 与 `tower_complete`;reviewer 只能读、看 diff、发消息、记录 finding 和提交审查。
18
+ - **Scope 门禁。** mission 只能修改其声明 scope glob 匹配的文件;越界改动会阻止合并。
19
+ - **Worktree 隔离。** worker 在独立 worktree 与分支上工作;主检出只会被 merge/teardown 触及。
20
+ - **人类掌握模式。** `/tower` 归用户所有。开启 `mode.allowAgentControl` 后模型可以切换,但必须
21
+ 给出理由;随包 patch 默认开启——设为 `false` 可让模式只由人类控制。
22
+ - **不自动恢复 goal。** 进入模式会暂停活跃 goal;退出时不会恢复它。
23
+ - **建议性 workspace 锁。** `mode.workspaceLock` 防止两个 Tower 会话意外驱动同一个仓库;它是同用户
24
+ 之间的协调锁,不是用户间的认证机制。
25
+
26
+ ## 不由插件强制执行的约束(部署关注点)
27
+
28
+ - **沙箱。** Host 插件运行在文件系统沙箱之外;reviewer 的 shell 能力是在工具面层面移除的,但底层
29
+ 进程仍拥有 DSH 的 OS 权限。需要硬隔离的部署必须自行限制进程/用户。
30
+ - **reviewer 信任。** 审查是 reviewer agent/模型给出的背书;插件能把它绑定到提交,但无法证明
31
+ reviewer 真的推理正确。高价值分支建议使用独立模型/角色。
32
+ - **仓库钩子。** `git merge` 会运行目标仓库的钩子(`pre-merge-commit` 等)。Tower 无法让这些钩子
33
+ 变安全;在不受信任的仓库上运行 Tower 前请先审查钩子。
34
+ - **HMR 重载。** 开启 `dev.hotReload` 时,`/tower reload`/`tower_reload` 会让 DSH 从磁盘重新导入
35
+ 插件。只为你信任的检出配置 HMR 监视 root;重载会执行这些文件当前的代码。
36
+ - **提示注入。** reviewer 会读取不受信任的分支内容(diff/评论)。只读 preset 限制了影响面,但模型侧
37
+ 没有绝对防护。
38
+ - **基线分支保护。** Tower 会写入本地基线分支;服务端的 branch protection/push 规则不属于本插件。
39
+
40
+ ## 报告问题
41
+
42
+ 不要在公开 issue 中粘贴密钥、token 或会话日志。见 [SECURITY.md](../../../SECURITY.md)。
@@ -0,0 +1,62 @@
1
+ # blocker 与失败码
2
+
3
+ [English](../../reference/blockers.md) | **简体中文**
4
+
5
+ `tower_preflight` 返回 `{ok:false, blockers:[{code, detail}]}`。`tower_merge` 与 `tower_rebase`
6
+ 在操作失败时返回相同结构。code 稳定;detail 面向人。
7
+
8
+ ## 门禁 blocker(26 个)
9
+
10
+ | Code | 含义 / 下一步 |
11
+ |---|---|
12
+ | `mission-missing` | 未知 mission id。用 `tower_status` 查看。 |
13
+ | `mission-not-completed` | mission 不是 `completed`;只有 `completed`(等待审查)的 mission 才能合并。 |
14
+ | `tasks-open` | 仍有未完成任务;worker 必须先完成,或带理由 `task_drop`。 |
15
+ | `blockers-present` | mission 仍有 blocker;解决后清除。 |
16
+ | `no-check-evidence` | 启用了 `gates.requireChecks` 且完成报告没有 `checks`。把 completed mission 退回 `active`,带 checks 重新调用 `tower_complete`,然后重新审查。 |
17
+ | `branch-missing` | 还没有 worktree/分支:先 spawn worker。 |
18
+ | `stale-base` | 分支不包含基线 tip:执行 `tower_rebase`。 |
19
+ | `read-only-survey` | survey mission 修改了文件;survey mission 必须只读。 |
20
+ | `out-of-scope` | 改动文件超出 mission scope;有意为之就扩大 scope,否则回退。 |
21
+ | `base-dirty` | 主检出存在未提交改动,合并会覆盖它们。 |
22
+ | `deps-unmerged` | 依赖 mission 尚未 merged/abandoned。 |
23
+ | `no-review` | 分支上没有审查:spawn reviewer。 |
24
+ | `review-mission-mismatch` | 最新审查属于另一个 mission。 |
25
+ | `not-clean` | 最新审查是 `p1-Nitems`/`p2-Nitems`;需要再来一轮。 |
26
+ | `review-hold` | 最新审查建议 hold;必须由后续的干净审查取代。 |
27
+ | `review-not-approved` | 最新干净审查给出的是 `fix-then-merge` 或 `hold`;只有 `merge` 才算批准。 |
28
+ | `tip-moved` | 审查之后分支发生变化;重新审查(或使用干净 rebase 的免审豁免)。 |
29
+ | `worker-busy` | 该 mission 的 roster agent 正在运行;等待或让其退役。 |
30
+ | `worker-status-unknown` | 父目录不可用,无法证明 live roster agent 已停止;等目录恢复后重试,或先 adopt。 |
31
+ | `base-wrong-branch` | 主检出不在记录的基线分支上。 |
32
+ | `branch-owned-by-closed-missions` | mission 已 merged/abandoned;用新标题重新 plan。 |
33
+ | `worker-stale` | 该 mission 的 roster worker 超过 `spawn.staleMs` 没有目录活动;合并前 adopt(只有确实还在运行时才 interrupt)。 |
34
+ | `tests-missing` | 启用了 `gates.requireTests` 且没有针对被审查 tip 的通过测试回执;运行 `tower_test` 或 `tower_complete`。 |
35
+ | `tests-failed` | 最新测试发现回执失败,或不满足 minCount/required 检查。修复测试并重跑。 |
36
+ | `review-exemption-invalid` | mission 请求 `reviewPolicy:"none"`,但 `gates.allowReviewExemption` 关闭(或缺少理由)。 |
37
+ | `merge-intent-pending` | 崩溃/失败前记录了合并意图;先运行 `tower_merge` 对账,再进行其他门禁。 |
38
+
39
+ ## 合并失败(4 个)
40
+
41
+ | Code | 含义 / 下一步 |
42
+ |---|---|
43
+ | `merge-conflict` | 真实的 index 冲突;mission 变为 `blocked`。解决后把 mission 恢复为 `active`/`completed`,再重试。 |
44
+ | `merge-hook-failed` | 在产生提交之前钩子/进程失败;仓库已恢复,mission 保持 `completed`。重试;`timeout: true` 表示 git 进程超时,慢钩子请调大 `timeoutMs`。 |
45
+ | `merge-shape` | Git 产生了意外的合并父提交;检查仓库,不要强推。 |
46
+ | `batch-invalid` | batch id/成员/回执/检查引用无效,或 close 证据不完整。 |
47
+
48
+ 已经落地的合并不是 blocker:`tower_merge` 会记录它(`reconciled`、`alreadyLanded`),或从失败调用中恢复
49
+ (`recoveredFromFailure`、`warnings`)。
50
+
51
+ ## rebase 失败(3 个;`worker-busy` 与门禁共用)
52
+
53
+ | Code | 含义 / 下一步 |
54
+ |---|---|
55
+ | `worktree-dirty` | 提交或丢弃 worktree 改动后重试。 |
56
+ | `rebase-conflict` | 真实冲突;mission 变为 `blocked`。手工解决后将其恢复为 `active`,再重试。 |
57
+ | `worker-busy` | 该 mission 有 agent 正在运行;先等待或让其退役。 |
58
+
59
+ 分支已包含在基线中时,`tower_rebase` 返回 `alreadyMerged: true`;此时调用 `tower_merge` 记录这次
60
+ 已落地的合并。
61
+
62
+ 稳定的 blocker code 共 32 个(26 个门禁 + 4 个合并失败 + 2 个 rebase 专属;`worker-busy` 与门禁共用)。
@@ -0,0 +1,113 @@
1
+ # 配置参考
2
+
3
+ [English](../../reference/config.md) | **简体中文**
4
+
5
+ 每个键都是可选的;默认值即随包行为。未知顶层键会被拒绝。
6
+ `section` 与 `allowAgentControl` 是 `mode.promptSection` 与 `mode.allowAgentControl` 的兼容别名。
7
+ 兼容别名只用于补缺:同时设置两种写法时,规范的 `mode.*` 键优先。
8
+
9
+ ## 层级与优先级
10
+
11
+ 从低到高:
12
+
13
+ 1. 代码默认值(下表中的默认值),
14
+ 2. profile 配置(挂载 `dsh-tower` 的 Cordis patch 传给 `apply()` 的配置),
15
+ 3. 仓库配置 `<git-common-dir>/dsh-tower/config.json`(通常是 `.git/dsh-tower/config.json`),
16
+ 4. 单次工具参数,例如 `worktreeRoot` 与 `timeoutMs`。
17
+
18
+ 每次 Tower 工具打开仓库时都会读取仓库配置层,因此仓库可以在不修改 profile 的情况下调整 review、
19
+ merge、worktree 与 spawn 策略。它使用与 profile 配置相同的 JSON 形状(相同的分组与兼容别名),
20
+ 并按分组合并:仓库设置的键优先,仓库未设置的 profile 键保留。`tower_status` 会报告
21
+ `config.path` 与 `config.repoConfigPresent`。
22
+
23
+ ```json
24
+ {
25
+ "review": { "maxRoundsPerReviewer": 3 },
26
+ "merge": { "timeoutMs": 1800000 },
27
+ "worktrees": { "root": "D:/tower-worktrees" },
28
+ "spawn": { "reviewerPreset": "tower-reviewer", "maxDepth": 2 }
29
+ }
30
+ ```
31
+
32
+ `mode.*` 分组是会话级的,不是仓库级的:为兼容 schema,它可以在仓库文件中出现,但只有 profile
33
+ 配置能控制 Tower 模式提示与模型切换开关。文件格式错误、值不是对象、未知顶层键,或违反声明 schema
34
+ 的值(例如 `merge.timeoutMs` 小于 1000)会以稳定的 `config-invalid` / `config-unknown-key`
35
+ 工具错误码失败。
36
+
37
+ | 键 | 默认值 | 含义 |
38
+ |---|---|---|
39
+ | `section` | `''` | 兼容别名:替换整段 Tower 模式提示文案。空值保留内置提醒。 |
40
+ | `allowAgentControl` | `false`(随包 patch:`true`) | 兼容别名:注册 `tower_control`。 |
41
+ | `init.autoGitInit` | `false`(随包 patch:`true`) | 当 `repoRoot` 是绝对、已存在但不是仓库的目录时,`tower_init` 可以执行 `git init` 并创建一个空的初始提交。 |
42
+ | `init.snapshotDirty` | `false` | `tower_init` 可以把未提交的**已跟踪**改动快照为游离提交作为 mission 基线(不含未跟踪文件;该提交通过 `refs/tower/snapshot/*` 保持存活)。 |
43
+ | `mode.promptSection` | `''` | 替换用的提示文案(与 `section` 同时设置时优先)。 |
44
+ | `mode.sectionAppend` | `''` | 追加在提醒之后的额外文本(团队约定)。 |
45
+ | `mode.allowAgentControl` | `false` | 注册 `tower_control`;模型可带理由进入/退出。 |
46
+ | `mode.autoPauseGoal` | `true` | 进入模式时暂停活跃 goal;退出时不会自动恢复。 |
47
+ | `mode.requireResumeAuthorization` | `false` | 恢复因 `pauseCause:user` 暂停的 mission 时,必须提供 `authorization` 引用。 |
48
+ | `mode.workspaceLock` | `false`(随包 patch:`true`) | 每个仓库只允许一个 Tower 会话(`tower.lock`);其他会话得到 `workspace-busy`。 |
49
+ | `mode.workspaceLockScope` | `all` | `all` 锁定所有仓库工具;`mutations` 对只读工具跳过 workspace 锁。 |
50
+ | `mode.planModePolicy` | `clear` | 进入 Tower 模式会清除活跃的 plan mode(或 `refuse` 直到退出计划);前置步骤也会清除稍后激活的计划。 |
51
+ | `mode.workspaceStaleMs` | `300000` | 超过该时长的 workspace 锁可以被接管。 |
52
+ | `mode.heartbeatMs` | `10000` | workspace 锁的心跳间隔。 |
53
+ | `mode.denyTools` | goal/todo/team/workflow/subagent 工具(默认 12 个) | 模式激活期间被拒绝的工具。 |
54
+ | `mode.storePath` | `$DSH_HOME/tower-mode` | `modes.json` 所在目录。 |
55
+ | `mode.maxEntries` | `200` | 最多持久化的模式条目数(1–2000)。 |
56
+ | `review.maxRoundsPerReviewer` | `5` | 每个 reviewer 对每条分支的审查轮数上限。 |
57
+ | `merge.timeoutMs` | `900000`(15 分钟) | `tower_merge` 默认超时,包含钩子;单次调用的 `timeoutMs` 优先。 |
58
+ | `merge.maxBufferBytes` | `67108864`(64 MB) | 合并输出缓冲区。 |
59
+ | `worktrees.root` | `''` | 默认 worktree 父目录;空值推导为 `<主检出父目录>/.dsh-tower-worktrees/<hash>`。 |
60
+ | `spawn.workerPreset` | `tower-worker` | `role=worker` 使用的 preset。 |
61
+ | `spawn.reviewerPreset` | `tower-reviewer` | `role=reviewer` 使用的 preset。 |
62
+ | `spawn.maxDepth` | `1` | spawn 时请求的子代理深度。 |
63
+ | `spawn.provider` | `''` | 子代理 provider id;空值优先 `spawn`,否则取第一个 provider。 |
64
+ | `spawn.workerModel` | `{}` | worker 可选模型路由 `{provider, model, reasoningEffort, maxTokens}`。 |
65
+ | `spawn.reviewerModel` | `{}` | reviewer 可选模型路由;使用不同模型可获得更强的独立审查。 |
66
+ | `spawn.maxLiveWorkers` | `4` | 每个仓库同时存活的 worker 上限(空闲 `inactive`、failed/interrupted/missing/stale/retired 不计入);超过返回 `worker-limit`。 |
67
+ | `spawn.staleMs` | `15000` | 超过该时长没有目录活动的 roster 条目会被标记为 `stale` 并阻止合并(`worker-stale`);0 关闭。 |
68
+ | `spawn.maxWaitMs` | `90000` | `tower_spawn.waitMs` 的上限(与 Kimi 兼容的 90s 默认值)。 |
69
+ | `spawn.requireLease` | `''` | worker spawn 前必须持有的可选租约名。 |
70
+ | `send.maxPerMinute` | `120` | 每个仓库的 `tower_send` 每分钟速率限制(0 关闭);超出返回 `rate-limited`。 |
71
+ | `send.interruptDefault` | `false`(随包 patch:`true`) | `tower_send.interrupt` 的默认值(投递前先中断运行中的回合)。 |
72
+ | `tests.enabled` | `true` | worktree 的 `package.json` 配置了测试发现时运行它。 |
73
+ | `tests.admission` | `complete` | `spawn` 在启动 worker 前运行发现(fail-closed,不启动子代理);`complete` 在 `tower_complete` 时运行;`off` 只在显式 `tower_test` 时运行。 |
74
+ | `tests.timeoutMs` | `60000` | 每条发现命令的超时。 |
75
+ | `tests.maxBytes` | `16777216`(16 MiB) | 每条命令捕获的 stdout 上限。 |
76
+ | `tests.autoChecks` | `true` | 向完成报告附加自动 `tests` 检查。 |
77
+ | `tests.allowPackageDiscovery` | `true` | 允许仓库控制的 `tower.testDiscovery` 命令(会执行 worktree 代码;见安全页)。 |
78
+ | `gates.requireChecks` | `false` | 合并前要求至少一条 `tower_complete.checks` 证据(`no-check-evidence`)。 |
79
+ | `gates.requireTests` | `false` | 要求针对被审查 tip 的通过测试回执(`tests-missing`)。 |
80
+ | `gates.allowReviewExemption` | `false` | 允许用 `reviewPolicy:"none"` + 理由跳过审查 blocker;默认故意关闭。 |
81
+ | `docs.board` | `true` | 变更后写入 `MISSIONS.md`。 |
82
+ | `docs.boardLocation` | `common` | `common` = `<git-common-dir>/dsh-tower/MISSIONS.md`;`repo` = `<repoRoot>/MISSIONS.md`。 |
83
+ | `leases.ttlMs` | `900000` | 默认咨询式租约寿命;过期不会自动释放。 |
84
+ | `dev.hotReload` | `true` | 注册 `tower_reload`;关闭时 `/tower reload` 也被禁用。无 HMR 服务时调用返回 `reload-unavailable`。 |
85
+ | `dev.reloadDelayMs` | `50` | 计划中的 reload 延迟,先让工具结果送达。 |
86
+ | `dev.reloadRequiredIds` | 四个 Tower loader id | 作为 required 传给 `reconcileProfile` 的条目 id。 |
87
+
88
+ ## 示例
89
+
90
+ ```yaml
91
+ - id: tower
92
+ name: 'dsh-tower'
93
+ config:
94
+ mode:
95
+ allowAgentControl: true
96
+ sectionAppend: 'Team conventions: tests are mandatory.'
97
+ workspaceLock: true
98
+ review:
99
+ maxRoundsPerReviewer: 3
100
+ merge:
101
+ timeoutMs: 1800000
102
+ spawn:
103
+ reviewerModel:
104
+ provider: deepseek-account
105
+ model: deepseek-flash
106
+ dev:
107
+ hotReload: true
108
+ ```
109
+
110
+ ## 查看实际生效的 schema
111
+
112
+ 插件声明 `Config` 后(本版本已声明),用 `cordis_inspect_query` 调用
113
+ `Config.listConfigs({ name: "dsh-tower" })` 可返回投影后的 JSON Schema。
@@ -0,0 +1,30 @@
1
+ # Doctor
2
+
3
+ [English](../../reference/doctor.md) | **简体中文**
4
+
5
+ `tower_doctor({repoRoot})` 是只读的。它从不获取 state 锁、写状态或改动 Git。它返回:
6
+
7
+ ```json
8
+ {
9
+ "ok": true,
10
+ "checks": [
11
+ { "id": "git", "status": "pass", "detail": "git version 2.55.0" },
12
+ { "id": "state", "status": "pass", "detail": ".../state.json revision 7" }
13
+ ],
14
+ "environmentContract": {
15
+ "platform": "win32",
16
+ "gitVersion": "git version 2.55.0",
17
+ "repoRoot": "D:/repo",
18
+ "commonDir": "D:/repo/.git",
19
+ "stateDir": "D:/repo/.git/dsh-tower"
20
+ }
21
+ }
22
+ ```
23
+
24
+ 检查覆盖:git 可用性、仓库身份、state schema/加载、仓库配置是否存在、worker/reviewer preset 名称、
25
+ subagents 与 HMR 服务、workspace 锁持有者/是否过期、实时 roster、worktree 是否存在/归属/是否脏、
26
+ test-discovery 策略、旧版快照、batch 与租约。`warn` 是提示;`fail` 表示仓库在 spawn/merge 前需要
27
+ 处理。state 加载失败会作为失败的检查报告而不是抛出,因此 doctor 在损坏的仓库上仍然有用。
28
+
29
+ 环境契约仅供参考;它不是身份或授权证明。`tower_init` 仍要求控制塔角色,`tower_spawn` 仍会检查
30
+ 实时子代理目录。
@@ -0,0 +1,52 @@
1
+ # 工具错误码
2
+
3
+ [English](../../reference/errors.md) | **简体中文**
4
+
5
+ 工具失败是抛出的错误,不是 `{ok:false}` 结果(门禁结果是例外:见 [blockers.md](blockers.md))。
6
+ 每个抛出的错误都以稳定 code 作为消息前缀:
7
+
8
+ ```
9
+ [tower:mission-not-found] unknown mission "M9"
10
+ ```
11
+
12
+ 调用方可以匹配前缀,而不必匹配文案。下面的 code 保留给对应条件;没有更具体匹配的消息会报告为
13
+ `tool-internal`。
14
+
15
+ | Code | 含义 | 典型触发 |
16
+ |---|---|---|
17
+ | `invalid-argument` | 工具参数缺失、格式错误或不被允许。 | 缺少 `missionId`/`id`;未知状态;空 report;非法 scope glob;`timeoutMs < 1000`;更新时传入 `status: "all"`。 |
18
+ | `repo-invalid` | `repoRoot` 缺失、是相对路径,或不在 Git 仓库内。 | 传入的路径所在目录不是仓库 |
19
+ | `not-initialized` | 该仓库尚未初始化 Tower。 | 在 `tower_init` 之前调用读取类工具 |
20
+ | `mission-not-found` | 不存在该 id 的 mission。 | 过期或拼错的 `M9` |
21
+ | `finding-not-found` | 不存在该 id 的 finding。 | `tower_findings({id:"F99"})` |
22
+ | `invalid-transition` | 请求的状态转换不被允许。 | 尝试 blocked→merged;finding 状态图之外的流转;reviewer 修改 finding;在还有未完成任务、blocker、无提交或未读消息时完成 mission。 |
23
+ | `state-locked` | 另一个 Tower 写入者持有 state 锁。 | 锁超时后的并发写 |
24
+ | `state-corrupt` | Tower state 文件不可读或无效。 | 手工编辑过的 `state.json` |
25
+ | `config-invalid` | Tower 配置无效。 | 格式错误的仓库 `config.json`、违反 schema 的值 |
26
+ | `config-unknown-key` | Tower 配置存在未知顶层键。 | 例如把键拼成 `allowAgentToggle` |
27
+ | `git-failed` | git 命令因未归入其他分类的原因失败。 | 被拒绝的合并命令、非法 ref |
28
+ | `git-timeout` | git 命令超时或输出超出缓冲区。 | 慢 pre-merge 钩子、超大 diff |
29
+ | `spawn-unavailable` | subagents 服务不可用。 | 没有 `subagents` 的 Host |
30
+ | `provider-missing` | 没有注册的 subagent provider。 | provider 列表为空 |
31
+ | `preset-missing` | 配置的 agent preset 未注册。 | 缺少 `tower-worker`/`tower-reviewer` preset 行、DSH 未重启 |
32
+ | `reviewer-prerequisite` | reviewer spawn 的前置条件未满足。 | mission 尚未 `completed` 或还没有 worktree 就 spawn reviewer |
33
+ | `not-authorized` | 调用方 agent 角色无权执行该操作。 | worker 调用仅限控制塔的工具,或修改 mission 状态/scope;reviewer 修改 finding |
34
+ | `workspace-busy` | 另一个 Tower 会话持有该仓库的 workspace 锁。 | `mode.workspaceLock:true` 时第二个会话调用仓库级工具 |
35
+ | `worker-limit` | 仓库达到 `spawn.maxLiveWorkers` 个活跃 worker。 | 默认上限下 spawn 第五个 worker |
36
+ | `rate-limited` | 达到 `tower_send` 的每分钟速率限制。 | 超出 `send.maxPerMinute`;稍后重试 |
37
+ | `takeover-refused` | mission 仍有运行中的 worker。 | 在 `tower_interrupt` 之前调用 `tower_spawn({takeover:true})` |
38
+ | `reload-unavailable` | 当前 profile 中没有可用的 DSH HMR 服务。 | 在没有 HMR 的 profile 上调用 `tower_reload` / `/tower reload` |
39
+ | `reload-failed` | HMR reload 失败。 | `reconcileProfile` 抛错;检查 DSH 日志并重启 |
40
+ | `tests-failed` | 声明的测试发现失败,或不满足准入要求。 | 命令非零退出、输出格式错误、minCount/required 不足 |
41
+ | `tests-timeout` | 测试发现超出时间或输出预算。 | `tests.timeoutMs`,或 stdout 超过 `tests.maxBytes` |
42
+ | `lease-busy` | 另一个持有者拥有请求的咨询式租约。 | `heavy-ci` 被持有时调用 `tower_resource acquire` |
43
+ | `lease-token` | 租约释放 token 与持有者不匹配。 | 用另一个会话的 token 释放 |
44
+ | `batch-invalid` | batch 请求或其 close 证据无效。 | 未知成员、重复 id、不安全的回执路径、缺少回执哈希 |
45
+ | `legacy-invalid` | 旧版快照或映射无效。 | 未知 schema、源路径位于 Tower state 目录内、映射重写 |
46
+ | `tool-internal` | 意外失败。 | bug;阅读消息与 activity 日志 |
47
+
48
+ ## 为什么抛出?
49
+
50
+ 返回 `{ok:false}` 的形状保留给控制塔能决定的预期策略结果(`tower_merge`/`tower_rebase` 的门禁
51
+ blocker 以及 `tower_preflight`)。任何阻止工具给出决策的问题都会抛出,以免模型把它误当成正常的
52
+ 否定结果。