@armadra/agent 0.4.0 → 0.5.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 (422) hide show
  1. package/CHANGELOG.md +174 -1
  2. package/README.md +201 -82
  3. package/THIRD_PARTY_NOTICES.md +34 -0
  4. package/dist/acp.d.ts +18 -0
  5. package/dist/acp.js +19 -0
  6. package/dist/agent/limits.d.ts +38 -0
  7. package/dist/agent/limits.js +127 -0
  8. package/dist/agent/loop-guard.d.ts +38 -0
  9. package/dist/agent/loop-guard.js +95 -0
  10. package/dist/agent/loop.d.ts +4 -0
  11. package/dist/agent/loop.js +7 -0
  12. package/dist/agent/prompt-rules.d.ts +12 -0
  13. package/dist/agent/prompt-rules.js +18 -0
  14. package/dist/agent/reminders.d.ts +59 -0
  15. package/dist/agent/reminders.js +327 -0
  16. package/dist/agent/session-cache.d.ts +5 -0
  17. package/dist/agent/session-cache.js +17 -1
  18. package/dist/agent/session-classifier.d.ts +1 -1
  19. package/dist/agent/session-classifier.js +3 -1
  20. package/dist/agent/session-compaction.d.ts +35 -6
  21. package/dist/agent/session-compaction.js +157 -31
  22. package/dist/agent/session-core.d.ts +27 -1
  23. package/dist/agent/session-extensions.d.ts +61 -0
  24. package/dist/agent/session-extensions.js +129 -0
  25. package/dist/agent/session-images.d.ts +23 -0
  26. package/dist/agent/session-images.js +97 -0
  27. package/dist/agent/session-plan.d.ts +21 -0
  28. package/dist/agent/session-plan.js +524 -0
  29. package/dist/agent/session-rewind.d.ts +81 -0
  30. package/dist/agent/session-rewind.js +354 -0
  31. package/dist/agent/session-run.d.ts +28 -1
  32. package/dist/agent/session-run.js +157 -6
  33. package/dist/agent/session-settings.d.ts +49 -0
  34. package/dist/agent/session-settings.js +95 -0
  35. package/dist/agent/session-subagent.d.ts +36 -24
  36. package/dist/agent/session-subagent.js +259 -129
  37. package/dist/agent/session-sync.d.ts +10 -3
  38. package/dist/agent/session-sync.js +37 -8
  39. package/dist/agent/session-telemetry.d.ts +39 -0
  40. package/dist/agent/session-telemetry.js +231 -0
  41. package/dist/agent/session-tools.js +40 -1
  42. package/dist/agent/session.d.ts +30 -29
  43. package/dist/agent/session.js +79 -123
  44. package/dist/agent/subagent-registry.d.ts +91 -0
  45. package/dist/agent/subagent-registry.js +494 -0
  46. package/dist/agent/system-prompt.d.ts +6 -3
  47. package/dist/agent/system-prompt.js +7 -2
  48. package/dist/agent/tool-runner.d.ts +8 -1
  49. package/dist/agent/tool-runner.js +57 -4
  50. package/dist/agent/types-w5.d.ts +192 -0
  51. package/dist/agent/types-w5.js +10 -0
  52. package/dist/agent/types.d.ts +47 -4
  53. package/dist/agent/types.js +2 -0
  54. package/dist/agent/worktree.d.ts +36 -0
  55. package/dist/agent/worktree.js +86 -0
  56. package/dist/agents/builtin.d.ts +14 -0
  57. package/dist/agents/builtin.js +39 -0
  58. package/dist/agents/catalog.d.ts +43 -0
  59. package/dist/agents/catalog.js +82 -0
  60. package/dist/agents/discover.d.ts +35 -0
  61. package/dist/agents/discover.js +83 -0
  62. package/dist/agents/external.d.ts +82 -0
  63. package/dist/agents/external.js +326 -0
  64. package/dist/agents/parse.d.ts +18 -0
  65. package/dist/agents/parse.js +116 -0
  66. package/dist/agents/result.d.ts +32 -0
  67. package/dist/agents/result.js +67 -0
  68. package/dist/agents/task-control.d.ts +17 -0
  69. package/dist/agents/task-control.js +17 -0
  70. package/dist/agents/task-record.d.ts +78 -0
  71. package/dist/agents/task-record.js +141 -0
  72. package/dist/agents/types.d.ts +46 -0
  73. package/dist/agents/types.js +8 -0
  74. package/dist/ai/apis/anthropic-compat.d.ts +36 -0
  75. package/dist/ai/apis/anthropic-compat.js +92 -0
  76. package/dist/ai/apis/anthropic-request.d.ts +7 -6
  77. package/dist/ai/apis/anthropic-request.js +10 -28
  78. package/dist/ai/apis/cache-params.d.ts +11 -2
  79. package/dist/ai/apis/cache-params.js +25 -6
  80. package/dist/ai/apis/openai-compat.d.ts +4 -1
  81. package/dist/ai/apis/openai-compat.js +8 -1
  82. package/dist/ai/image-limits.d.ts +45 -0
  83. package/dist/ai/image-limits.js +68 -0
  84. package/dist/ai/providers/builtin.d.ts +18 -6
  85. package/dist/ai/providers/builtin.js +135 -6
  86. package/dist/ai/providers/catalog-data.js +17 -13
  87. package/dist/ai/providers/catalog.d.ts +60 -7
  88. package/dist/ai/providers/catalog.js +197 -38
  89. package/dist/ai/providers/channels.d.ts +33 -3
  90. package/dist/ai/providers/channels.js +108 -7
  91. package/dist/ai/providers/enrich.d.ts +6 -3
  92. package/dist/ai/providers/enrich.js +19 -4
  93. package/dist/ai/providers/models-dev-cache.d.ts +28 -24
  94. package/dist/ai/providers/models-dev-cache.js +92 -78
  95. package/dist/ai/providers/models-dev-data.d.ts +7 -0
  96. package/dist/ai/providers/models-dev-data.js +32 -0
  97. package/dist/ai/providers/models-dev-snapshot.d.ts +50 -0
  98. package/dist/ai/providers/models-dev-snapshot.js +137 -0
  99. package/dist/ai/providers/models-dev.d.ts +51 -10
  100. package/dist/ai/providers/models-dev.js +132 -39
  101. package/dist/ai/providers/registry.d.ts +20 -10
  102. package/dist/ai/providers/registry.js +82 -43
  103. package/dist/ai/types.d.ts +28 -2
  104. package/dist/ai/types.js +5 -0
  105. package/dist/bundle/ama.cjs +36203 -20499
  106. package/dist/checkpoints/backend.d.ts +61 -0
  107. package/dist/checkpoints/backend.js +135 -0
  108. package/dist/checkpoints/blobs.d.ts +60 -0
  109. package/dist/checkpoints/blobs.js +203 -0
  110. package/dist/checkpoints/gc.d.ts +47 -0
  111. package/dist/checkpoints/gc.js +144 -0
  112. package/dist/checkpoints/git-head.d.ts +15 -0
  113. package/dist/checkpoints/git-head.js +89 -0
  114. package/dist/checkpoints/index.d.ts +17 -0
  115. package/dist/checkpoints/index.js +17 -0
  116. package/dist/checkpoints/replay.d.ts +48 -0
  117. package/dist/checkpoints/replay.js +154 -0
  118. package/dist/checkpoints/restore.d.ts +86 -0
  119. package/dist/checkpoints/restore.js +263 -0
  120. package/dist/checkpoints/settings.d.ts +14 -0
  121. package/dist/checkpoints/settings.js +23 -0
  122. package/dist/checkpoints/shadow-git.d.ts +108 -0
  123. package/dist/checkpoints/shadow-git.js +491 -0
  124. package/dist/checkpoints/shadow-restore.d.ts +25 -0
  125. package/dist/checkpoints/shadow-restore.js +128 -0
  126. package/dist/checkpoints/tracker.d.ts +64 -0
  127. package/dist/checkpoints/tracker.js +192 -0
  128. package/dist/checkpoints/types.d.ts +99 -0
  129. package/dist/checkpoints/types.js +12 -0
  130. package/dist/cli/args.d.ts +8 -4
  131. package/dist/cli/args.js +17 -97
  132. package/dist/cli/bootstrap.js +13 -1
  133. package/dist/cli/codemode-notice.d.ts +3 -2
  134. package/dist/cli/codemode-notice.js +3 -2
  135. package/dist/cli/compose-agents.d.ts +27 -0
  136. package/dist/cli/compose-agents.js +111 -0
  137. package/dist/cli/compose-extensions.d.ts +29 -0
  138. package/dist/cli/compose-extensions.js +44 -0
  139. package/dist/cli/compose-session.d.ts +3 -0
  140. package/dist/cli/compose-session.js +47 -4
  141. package/dist/cli/compose.d.ts +3 -0
  142. package/dist/cli/compose.js +33 -6
  143. package/dist/cli/deps.d.ts +6 -0
  144. package/dist/cli/exit-codes.d.ts +10 -0
  145. package/dist/cli/exit-codes.js +12 -0
  146. package/dist/cli/help-text.d.ts +4 -0
  147. package/dist/cli/help-text.js +102 -0
  148. package/dist/cli/startup-screen.js +5 -3
  149. package/dist/cli/startup-steps.js +7 -1
  150. package/dist/cli/subcommands/config.d.ts +4 -1
  151. package/dist/cli/subcommands/config.js +17 -6
  152. package/dist/cli/subcommands/doctor.js +21 -0
  153. package/dist/cli/subcommands/models-discover.js +5 -6
  154. package/dist/cli/subcommands/models.d.ts +2 -0
  155. package/dist/cli/subcommands/models.js +33 -14
  156. package/dist/cli/subcommands/providers.js +5 -6
  157. package/dist/cli/subcommands/sessions.d.ts +3 -1
  158. package/dist/cli/subcommands/sessions.js +36 -2
  159. package/dist/codemode/capability.d.ts +24 -7
  160. package/dist/codemode/capability.js +38 -10
  161. package/dist/codemode/host-side.d.ts +14 -0
  162. package/dist/codemode/host-side.js +24 -1
  163. package/dist/codemode/tool.d.ts +1 -1
  164. package/dist/codemode/tool.js +6 -4
  165. package/dist/compaction/breaker.d.ts +25 -9
  166. package/dist/compaction/breaker.js +47 -17
  167. package/dist/compaction/estimate.d.ts +11 -3
  168. package/dist/compaction/estimate.js +64 -22
  169. package/dist/compaction/image-budget.d.ts +45 -0
  170. package/dist/compaction/image-budget.js +115 -0
  171. package/dist/compaction/post-compact.d.ts +38 -0
  172. package/dist/compaction/post-compact.js +152 -0
  173. package/dist/compaction/protect.d.ts +27 -0
  174. package/dist/compaction/protect.js +56 -0
  175. package/dist/compaction/prune-tier.d.ts +58 -12
  176. package/dist/compaction/prune-tier.js +120 -54
  177. package/dist/compaction/summarize-tier.d.ts +23 -4
  178. package/dist/compaction/summarize-tier.js +100 -29
  179. package/dist/config/checker.d.ts +28 -0
  180. package/dist/config/checker.js +97 -0
  181. package/dist/config/json-schema.js +63 -2
  182. package/dist/config/key-docs.js +72 -3
  183. package/dist/config/merge.d.ts +5 -3
  184. package/dist/config/merge.js +81 -7
  185. package/dist/config/profile.d.ts +2 -0
  186. package/dist/config/profile.js +2 -0
  187. package/dist/config/schema-w5.d.ts +18 -0
  188. package/dist/config/schema-w5.js +109 -0
  189. package/dist/config/schema.d.ts +5 -9
  190. package/dist/config/schema.js +68 -114
  191. package/dist/config/types-w5.d.ts +118 -0
  192. package/dist/config/types-w5.js +20 -0
  193. package/dist/config/types.d.ts +50 -1
  194. package/dist/config/types.js +12 -0
  195. package/dist/drivers/acp/client.d.ts +57 -0
  196. package/dist/drivers/acp/client.js +160 -0
  197. package/dist/drivers/acp/driver.d.ts +31 -0
  198. package/dist/drivers/acp/driver.js +274 -0
  199. package/dist/drivers/acp/testing/fake-agent-main.d.ts +5 -0
  200. package/dist/drivers/acp/testing/fake-agent-main.js +7 -0
  201. package/dist/drivers/acp/testing/fake-agent.d.ts +27 -0
  202. package/dist/drivers/acp/testing/fake-agent.js +211 -0
  203. package/dist/drivers/acp/types.d.ts +285 -0
  204. package/dist/drivers/acp/types.js +36 -0
  205. package/dist/drivers/agents.d.ts +58 -0
  206. package/dist/drivers/agents.js +142 -0
  207. package/dist/drivers/base.d.ts +35 -0
  208. package/dist/drivers/base.js +59 -0
  209. package/dist/drivers/catalog.d.ts +46 -0
  210. package/dist/drivers/catalog.js +167 -0
  211. package/dist/drivers/env.d.ts +20 -0
  212. package/dist/drivers/env.js +46 -0
  213. package/dist/drivers/host-runners.d.ts +18 -0
  214. package/dist/drivers/host-runners.js +45 -0
  215. package/dist/drivers/jsonrpc.d.ts +58 -0
  216. package/dist/drivers/jsonrpc.js +180 -0
  217. package/dist/drivers/native/claude-normalize.d.ts +46 -0
  218. package/dist/drivers/native/claude-normalize.js +106 -0
  219. package/dist/drivers/native/claude-stream.d.ts +29 -0
  220. package/dist/drivers/native/claude-stream.js +456 -0
  221. package/dist/drivers/native/codex-app-server.d.ts +26 -0
  222. package/dist/drivers/native/codex-app-server.js +407 -0
  223. package/dist/drivers/native/codex-normalize.d.ts +38 -0
  224. package/dist/drivers/native/codex-normalize.js +124 -0
  225. package/dist/drivers/native/oneshot.d.ts +28 -0
  226. package/dist/drivers/native/oneshot.js +312 -0
  227. package/dist/drivers/permissions.d.ts +40 -0
  228. package/dist/drivers/permissions.js +92 -0
  229. package/dist/drivers/pids.d.ts +32 -0
  230. package/dist/drivers/pids.js +93 -0
  231. package/dist/drivers/pool.d.ts +28 -0
  232. package/dist/drivers/pool.js +96 -0
  233. package/dist/drivers/probe.d.ts +47 -0
  234. package/dist/drivers/probe.js +172 -0
  235. package/dist/drivers/process.d.ts +48 -0
  236. package/dist/drivers/process.js +100 -0
  237. package/dist/drivers/runner.d.ts +61 -0
  238. package/dist/drivers/runner.js +388 -0
  239. package/dist/drivers/store.d.ts +49 -0
  240. package/dist/drivers/store.js +91 -0
  241. package/dist/drivers/turn.d.ts +41 -0
  242. package/dist/drivers/turn.js +99 -0
  243. package/dist/drivers/types.d.ts +122 -0
  244. package/dist/drivers/types.js +11 -0
  245. package/dist/git/info.d.ts +75 -0
  246. package/dist/git/info.js +191 -0
  247. package/dist/hooks/protocol.js +6 -1
  248. package/dist/hooks/types.d.ts +10 -1
  249. package/dist/hooks/types.js +2 -0
  250. package/dist/host/api-impl.d.ts +7 -0
  251. package/dist/host/api-impl.js +15 -0
  252. package/dist/host/types.d.ts +24 -2
  253. package/dist/host/types.js +4 -0
  254. package/dist/index.d.ts +3 -0
  255. package/dist/index.js +1 -0
  256. package/dist/modes/acp/acp-events.d.ts +46 -0
  257. package/dist/modes/acp/acp-events.js +233 -0
  258. package/dist/modes/acp/acp-mode.d.ts +17 -0
  259. package/dist/modes/acp/acp-mode.js +47 -0
  260. package/dist/modes/acp/acp-server.d.ts +79 -0
  261. package/dist/modes/acp/acp-server.js +315 -0
  262. package/dist/modes/commands-core.d.ts +7 -1
  263. package/dist/modes/commands-core.js +55 -0
  264. package/dist/modes/image-input.d.ts +12 -4
  265. package/dist/modes/image-input.js +22 -5
  266. package/dist/modes/interactive/agent-panels.d.ts +14 -0
  267. package/dist/modes/interactive/agent-panels.js +103 -0
  268. package/dist/modes/interactive/agent-ui.d.ts +73 -0
  269. package/dist/modes/interactive/agent-ui.js +234 -0
  270. package/dist/modes/interactive/approval-dialog.d.ts +21 -4
  271. package/dist/modes/interactive/approval-dialog.js +98 -20
  272. package/dist/modes/interactive/approval-merge.d.ts +41 -0
  273. package/dist/modes/interactive/approval-merge.js +89 -0
  274. package/dist/modes/interactive/clipboard-paste.d.ts +18 -0
  275. package/dist/modes/interactive/clipboard-paste.js +28 -0
  276. package/dist/modes/interactive/commands.d.ts +9 -1
  277. package/dist/modes/interactive/commands.js +19 -0
  278. package/dist/modes/interactive/double-esc.d.ts +20 -0
  279. package/dist/modes/interactive/double-esc.js +34 -0
  280. package/dist/modes/interactive/event-notices.d.ts +32 -0
  281. package/dist/modes/interactive/event-notices.js +79 -0
  282. package/dist/modes/interactive/external-editor.d.ts +16 -0
  283. package/dist/modes/interactive/external-editor.js +39 -0
  284. package/dist/modes/interactive/interactive-mode.d.ts +14 -1
  285. package/dist/modes/interactive/interactive-mode.js +134 -136
  286. package/dist/modes/interactive/key-dispatch.d.ts +16 -2
  287. package/dist/modes/interactive/key-dispatch.js +49 -1
  288. package/dist/modes/interactive/line/line-mode.d.ts +2 -1
  289. package/dist/modes/interactive/line/line-mode.js +22 -5
  290. package/dist/modes/interactive/line/line-render.d.ts +2 -1
  291. package/dist/modes/interactive/line/line-render.js +40 -4
  292. package/dist/modes/interactive/message-view.d.ts +1 -1
  293. package/dist/modes/interactive/message-view.js +24 -2
  294. package/dist/modes/interactive/panels.d.ts +18 -1
  295. package/dist/modes/interactive/panels.js +19 -5
  296. package/dist/modes/interactive/plan-command.d.ts +40 -0
  297. package/dist/modes/interactive/plan-command.js +129 -0
  298. package/dist/modes/interactive/plan-dialog.d.ts +98 -0
  299. package/dist/modes/interactive/plan-dialog.js +290 -0
  300. package/dist/modes/interactive/plan-flow.d.ts +34 -0
  301. package/dist/modes/interactive/plan-flow.js +98 -0
  302. package/dist/modes/interactive/rewind-command.d.ts +31 -0
  303. package/dist/modes/interactive/rewind-command.js +106 -0
  304. package/dist/modes/interactive/rewind-flow.d.ts +37 -0
  305. package/dist/modes/interactive/rewind-flow.js +151 -0
  306. package/dist/modes/interactive/rewind-list.d.ts +36 -0
  307. package/dist/modes/interactive/rewind-list.js +123 -0
  308. package/dist/modes/interactive/rewind-panel.d.ts +77 -0
  309. package/dist/modes/interactive/rewind-panel.js +309 -0
  310. package/dist/modes/interactive/rewind-text.d.ts +40 -0
  311. package/dist/modes/interactive/rewind-text.js +139 -0
  312. package/dist/modes/interactive/session-events.d.ts +24 -0
  313. package/dist/modes/interactive/session-events.js +96 -0
  314. package/dist/modes/interactive/status-area.d.ts +73 -0
  315. package/dist/modes/interactive/status-area.js +190 -0
  316. package/dist/modes/interactive/status-bar.d.ts +99 -11
  317. package/dist/modes/interactive/status-bar.js +273 -90
  318. package/dist/modes/interactive/status-line.d.ts +33 -0
  319. package/dist/modes/interactive/status-line.js +113 -0
  320. package/dist/modes/interactive/subagent-view.d.ts +56 -0
  321. package/dist/modes/interactive/subagent-view.js +154 -0
  322. package/dist/modes/interactive/tasks-report.d.ts +27 -0
  323. package/dist/modes/interactive/tasks-report.js +114 -0
  324. package/dist/modes/interactive/tool-view.d.ts +12 -2
  325. package/dist/modes/interactive/tool-view.js +34 -4
  326. package/dist/modes/print/print-mode.d.ts +13 -4
  327. package/dist/modes/print/print-mode.js +56 -12
  328. package/dist/modes/rpc/commands.d.ts +9 -0
  329. package/dist/modes/rpc/commands.js +44 -0
  330. package/dist/modes/rpc/rpc-mode.js +7 -1
  331. package/dist/modes/session-report.d.ts +11 -0
  332. package/dist/modes/session-report.js +48 -1
  333. package/dist/permissions/auto-safe.d.ts +5 -0
  334. package/dist/permissions/auto-safe.js +18 -0
  335. package/dist/permissions/classifier.d.ts +4 -0
  336. package/dist/permissions/classifier.js +2 -0
  337. package/dist/permissions/modes.js +1 -1
  338. package/dist/permissions/pipeline.d.ts +43 -4
  339. package/dist/permissions/pipeline.js +114 -9
  340. package/dist/permissions/readonly-bash.d.ts +30 -0
  341. package/dist/permissions/readonly-bash.js +118 -0
  342. package/dist/permissions/types.d.ts +42 -0
  343. package/dist/plan/compose.d.ts +9 -0
  344. package/dist/plan/compose.js +19 -0
  345. package/dist/plan/controller.d.ts +71 -0
  346. package/dist/plan/controller.js +27 -0
  347. package/dist/plan/done-markers.d.ts +17 -0
  348. package/dist/plan/done-markers.js +51 -0
  349. package/dist/plan/extract.d.ts +29 -0
  350. package/dist/plan/extract.js +160 -0
  351. package/dist/plan/prompts.d.ts +36 -0
  352. package/dist/plan/prompts.js +89 -0
  353. package/dist/plan/store.d.ts +37 -0
  354. package/dist/plan/store.js +90 -0
  355. package/dist/rpc.d.ts +58 -2
  356. package/dist/rpc.js +7 -0
  357. package/dist/sandbox/bash.d.ts +74 -0
  358. package/dist/sandbox/bash.js +126 -0
  359. package/dist/sandbox/detect.d.ts +48 -0
  360. package/dist/sandbox/detect.js +149 -0
  361. package/dist/sandbox/index.d.ts +5 -0
  362. package/dist/sandbox/index.js +5 -0
  363. package/dist/sandbox/profile.d.ts +33 -0
  364. package/dist/sandbox/profile.js +76 -0
  365. package/dist/sandbox/wrap.d.ts +29 -0
  366. package/dist/sandbox/wrap.js +117 -0
  367. package/dist/sdk.d.ts +33 -2
  368. package/dist/sdk.js +24 -2
  369. package/dist/session/types.d.ts +3 -1
  370. package/dist/session/types.js +1 -0
  371. package/dist/skills/builtin.d.ts +31 -0
  372. package/dist/skills/builtin.js +109 -0
  373. package/dist/skills/discover.d.ts +2 -1
  374. package/dist/skills/index-prompt.js +5 -8
  375. package/dist/tools/background-jobs.d.ts +71 -0
  376. package/dist/tools/background-jobs.js +198 -0
  377. package/dist/tools/bash.d.ts +53 -1
  378. package/dist/tools/bash.js +158 -7
  379. package/dist/tools/clipboard-image.d.ts +44 -0
  380. package/dist/tools/clipboard-image.js +148 -0
  381. package/dist/tools/edit.d.ts +2 -0
  382. package/dist/tools/edit.js +18 -3
  383. package/dist/tools/image-file.d.ts +38 -3
  384. package/dist/tools/image-file.js +81 -10
  385. package/dist/tools/image-resize.d.ts +58 -0
  386. package/dist/tools/image-resize.js +97 -0
  387. package/dist/tools/presets.d.ts +7 -2
  388. package/dist/tools/presets.js +11 -4
  389. package/dist/tools/read.d.ts +5 -2
  390. package/dist/tools/read.js +13 -10
  391. package/dist/tools/registry.d.ts +7 -1
  392. package/dist/tools/registry.js +27 -4
  393. package/dist/tools/task-ctl.d.ts +22 -0
  394. package/dist/tools/task-ctl.js +116 -0
  395. package/dist/tools/task.d.ts +22 -10
  396. package/dist/tools/task.js +66 -26
  397. package/dist/tools/todo.d.ts +22 -2
  398. package/dist/tools/todo.js +58 -14
  399. package/dist/tools/truncate.d.ts +15 -0
  400. package/dist/tools/truncate.js +25 -0
  401. package/dist/tools/types.d.ts +113 -0
  402. package/dist/tools/types.js +5 -0
  403. package/dist/tools/write.js +3 -0
  404. package/dist/tui/glyphs.d.ts +4 -0
  405. package/dist/tui/glyphs.js +4 -0
  406. package/dist/tui/keybindings.d.ts +5 -0
  407. package/dist/tui/keybindings.js +5 -0
  408. package/docs/acp.md +78 -0
  409. package/docs/agents.md +250 -0
  410. package/docs/codemode.md +12 -8
  411. package/docs/hooks.md +26 -22
  412. package/docs/permissions.md +77 -13
  413. package/docs/plan.md +107 -0
  414. package/docs/providers.md +228 -52
  415. package/docs/rewind-plan.md +181 -0
  416. package/docs/rpc.md +92 -5
  417. package/docs/sandbox.md +215 -0
  418. package/docs/session-format.md +41 -21
  419. package/docs/sessions.md +41 -1
  420. package/docs/tui-design.md +751 -0
  421. package/docs/tui.md +125 -6
  422. package/package.json +13 -2
@@ -8,9 +8,14 @@
8
8
  * - 审批:`RpcApprovals` 是 UI broker(`set_client_capabilities` 声明 approvals 后挂上),
9
9
  * `permission_response` 按 requestId 作答;先到的回答暂存,等 broker 被问到时取用。
10
10
  * - 密钥不离开进程:`get_available_models` 只回 `hasKey` 与 `keySource`。
11
+ * - [W5-F] 计划:声明 `plans` 能力后计划审批交客户端(`plan_proposed` → `plan_response`),未声明按
12
+ * `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
13
+ * 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
11
14
  */
12
15
  import { formatModelRef } from "../../ai/providers/channels.js";
13
16
  import { AgentSessionImpl } from "../../agent/session.js";
17
+ import { planController } from "../../agent/session-plan.js";
18
+ import { currentTodos } from "../../plan/store.js";
14
19
  import { getSupportedLevels } from "../../ai/thinking.js";
15
20
  import { switchSession } from "../../cli/compose-session.js";
16
21
  import { AmaError } from "../../errors.js";
@@ -62,6 +67,17 @@ function impl(session) {
62
67
  return session;
63
68
  throw new AmaError("not_implemented", "该会话不支持此命令");
64
69
  }
70
+ /** [W5-F] 当前会话的计划控制面;会话没有装配 plan 扩展时报 not_implemented。 */
71
+ function plans(ctx) {
72
+ const controller = planController(ctx.session());
73
+ if (controller === undefined)
74
+ throw new AmaError("not_implemented", "该会话没有装配 plan 扩展");
75
+ return controller;
76
+ }
77
+ /** 声明了 `plans` 能力 → 客户端作答;否则按 `plan.unattended`。换会话后重新应用。 */
78
+ function applyPlanAttendance(ctx) {
79
+ planController(ctx.session())?.setAttendance(ctx.capabilities.has("plans") ? "client" : "unattended");
80
+ }
65
81
  /** 开始一次运行:开始 / 入队 / 被处理后即返回,不等运行结束。 */
66
82
  async function startRun(ctx, run) {
67
83
  const session = ctx.session();
@@ -89,6 +105,7 @@ async function startRun(ctx, run) {
89
105
  async function switched(ctx, next, reason) {
90
106
  const session = await next;
91
107
  ctx.onSessionChanged(session);
108
+ applyPlanAttendance(ctx);
92
109
  if (session instanceof AgentSessionImpl)
93
110
  session.announceStart(reason);
94
111
  return { sessionId: session.state.sessionId, sessionFile: session.state.sessionFile };
@@ -213,12 +230,24 @@ export const handlers = {
213
230
  ]
214
231
  : []),
215
232
  }),
233
+ get_rewind_points: async (_p, ctx) => ({ points: ctx.session().rewindPoints() }),
234
+ rewind: async (p, ctx) => {
235
+ const request = { entryId: p.entryId, mode: p.mode };
236
+ if (p.dryRun !== undefined)
237
+ request.dryRun = p.dryRun;
238
+ if (p.onConflict !== undefined)
239
+ request.onConflict = p.onConflict;
240
+ return ctx.session().rewind(request);
241
+ },
242
+ summarize_from: async (p, ctx) => ctx.session().summarizeFrom(p.entryId, p.instructions),
243
+ summarize_up_to: async (p, ctx) => ctx.session().summarizeUpTo(p.entryId, p.instructions),
216
244
  set_client_capabilities: async (p, ctx) => {
217
245
  ctx.capabilities = new Set(p.capabilities);
218
246
  const approvals = ctx.capabilities.has("approvals");
219
247
  ctx.runtime.approvals.setUiBroker(approvals ? ctx.approvals : undefined);
220
248
  if (!approvals)
221
249
  ctx.approvals.cancelAll();
250
+ applyPlanAttendance(ctx);
222
251
  return { capabilities: [...ctx.capabilities] };
223
252
  },
224
253
  permission_response: async (p, ctx) => ({
@@ -264,5 +293,20 @@ export const handlers = {
264
293
  },
265
294
  get_commands: async (_p, ctx) => ({ commands: commands(ctx.runtime) }),
266
295
  get_skills: async (_p, ctx) => ({ skills: ctx.runtime.resources.skills }),
296
+ // [W5-F] 计划 / 任务命令(docs/wave5-plan.md §6.5、§7.5)
297
+ plan_response: async (p, ctx) => {
298
+ const result = await plans(ctx).respond(p);
299
+ if (result.freshPrompt !== undefined) {
300
+ const prompt = result.freshPrompt;
301
+ await switched(ctx, switchSession(ctx.runtime, { kind: "new" }), "new");
302
+ planController(ctx.session())?.adopt(result.plan);
303
+ await startRun(ctx, () => ctx.session().prompt(prompt, { origin: "plan" }));
304
+ }
305
+ return { planId: result.planId, decision: result.decision };
306
+ },
307
+ get_plan: async (p, ctx) => plans(ctx).get(p.planId),
308
+ get_todos: async (_p, ctx) => ({ items: currentTodos(impl(ctx.session()).manager.branch()) }),
309
+ get_tasks: async (_p, ctx) => ({ tasks: [...(ctx.tasks?.()?.list() ?? [])] }),
310
+ get_agents: async (_p, ctx) => ({ agents: [...(ctx.agents?.() ?? [])] }),
267
311
  };
268
312
  export const RPC_COMMAND_TYPES = Object.keys(handlers);
@@ -19,9 +19,11 @@ import { RPC_PROTOCOL_VERSION } from "../../rpc.js";
19
19
  import { AMA_VERSION } from "../../version.js";
20
20
  import { toJsonLine, toWireEvent } from "../print/json-event.js";
21
21
  import { errorText, onTerminationSignals } from "../shared.js";
22
+ import { sessionAgents, taskRegistryView } from "../../agent/subagent-registry.js";
23
+ import { cachedAgentInfos } from "../../agents/external.js";
22
24
  import { RpcApprovals, handlers } from "./commands.js";
23
25
  import { createLineReader, writeChunked } from "./jsonl.js";
24
- export const RPC_CAPABILITIES = ["approvals", "images", "hooks"];
26
+ export const RPC_CAPABILITIES = ["approvals", "images", "hooks", "plans"];
25
27
  export async function runRpcMode(runtime, context, options = {}) {
26
28
  const stdin = options.stdin ?? process.stdin;
27
29
  const stdout = options.stdout ?? process.stdout;
@@ -48,6 +50,10 @@ export async function runRpcMode(runtime, context, options = {}) {
48
50
  onBackgroundError(error) {
49
51
  void write({ type: "notification", level: "error", message: errorText(error) });
50
52
  },
53
+ // [W5-G] get_tasks / get_agents:当前会话的任务注册表与可用类型
54
+ tasks: () => taskRegistryView(session.state.sessionId),
55
+ // [W5-EG] 外部 Agent 的安装 / 版本要异步探测:会话建立时缓存、变化时刷新,未就绪时只列类型目录
56
+ agents: () => cachedAgentInfos(session.state.sessionId) ?? sessionAgents(session.state.sessionId),
51
57
  };
52
58
  runtime.notifier.set((message, level) => {
53
59
  void write({ type: "notification", level, message });
@@ -47,6 +47,17 @@ export declare function warmStopText(reason: string | undefined): string;
47
47
  export declare function warmingText(status: WarmerStatus, now: number, minSavingsUsd: number | undefined): string;
48
48
  /** 缓存段的键值行(`/session` 与 `/cache` 共用)。 */
49
49
  export declare function cacheRows(session: AgentSession, now?: number): KeyValueRow[];
50
+ /** [W5-U] 外部 Agent 用量一项:美元 / token / 请求数按各自单位,不换算。 */
51
+ export declare function externalUsageText(usage: {
52
+ runs: number;
53
+ unit: "usd" | "tokens" | "requests";
54
+ amount: number;
55
+ tokens?: number;
56
+ }): string;
57
+ /** [W5-U] 「外部 Agent」段(`getStats().external.byAgent`);没有外部运行时为空。 */
58
+ export declare function externalRows(session: AgentSession): KeyValueRow[];
59
+ /** [W5-U] 子 Agent 任务汇总(`getStats().tasks`);没有任务时 undefined。 */
60
+ export declare function taskStatsText(session: AgentSession): string | undefined;
50
61
  /** 键值行 → 纯文本(键列对齐、去行尾空白)。 */
51
62
  export declare function renderRows(rows: readonly KeyValueRow[], indent?: string): string[];
52
63
  export declare function describeCache(session: AgentSession, now?: number): string;
@@ -219,6 +219,45 @@ export function cacheRows(session, now = Date.now()) {
219
219
  }
220
220
  return rows;
221
221
  }
222
+ /** [W5-U] 外部 Agent 用量一项:美元 / token / 请求数按各自单位,不换算。 */
223
+ export function externalUsageText(usage) {
224
+ const amount = usage.unit === "usd"
225
+ ? formatUsd(usage.amount)
226
+ : usage.unit === "tokens"
227
+ ? `${formatTokenCount(usage.amount)} token`
228
+ : `${usage.amount} 次请求`;
229
+ const tokens = usage.unit !== "tokens" && usage.tokens !== undefined && usage.tokens > 0
230
+ ? ` · ${formatTokenCount(usage.tokens)} token`
231
+ : "";
232
+ return `${usage.runs} 次运行 · ${amount}${tokens}`;
233
+ }
234
+ /** [W5-U] 「外部 Agent」段(`getStats().external.byAgent`);没有外部运行时为空。 */
235
+ export function externalRows(session) {
236
+ const byAgent = session.getStats().external?.byAgent ?? {};
237
+ return Object.entries(byAgent)
238
+ .sort(([a], [b]) => a.localeCompare(b))
239
+ .map(([agent, usage]) => ({ key: agent, value: externalUsageText(usage) }));
240
+ }
241
+ /** [W5-U] 子 Agent 任务汇总(`getStats().tasks`);没有任务时 undefined。 */
242
+ export function taskStatsText(session) {
243
+ const tasks = session.getStats().tasks;
244
+ if (tasks === undefined || tasks.total === 0)
245
+ return undefined;
246
+ const parts = [`${tasks.total} 个任务`];
247
+ if (tasks.running > 0)
248
+ parts.push(`运行中 ${tasks.running}`);
249
+ const labels = {
250
+ completed: "完成",
251
+ failed: "失败",
252
+ aborted: "已停止",
253
+ max_turns: "轮数耗尽",
254
+ interrupted: "已中断",
255
+ };
256
+ for (const [status, count] of Object.entries(tasks.byStatus))
257
+ if (count !== undefined && count > 0)
258
+ parts.push(`${labels[status] ?? status} ${count}`);
259
+ return `${parts.join(" · ")}(/tasks)`;
260
+ }
222
261
  /** 键值行 → 纯文本(键列对齐、去行尾空白)。 */
223
262
  export function renderRows(rows, indent = "") {
224
263
  return new KeyValue(rows)
@@ -257,7 +296,15 @@ export function describeSession(session, now = Date.now()) {
257
296
  value: `${stats.contextTokens ?? "?"} / ${stats.contextWindow ?? "?"}(${percent}%)`,
258
297
  },
259
298
  ];
260
- return [...renderRows(rows), describeCache(session, now)].join("\n");
299
+ const tasks = taskStatsText(session);
300
+ if (tasks !== undefined)
301
+ rows.push({ key: "子 Agent", value: tasks });
302
+ const external = externalRows(session);
303
+ return [
304
+ ...renderRows(rows),
305
+ describeCache(session, now),
306
+ ...(external.length > 0 ? ["外部 Agent", ...renderRows(external, " ")] : []),
307
+ ].join("\n");
261
308
  }
262
309
  /** `/cache fingerprint`:最近一次真实请求的前缀指纹(system / tools 哈希与模型)。 */
263
310
  export function describeFingerprint(session) {
@@ -58,3 +58,8 @@ export interface BashAutoAnalysis {
58
58
  }
59
59
  /** auto 模式下一条 bash 命令的规则层与静态判定结论。 */
60
60
  export declare function analyzeBashForAuto(command: string, options: BashAutoOptions): BashAutoAnalysis;
61
+ /**
62
+ * [S2] 命令文本里出现的机密路径(读写都算,含嵌套命令);没有返回 undefined。default 模式「沙箱内免审批」
63
+ * 用它把 `cat .env` 之类留给正常审批:OS 沙箱挡不住读工作区里的机密文件,输出会进模型上下文。
64
+ */
65
+ export declare function secretReferenceReason(command: string, cwd: string): string | undefined;
@@ -527,3 +527,21 @@ export function analyzeBashForAuto(command, options) {
527
527
  }
528
528
  return { safe: true, reason: "every command is in the auto safe list" };
529
529
  }
530
+ /**
531
+ * [S2] 命令文本里出现的机密路径(读写都算,含嵌套命令);没有返回 undefined。default 模式「沙箱内免审批」
532
+ * 用它把 `cat .env` 之类留给正常审批:OS 沙箱挡不住读工作区里的机密文件,输出会进模型上下文。
533
+ */
534
+ export function secretReferenceReason(command, cwd) {
535
+ const nested = collectNestedCommands(command);
536
+ for (const text of [command, ...nested.commands]) {
537
+ for (const segment of splitShellSegments(text)) {
538
+ const { argv, redirects } = parseSegment(segment);
539
+ for (const word of [...argv.slice(1), ...redirects.inputs, ...redirects.outputs]) {
540
+ const reason = secretInWord(word, cwd);
541
+ if (reason !== undefined)
542
+ return reason;
543
+ }
544
+ }
545
+ }
546
+ return undefined;
547
+ }
@@ -24,6 +24,10 @@ export interface ClassifierRequest {
24
24
  projectRoot: string;
25
25
  /** 最近一条用户消息(会被截断)。 */
26
26
  userMessage?: string;
27
+ /** [S2] bash 将经 OS 沙箱运行(写入只限工作区与临时目录、网络按配置):交给分类器作为输入之一。 */
28
+ sandbox?: {
29
+ network: "deny" | "allow";
30
+ };
27
31
  }
28
32
  export interface ClassifierVerdict {
29
33
  decision: "allow" | "ask";
@@ -65,6 +65,8 @@ export function buildClassifierPrompt(request) {
65
65
  `project_root: ${neutralize(JSON.stringify(request.projectRoot))}`,
66
66
  `arguments_json: ${neutralize(args)}`,
67
67
  ];
68
+ if (request.sandbox !== undefined)
69
+ lines.push(`os_sandbox: "writes limited to the project and temp dirs; network ${request.sandbox.network === "deny" ? "blocked" : "allowed"}"`);
68
70
  if (request.userMessage !== undefined && request.userMessage.trim() !== "") {
69
71
  const summary = truncate(request.userMessage.replace(/\s+/g, " ").trim(), CLASSIFIER_USER_LIMIT);
70
72
  lines.push(`latest_user_message: ${neutralize(JSON.stringify(summary))}`);
@@ -8,7 +8,7 @@ import { PERMISSION_MODES_STRICT_FIRST } from "./types.js";
8
8
  export const PERMISSION_MODE_INFO = {
9
9
  default: { label: "Manual", description: "写文件、执行命令前询问" },
10
10
  "auto-edit": { label: "Accept edits", description: "自动接受文件编辑,执行命令仍询问" },
11
- plan: { label: "Plan", description: "只读,不改文件、不跑命令" },
11
+ plan: { label: "Plan", description: "只读调研,只跑只读命令,出计划后审批执行" },
12
12
  auto: { label: "Auto", description: "由 ama 判断每一步:安全的自动放行,有风险的才问" },
13
13
  "full-auto": { label: "Bypass permissions", description: "全部放行(危险命令仍询问)" },
14
14
  allowlist: {
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * ① deny 规则 ∪ Hook deny → deny
5
5
  * ② 危险命令(仅 bash) → ask(无人值守 deny),之后各步不能放宽
6
- * ③ 模式:plan read 允许,write / execute deny
6
+ * ③ 模式:plan read 允许,write / execute deny([W5-F] 细化见 {@link planDecision}:只读 bash、
7
+ * task 放行,`todo set / update` 拒绝)
7
8
  * default read 允许,write / execute 问
8
9
  * auto-edit read / write 允许,execute 问
9
10
  * full-auto 全部允许
@@ -16,23 +17,45 @@
16
17
  * 安全名单里的 bash)→ allow;都没决定 → ask 且 `classify: true`,由调用方问模型分类器(分类器只能
17
18
  * 把它变 allow)。每个结论带 `auto{layer, decision, reason}`。
18
19
  *
19
- * allowlist:①② 同上;只读工具与 allow 规则 / Hook allow 命中放行,其余 deny——从不询问(危险命令、
20
- * Hook ask 也 deny)。
20
+ * allowlist:①② 同上;只读工具、只读 bash([W5-F] 与 plan 同一子集,保持 plan ⊆ allowlist)与
21
+ * allow 规则 / Hook allow 命中放行,task 同 plan 放行(子会话共用本管线),其余 deny——从不询问
22
+ * (危险命令、Hook ask 也 deny)。
21
23
  *
22
24
  * allow_session 记忆(内存,不落盘):bash 记命令的前两个词(`npm test`),之后以它开头的命令
23
25
  * 命中;带 path 的工具记文件所在目录,之后该目录下的路径命中;其它工具只记工具名。
24
26
  * 记忆只作用于第 ③ 步模式产生的 ask(auto 里只作用于规则层之后)。
25
27
  *
28
+ * [S2] bash 沙箱(docs/permissions.md「判定顺序」):default / auto-edit 下,bash 调用若将在 OS 沙箱内
29
+ * 运行且沙箱拒绝网络、没请求 `sandbox: false`、命令文本不碰机密路径、嵌套不超深,第 ③ 步的 ask 变
30
+ * allow(`sandboxed: true`);①② 与 Hook ask 照旧优先。auto 下沙箱不直接放行,只作为分类器输入;
31
+ * 请求 `sandbox: false` 越出沙箱时规则层询问(allow 规则 / Hook allow / 会话记忆仍可放行)。
32
+ *
26
33
  * 包装里的命令(`sh -c '…'`、`eval`、`xargs`、`find -exec`,见 dangerous.ts)逐层核对:deny 规则命中
27
34
  * 任一层即 deny;allow 规则与会话记忆要求外层与每一层嵌套命令都被覆盖,嵌套超深时不放行。
28
35
  */
29
36
  import type { AutoAuditEntry, AutoDecision, Decision, PermissionCheckInput, PermissionMode, PermissionPipelineApi, PermissionVerdict, Rule } from "./types.js";
30
37
  import type { ToolPermission } from "../tools/types.js";
38
+ import { type BashSandbox } from "../sandbox/bash.js";
39
+ import type { PlanBashMode } from "../config/types-w5.js";
31
40
  /**
32
41
  * 第 ③ 步的模式真值表(不看输入)。auto 的 write / execute 在这里是 ask,真正的结论由规则层、
33
42
  * 静态判定与分类器给出;allowlist 的 write / execute 是 deny(allow 规则可放行)。
34
43
  */
35
44
  export declare function modeDecision(mode: PermissionMode, permission: ToolPermission): Decision;
45
+ /** [W5-F] plan 模式拒绝写 / 执行时的说明:带指引,提醒被压缩掉后模型也能从拒绝结果里恢复。 */
46
+ export declare const PLAN_MODE_MESSAGE = "Plan mode is active: write/execute tools are disabled. Finish the plan with a <proposed_plan> block.";
47
+ /** [W5-F] plan 下 `todo set / update` 的拒绝说明(计划与清单分开,避免跳过审批)。 */
48
+ export declare const PLAN_TODO_MESSAGE = "Plan mode is active: the todo list is created from the approved plan. Write the steps in a <proposed_plan> block instead.";
49
+ /**
50
+ * [W5-F] plan 模式第 ③ 步(输入可见,docs/wave5-plan.md §6.2):
51
+ * read 放行(`todo` 只放行 get);bash 按 `plan.bash`(readonly:只读子集放行其余拒绝;ask:其余询问;
52
+ * deny:全拒);task 放行(子会话共用同一管线,同样处在 plan);其余 write / execute 拒绝。
53
+ * 返回 undefined = 交给通常的模式真值表。
54
+ */
55
+ export declare function planDecision(input: Pick<PermissionCheckInput, "toolName" | "permission" | "input">, planBash: PlanBashMode, cwd: string, projectRoot?: string): {
56
+ decision: Decision;
57
+ message?: string;
58
+ };
36
59
  export declare const UNATTENDED_MESSAGE = "This tool call requires approval, but no one is available to approve it (unattended mode).";
37
60
  export declare const ALLOWLIST_MESSAGE = "Not in the allowlist (\u4E0D\u5728\u5141\u8BB8\u540D\u5355): permission mode \"allowlist\" only runs read-only tools and calls matched by an allow rule, and never asks.";
38
61
  interface SessionGrant {
@@ -49,6 +72,10 @@ export interface PermissionPipelineOptions {
49
72
  projectRoot?: string;
50
73
  /** auto:安全名单追加(`permission.autoSafeCommands`)。 */
51
74
  autoSafeCommands?: readonly string[];
75
+ /** [W5-F] plan 模式下的 bash(config `plan.bash`),缺省 readonly。 */
76
+ planBash?: PlanBashMode;
77
+ /** [S2] bash 的 OS 沙箱设定(与 bash 工具用同一份,保证「会不会在沙箱里跑」两边结论一致)。 */
78
+ bashSandbox?: BashSandbox;
52
79
  }
53
80
  export declare class PermissionPipeline implements PermissionPipelineApi {
54
81
  private currentMode;
@@ -58,16 +85,28 @@ export declare class PermissionPipeline implements PermissionPipelineApi {
58
85
  readonly cwd: string;
59
86
  readonly projectRoot: string;
60
87
  readonly autoSafeCommands: readonly string[];
88
+ private planBashMode;
89
+ readonly bashSandbox: BashSandbox | undefined;
61
90
  constructor(options: PermissionPipelineOptions);
62
91
  get mode(): PermissionMode;
63
92
  setMode(mode: PermissionMode): void;
93
+ /** [W5-F] config `plan.bash`(plan 扩展在会话装配时设置)。 */
94
+ get planBash(): PlanBashMode;
95
+ setPlanBash(mode: PlanBashMode): void;
64
96
  get rules(): readonly Rule[];
65
97
  rememberForSession(toolName: string, input: unknown): void;
66
98
  clearSessionGrants(): void;
67
99
  recordAutoDecision(toolName: string, input: unknown, decision: AutoDecision): void;
68
100
  autoDecisions(): readonly AutoAuditEntry[];
69
- check(input: PermissionCheckInput): PermissionVerdict;
101
+ check(request: PermissionCheckInput): PermissionVerdict;
70
102
  private evaluate;
103
+ /**
104
+ * [S2] default / auto-edit 的「沙箱内免审批」:将在沙箱内运行、沙箱拒绝网络(联网可外带数据)、
105
+ * 命令文本不碰机密路径(沙箱挡不住读工作区里的 `.env`)、嵌套不超深。
106
+ */
107
+ private sandboxAllows;
108
+ /** [S2] 沙箱生效时请求 `sandbox: false` 越出沙箱。 */
109
+ private escapesSandbox;
71
110
  /** allow 规则(每层都覆盖)/ Hook allow / 会话记忆;都不命中返回 undefined。 */
72
111
  private allowedBy;
73
112
  /** auto 规则层(受保护路径、项目外写入、网络、删除类)要询问的原因。 */
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * ① deny 规则 ∪ Hook deny → deny
5
5
  * ② 危险命令(仅 bash) → ask(无人值守 deny),之后各步不能放宽
6
- * ③ 模式:plan read 允许,write / execute deny
6
+ * ③ 模式:plan read 允许,write / execute deny([W5-F] 细化见 {@link planDecision}:只读 bash、
7
+ * task 放行,`todo set / update` 拒绝)
7
8
  * default read 允许,write / execute 问
8
9
  * auto-edit read / write 允许,execute 问
9
10
  * full-auto 全部允许
@@ -16,20 +17,29 @@
16
17
  * 安全名单里的 bash)→ allow;都没决定 → ask 且 `classify: true`,由调用方问模型分类器(分类器只能
17
18
  * 把它变 allow)。每个结论带 `auto{layer, decision, reason}`。
18
19
  *
19
- * allowlist:①② 同上;只读工具与 allow 规则 / Hook allow 命中放行,其余 deny——从不询问(危险命令、
20
- * Hook ask 也 deny)。
20
+ * allowlist:①② 同上;只读工具、只读 bash([W5-F] 与 plan 同一子集,保持 plan ⊆ allowlist)与
21
+ * allow 规则 / Hook allow 命中放行,task 同 plan 放行(子会话共用本管线),其余 deny——从不询问
22
+ * (危险命令、Hook ask 也 deny)。
21
23
  *
22
24
  * allow_session 记忆(内存,不落盘):bash 记命令的前两个词(`npm test`),之后以它开头的命令
23
25
  * 命中;带 path 的工具记文件所在目录,之后该目录下的路径命中;其它工具只记工具名。
24
26
  * 记忆只作用于第 ③ 步模式产生的 ask(auto 里只作用于规则层之后)。
25
27
  *
28
+ * [S2] bash 沙箱(docs/permissions.md「判定顺序」):default / auto-edit 下,bash 调用若将在 OS 沙箱内
29
+ * 运行且沙箱拒绝网络、没请求 `sandbox: false`、命令文本不碰机密路径、嵌套不超深,第 ③ 步的 ask 变
30
+ * allow(`sandboxed: true`);①② 与 Hook ask 照旧优先。auto 下沙箱不直接放行,只作为分类器输入;
31
+ * 请求 `sandbox: false` 越出沙箱时规则层询问(allow 规则 / Hook allow / 会话记忆仍可放行)。
32
+ *
26
33
  * 包装里的命令(`sh -c '…'`、`eval`、`xargs`、`find -exec`,见 dangerous.ts)逐层核对:deny 规则命中
27
34
  * 任一层即 deny;allow 规则与会话记忆要求外层与每一层嵌套命令都被覆盖,嵌套超深时不放行。
28
35
  */
29
36
  import { dirname, sep } from "node:path";
37
+ import { isBackgroundJobQuery } from "../tools/background-jobs.js";
30
38
  import { findAllowRule, findDenyRule, inputCommand, inputPath, normalizeCommand, splitShellSegments, } from "./rules.js";
31
39
  import { collectNestedCommands, matchDangerous } from "./dangerous.js";
32
- import { analyzeBashForAuto } from "./auto-safe.js";
40
+ import { analyzeBashForAuto, secretReferenceReason } from "./auto-safe.js";
41
+ import { runsSandboxed, wantsUnsandboxed } from "../sandbox/bash.js";
42
+ import { isReadonlyBash } from "./readonly-bash.js";
33
43
  import { secretPathReason, writeProtectionReason } from "./protected.js";
34
44
  import { AUTO_AUDIT_LIMIT } from "./types.js";
35
45
  /** bash 调用拆成「外层 + 每层嵌套命令」各自的输入;嵌套超深时 tooDeep 为真。非 bash 原样返回。 */
@@ -56,6 +66,42 @@ export function modeDecision(mode, permission) {
56
66
  return permission === "write" ? "allow" : "ask";
57
67
  return "ask";
58
68
  }
69
+ /** [W5-F] plan 模式拒绝写 / 执行时的说明:带指引,提醒被压缩掉后模型也能从拒绝结果里恢复。 */
70
+ export const PLAN_MODE_MESSAGE = "Plan mode is active: write/execute tools are disabled. Finish the plan with a <proposed_plan> block.";
71
+ /** [W5-F] plan 下 `todo set / update` 的拒绝说明(计划与清单分开,避免跳过审批)。 */
72
+ export const PLAN_TODO_MESSAGE = "Plan mode is active: the todo list is created from the approved plan. Write the steps in a <proposed_plan> block instead.";
73
+ /**
74
+ * [W5-F] plan 模式第 ③ 步(输入可见,docs/wave5-plan.md §6.2):
75
+ * read 放行(`todo` 只放行 get);bash 按 `plan.bash`(readonly:只读子集放行其余拒绝;ask:其余询问;
76
+ * deny:全拒);task 放行(子会话共用同一管线,同样处在 plan);其余 write / execute 拒绝。
77
+ * 返回 undefined = 交给通常的模式真值表。
78
+ */
79
+ export function planDecision(input, planBash, cwd, projectRoot = cwd) {
80
+ const { toolName, permission } = input;
81
+ if (toolName === "todo") {
82
+ const action = typeof input.input === "object" && input.input !== null
83
+ ? input.input["action"]
84
+ : undefined;
85
+ return action === "get"
86
+ ? { decision: "allow" }
87
+ : { decision: "deny", message: PLAN_TODO_MESSAGE };
88
+ }
89
+ if (permission === "read")
90
+ return { decision: "allow" };
91
+ if (toolName === "task")
92
+ return { decision: "allow" };
93
+ if (toolName === "bash") {
94
+ if (planBash === "deny")
95
+ return { decision: "deny", message: PLAN_MODE_MESSAGE };
96
+ const command = inputCommand(input.input);
97
+ if (command !== undefined && isReadonlyBash(command, { cwd, projectRoot }))
98
+ return { decision: "allow" };
99
+ return planBash === "ask"
100
+ ? { decision: "ask" }
101
+ : { decision: "deny", message: PLAN_MODE_MESSAGE };
102
+ }
103
+ return { decision: "deny", message: PLAN_MODE_MESSAGE };
104
+ }
59
105
  export const UNATTENDED_MESSAGE = "This tool call requires approval, but no one is available to approve it (unattended mode).";
60
106
  export const ALLOWLIST_MESSAGE = 'Not in the allowlist (不在允许名单): permission mode "allowlist" only runs read-only tools and calls matched by an allow rule, and never asks.';
61
107
  /** 审计摘要:bash 取命令,带 path 的取路径,其它取工具名;单行、截断。 */
@@ -103,8 +149,12 @@ export class PermissionPipeline {
103
149
  cwd;
104
150
  projectRoot;
105
151
  autoSafeCommands;
152
+ planBashMode;
153
+ bashSandbox;
106
154
  constructor(options) {
155
+ this.bashSandbox = options.bashSandbox;
107
156
  this.currentMode = options.mode;
157
+ this.planBashMode = options.planBash ?? "readonly";
108
158
  this.ruleList = [...options.rules];
109
159
  this.cwd = options.cwd;
110
160
  this.projectRoot = options.projectRoot ?? options.cwd;
@@ -116,6 +166,13 @@ export class PermissionPipeline {
116
166
  setMode(mode) {
117
167
  this.currentMode = mode;
118
168
  }
169
+ /** [W5-F] config `plan.bash`(plan 扩展在会话装配时设置)。 */
170
+ get planBash() {
171
+ return this.planBashMode;
172
+ }
173
+ setPlanBash(mode) {
174
+ this.planBashMode = mode;
175
+ }
119
176
  get rules() {
120
177
  return this.ruleList;
121
178
  }
@@ -138,7 +195,11 @@ export class PermissionPipeline {
138
195
  autoDecisions() {
139
196
  return [...this.audit];
140
197
  }
141
- check(input) {
198
+ check(request) {
199
+ // [W5-H2] 后台 bash 的查询(bash{job, action},不带 command)只读 / 结束本会话自己启动的任务:按 read
200
+ const input = isBackgroundJobQuery(request.toolName, request.input)
201
+ ? { ...request, permission: "read" }
202
+ : request;
142
203
  const verdict = this.evaluate(input);
143
204
  if (this.currentMode === "auto" && verdict.auto === undefined) {
144
205
  verdict.auto = {
@@ -200,13 +261,16 @@ export class PermissionPipeline {
200
261
  return this.evaluateAuto(input, layers, command);
201
262
  if (this.currentMode === "allowlist")
202
263
  return this.evaluateAllowlist(input, layers);
203
- // ③ 模式
204
- const byMode = modeDecision(this.currentMode, permission);
264
+ // ③ 模式(plan 细化见 planDecision)
265
+ const plan = this.currentMode === "plan"
266
+ ? planDecision(input, this.planBashMode, this.cwd, this.projectRoot)
267
+ : undefined;
268
+ const byMode = plan?.decision ?? modeDecision(this.currentMode, permission);
205
269
  if (byMode === "deny") {
206
270
  return {
207
271
  decision: "deny",
208
272
  step: "mode",
209
- message: `Permission mode "${this.currentMode}" allows only read-only tools`,
273
+ message: plan?.message ?? PLAN_MODE_MESSAGE,
210
274
  };
211
275
  }
212
276
  let verdict = byMode === "allow"
@@ -228,6 +292,10 @@ export class PermissionPipeline {
228
292
  else if (granted) {
229
293
  verdict = { decision: "allow", step: "session" };
230
294
  }
295
+ else if ((this.currentMode === "default" || this.currentMode === "auto-edit") &&
296
+ this.sandboxAllows(input, command, layers.tooDeep)) {
297
+ verdict = { decision: "allow", step: "mode", sandboxed: true };
298
+ }
231
299
  }
232
300
  if (input.hookDecision === "ask") {
233
301
  const reason = "hook";
@@ -240,6 +308,25 @@ export class PermissionPipeline {
240
308
  }
241
309
  return verdict;
242
310
  }
311
+ /**
312
+ * [S2] default / auto-edit 的「沙箱内免审批」:将在沙箱内运行、沙箱拒绝网络(联网可外带数据)、
313
+ * 命令文本不碰机密路径(沙箱挡不住读工作区里的 `.env`)、嵌套不超深。
314
+ */
315
+ sandboxAllows(input, command, tooDeep) {
316
+ if (input.toolName !== "bash" || command === undefined || tooDeep)
317
+ return false;
318
+ if (!runsSandboxed(this.bashSandbox, input.input))
319
+ return false;
320
+ if (this.bashSandbox?.network !== "deny")
321
+ return false;
322
+ return secretReferenceReason(command, this.cwd) === undefined;
323
+ }
324
+ /** [S2] 沙箱生效时请求 `sandbox: false` 越出沙箱。 */
325
+ escapesSandbox(input) {
326
+ return (input.toolName === "bash" &&
327
+ this.bashSandbox?.active === true &&
328
+ wantsUnsandboxed(input.input));
329
+ }
243
330
  /** allow 规则(每层都覆盖)/ Hook allow / 会话记忆;都不命中返回 undefined。 */
244
331
  allowedBy(input, layers, useGrants) {
245
332
  const { toolName } = input;
@@ -329,6 +416,16 @@ export class PermissionPipeline {
329
416
  : "allowed for this session";
330
417
  return { ...allowed, auto: rule("allow", reason) };
331
418
  }
419
+ if (this.escapesSandbox(input)) {
420
+ const why = "runs outside the OS sandbox (sandbox:false)";
421
+ return {
422
+ decision: "ask",
423
+ step: "auto-rule",
424
+ approvalReason: "mode",
425
+ message: `Auto mode asks before this: ${why}`,
426
+ auto: rule("ask", why),
427
+ };
428
+ }
332
429
  const fixed = (reason) => ({
333
430
  decision: "allow",
334
431
  step: "auto-static",
@@ -349,6 +446,9 @@ export class PermissionPipeline {
349
446
  step: "auto-classify",
350
447
  approvalReason: "mode",
351
448
  classify: true,
449
+ ...(command !== undefined && runsSandboxed(this.bashSandbox, input.input)
450
+ ? { sandboxed: true }
451
+ : {}),
352
452
  auto: {
353
453
  layer: "classifier",
354
454
  decision: "ask",
@@ -364,7 +464,12 @@ export class PermissionPipeline {
364
464
  });
365
465
  if (input.hookDecision === "ask")
366
466
  return deny(input.hookReason);
367
- if (input.permission === "read")
467
+ // task 与 plan 同样放行:子会话共用本管线,同样只放行只读与名单内调用
468
+ if (input.permission === "read" || input.toolName === "task")
469
+ return { decision: "allow", step: "mode" };
470
+ const command = input.toolName === "bash" ? inputCommand(input.input) : undefined;
471
+ if (command !== undefined &&
472
+ isReadonlyBash(command, { cwd: this.cwd, projectRoot: this.projectRoot }))
368
473
  return { decision: "allow", step: "mode" };
369
474
  return this.allowedBy(input, layers, false) ?? deny();
370
475
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * plan 模式与 allowlist 放行的只读 bash 子集 `READONLY_BASH`(docs/wave5-plan.md §6.2、D21)。[W5-F]
3
+ *
4
+ * 判定 = auto 的静态判定(`analyzeBashForAuto`:分词、嵌套展开、规则层的网络 / 删除 / 写入目标 /
5
+ * 机密路径、命令替换、变量展开、点文件通配)**全部通过**,再加四条更窄的限制:
6
+ * - 每段命令都命中 {@link READONLY_BASH}(比 auto 安全名单窄:去掉测试 / 构建运行器、`printenv`
7
+ * 这类会把环境变量里的密钥打进上下文的命令);
8
+ * - 没有输出重定向(`/dev/null` 等设备除外);
9
+ * - 没有嵌套 shell(`sh -c`、`eval`、`xargs`、`find -exec`)与进程替换;
10
+ * - 段首没有环境赋值或包装命令(`GIT_EXTERNAL_DIFF=… git diff`、`env …`、`nohup …`)。
11
+ *
12
+ * 机密路径(`cat .env`)由 auto 规则层判为「要询问」,这里因此不算只读;deny 规则仍先于模式判定。
13
+ */
14
+ export interface ReadonlySpec {
15
+ /** 词前缀(`["git", "log"]`);git 的全局选项只允许 `-C <dir>` 与 `--no-pager`。 */
16
+ words: readonly string[];
17
+ /** 前缀之后任一参数命中即不算只读。 */
18
+ forbid?: RegExp;
19
+ }
20
+ /** 只读命令名单(每条在 readonly-bash.test.ts 有正反例)。 */
21
+ export declare const READONLY_BASH: readonly ReadonlySpec[];
22
+ export interface ReadonlyBashOptions {
23
+ /** 相对路径(机密路径、写入目标)的解析基准;缺省 `process.cwd()`。 */
24
+ cwd?: string;
25
+ projectRoot?: string;
26
+ }
27
+ /** 不是只读的原因;只读返回 undefined。 */
28
+ export declare function readonlyBashReason(command: string, options?: ReadonlyBashOptions): string | undefined;
29
+ /** 每段命令都在 {@link READONLY_BASH} 且无重定向 / 命令替换 / 嵌套 shell。 */
30
+ export declare function isReadonlyBash(command: string, options?: ReadonlyBashOptions): boolean;