@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.2

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 (387) hide show
  1. package/README.md +13 -10
  2. package/dist/agent/agent-definition-registry.d.ts +3 -0
  3. package/dist/agent/agent-definition-registry.js +3 -1
  4. package/dist/agent/agent-definition.d.ts +15 -0
  5. package/dist/agent/agent-definition.js +27 -3
  6. package/dist/arena/context/context-tools.js +47 -4
  7. package/dist/arena/ledger.js +9 -1
  8. package/dist/arena/strategies/utils.d.ts +1 -4
  9. package/dist/arena/strategies/utils.js +7 -84
  10. package/dist/automation/cron-expr.d.ts +10 -0
  11. package/dist/automation/cron-expr.js +23 -5
  12. package/dist/automation/runner.d.ts +21 -4
  13. package/dist/automation/runner.js +48 -12
  14. package/dist/automation/scheduler.d.ts +38 -0
  15. package/dist/automation/scheduler.js +89 -12
  16. package/dist/automation/store.js +5 -2
  17. package/dist/capability-control/disabled-lists.d.ts +25 -0
  18. package/dist/capability-control/disabled-lists.js +57 -0
  19. package/dist/capability-control/overlay.d.ts +15 -0
  20. package/dist/capability-control/overlay.js +27 -0
  21. package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
  22. package/dist/cc-orchestrator/agent-adapter.js +133 -0
  23. package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
  24. package/dist/cc-orchestrator/cc-capability.js +53 -0
  25. package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
  26. package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
  27. package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
  28. package/dist/cc-orchestrator/codex-session-history.js +187 -0
  29. package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
  30. package/dist/cc-orchestrator/external-agent-changes.js +214 -0
  31. package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
  32. package/dist/cc-orchestrator/external-agent-driver.js +69 -0
  33. package/dist/cc-orchestrator/index.d.ts +8 -0
  34. package/dist/cc-orchestrator/index.js +8 -0
  35. package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
  36. package/dist/cc-orchestrator/relevance-judge.js +29 -0
  37. package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
  38. package/dist/cc-orchestrator/session-discovery.js +125 -0
  39. package/dist/cc-orchestrator/session-history.d.ts +19 -0
  40. package/dist/cc-orchestrator/session-history.js +67 -0
  41. package/dist/cli/agent-server-stdio.d.ts +15 -1
  42. package/dist/cli/agent-server-stdio.js +115 -17
  43. package/dist/cli/agent-server-tcp.js +40 -26
  44. package/dist/context/compaction.d.ts +56 -0
  45. package/dist/context/compaction.js +186 -0
  46. package/dist/context/manager.js +25 -4
  47. package/dist/cost-tracker.js +5 -61
  48. package/dist/credentials/cipher.d.ts +49 -0
  49. package/dist/credentials/cipher.js +45 -0
  50. package/dist/credentials/cookie-jar.d.ts +24 -0
  51. package/dist/credentials/cookie-jar.js +40 -0
  52. package/dist/credentials/index.d.ts +7 -0
  53. package/dist/credentials/index.js +5 -0
  54. package/dist/credentials/inject-credential-tool.d.ts +20 -0
  55. package/dist/credentials/inject-credential-tool.js +130 -0
  56. package/dist/credentials/store.d.ts +72 -0
  57. package/dist/credentials/store.js +184 -0
  58. package/dist/credentials/types.d.ts +56 -0
  59. package/dist/credentials/use-credential-tool.d.ts +29 -0
  60. package/dist/credentials/use-credential-tool.js +205 -0
  61. package/dist/credentials/use-gate.d.ts +56 -0
  62. package/dist/credentials/use-gate.js +52 -0
  63. package/dist/data/model-metadata.d.ts +77 -0
  64. package/dist/data/model-metadata.js +56 -0
  65. package/dist/data/model-metadata.json +216 -0
  66. package/dist/data/openrouter-models.d.ts +18 -7
  67. package/dist/data/openrouter-models.js +35 -8
  68. package/dist/engine/aux-key.d.ts +10 -0
  69. package/dist/engine/aux-key.js +11 -0
  70. package/dist/engine/dynamic-tool-defs.d.ts +19 -0
  71. package/dist/engine/dynamic-tool-defs.js +36 -0
  72. package/dist/engine/engine.d.ts +267 -138
  73. package/dist/engine/engine.js +957 -191
  74. package/dist/engine/friendly-error.d.ts +18 -0
  75. package/dist/engine/friendly-error.js +63 -0
  76. package/dist/engine/goal.d.ts +145 -0
  77. package/dist/engine/goal.js +144 -0
  78. package/dist/engine/image-policy.d.ts +13 -0
  79. package/dist/engine/image-policy.js +24 -0
  80. package/dist/engine/model-connections-pool.d.ts +17 -0
  81. package/dist/engine/model-connections-pool.js +66 -0
  82. package/dist/engine/model-facade.d.ts +10 -0
  83. package/dist/engine/model-facade.js +25 -0
  84. package/dist/engine/patch-orphaned-tools.js +3 -0
  85. package/dist/engine/resolve-llm-config.d.ts +16 -0
  86. package/dist/engine/resolve-llm-config.js +44 -0
  87. package/dist/engine/runtime.js +2 -1
  88. package/dist/engine/sandbox-cache-key.d.ts +10 -0
  89. package/dist/engine/sandbox-cache-key.js +9 -0
  90. package/dist/engine/sandbox-config.d.ts +31 -0
  91. package/dist/engine/sandbox-config.js +38 -0
  92. package/dist/engine/session-usage.d.ts +19 -0
  93. package/dist/engine/session-usage.js +25 -0
  94. package/dist/engine/steer-queue.d.ts +32 -0
  95. package/dist/engine/steer-queue.js +26 -0
  96. package/dist/engine/streaming-tool-queue.d.ts +12 -0
  97. package/dist/engine/streaming-tool-queue.js +50 -10
  98. package/dist/engine/turn-loop.d.ts +67 -3
  99. package/dist/engine/turn-loop.js +292 -41
  100. package/dist/engine/types.d.ts +175 -0
  101. package/dist/engine/types.js +13 -0
  102. package/dist/external-agents/config.d.ts +2 -0
  103. package/dist/external-agents/config.js +15 -0
  104. package/dist/external-agents/types.d.ts +31 -0
  105. package/dist/external-agents/types.js +1 -0
  106. package/dist/git/utils.js +11 -6
  107. package/dist/git/worktree.d.ts +48 -0
  108. package/dist/git/worktree.js +86 -10
  109. package/dist/hooks/goal-stop-hook.d.ts +28 -0
  110. package/dist/hooks/goal-stop-hook.js +186 -9
  111. package/dist/hooks/registry.d.ts +8 -0
  112. package/dist/hooks/registry.js +19 -0
  113. package/dist/hooks/shell-runner.js +10 -21
  114. package/dist/index.d.ts +49 -6
  115. package/dist/index.js +60 -4
  116. package/dist/llm/capabilities/param-specs.d.ts +14 -0
  117. package/dist/llm/capabilities/param-specs.js +62 -0
  118. package/dist/llm/capabilities/rules.js +4 -0
  119. package/dist/llm/capabilities/types.d.ts +10 -0
  120. package/dist/llm/client-base.d.ts +28 -1
  121. package/dist/llm/client-base.js +119 -13
  122. package/dist/llm/model-cache.js +4 -2
  123. package/dist/llm/model-pool.d.ts +14 -0
  124. package/dist/llm/model-pool.js +13 -0
  125. package/dist/llm/provider-auth.d.ts +41 -0
  126. package/dist/llm/provider-auth.js +76 -0
  127. package/dist/llm/provider-catalog.d.ts +4 -0
  128. package/dist/llm/providers/anthropic.js +64 -14
  129. package/dist/llm/providers/openai.d.ts +36 -0
  130. package/dist/llm/providers/openai.js +257 -51
  131. package/dist/llm/reasoning-setting.d.ts +3 -3
  132. package/dist/llm/reasoning-setting.js +9 -1
  133. package/dist/llm/stream-watchdog.js +5 -1
  134. package/dist/llm/token-counter.js +9 -2
  135. package/dist/llm/types.d.ts +4 -0
  136. package/dist/logging/sanitize-messages.js +22 -0
  137. package/dist/lsp/manager.d.ts +1 -1
  138. package/dist/lsp/manager.js +40 -10
  139. package/dist/model-catalog/builtin.d.ts +12 -0
  140. package/dist/model-catalog/builtin.js +407 -0
  141. package/dist/model-catalog/gen-connections.d.ts +20 -0
  142. package/dist/model-catalog/gen-connections.js +28 -0
  143. package/dist/model-catalog/index.d.ts +41 -0
  144. package/dist/model-catalog/index.js +90 -0
  145. package/dist/model-catalog/params.d.ts +20 -0
  146. package/dist/model-catalog/params.js +45 -0
  147. package/dist/model-catalog/resolve.d.ts +48 -0
  148. package/dist/model-catalog/resolve.js +33 -0
  149. package/dist/model-catalog/save-entry.d.ts +32 -0
  150. package/dist/model-catalog/save-entry.js +104 -0
  151. package/dist/model-catalog/types.d.ts +561 -0
  152. package/dist/model-catalog/types.js +93 -0
  153. package/dist/model-catalog/upsert.d.ts +9 -0
  154. package/dist/model-catalog/upsert.js +8 -0
  155. package/dist/onboarding.d.ts +12 -82
  156. package/dist/onboarding.js +61 -326
  157. package/dist/plugins/gitOps.d.ts +19 -0
  158. package/dist/plugins/gitOps.js +73 -4
  159. package/dist/plugins/installer/checkUpdate.d.ts +16 -0
  160. package/dist/plugins/installer/checkUpdate.js +32 -0
  161. package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
  162. package/dist/plugins/installer/codex/convertCommands.js +46 -0
  163. package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
  164. package/dist/plugins/installer/codex/convertMcp.js +45 -5
  165. package/dist/plugins/installer/install.js +25 -1
  166. package/dist/plugins/installer/installFromArchive.d.ts +43 -0
  167. package/dist/plugins/installer/installFromArchive.js +134 -0
  168. package/dist/plugins/installer/installFromSource.js +8 -2
  169. package/dist/plugins/installer/loadPluginAgents.js +6 -2
  170. package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
  171. package/dist/plugins/installer/loadPluginMcp.js +35 -2
  172. package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
  173. package/dist/plugins/installer/pruneDisabled.js +73 -0
  174. package/dist/plugins/installer/types.d.ts +5 -2
  175. package/dist/plugins/installer/types.js +1 -0
  176. package/dist/plugins/installer/uninstall.js +4 -0
  177. package/dist/plugins/installer/unzip.d.ts +14 -0
  178. package/dist/plugins/installer/unzip.js +82 -0
  179. package/dist/plugins/installer/update.d.ts +14 -0
  180. package/dist/plugins/installer/update.js +53 -21
  181. package/dist/plugins/loadPluginHooks.d.ts +47 -1
  182. package/dist/plugins/loadPluginHooks.js +73 -1
  183. package/dist/plugins/marketplaceManager.d.ts +7 -0
  184. package/dist/plugins/marketplaceManager.js +20 -0
  185. package/dist/plugins/pluginCommandHook.js +4 -18
  186. package/dist/plugins/pluginContent.d.ts +30 -0
  187. package/dist/plugins/pluginContent.js +83 -0
  188. package/dist/plugins/pluginInstaller.d.ts +13 -0
  189. package/dist/plugins/pluginInstaller.js +42 -8
  190. package/dist/plugins/schemas.js +1 -0
  191. package/dist/plugins/types.d.ts +6 -0
  192. package/dist/preset/index.d.ts +7 -1
  193. package/dist/preset/index.js +96 -6
  194. package/dist/product/types.d.ts +1 -1
  195. package/dist/prompt/composer.d.ts +27 -0
  196. package/dist/prompt/composer.js +67 -21
  197. package/dist/prompt/instruction-scanner.js +5 -3
  198. package/dist/prompt/section-loader.js +1 -0
  199. package/dist/prompt/sections/base.md +1 -0
  200. package/dist/prompt/sections/browser.md +10 -0
  201. package/dist/prompt/sections/coding.md +4 -0
  202. package/dist/protocol/chat-session-manager.d.ts +8 -2
  203. package/dist/protocol/chat-session-manager.js +36 -0
  204. package/dist/protocol/chat-session.d.ts +65 -0
  205. package/dist/protocol/chat-session.js +94 -2
  206. package/dist/protocol/client.d.ts +27 -0
  207. package/dist/protocol/client.js +38 -0
  208. package/dist/protocol/server.d.ts +112 -0
  209. package/dist/protocol/server.js +586 -16
  210. package/dist/protocol/transport.js +3 -2
  211. package/dist/protocol/types.d.ts +46 -0
  212. package/dist/protocol/types.js +16 -0
  213. package/dist/quota/credentials.d.ts +3 -0
  214. package/dist/quota/credentials.js +80 -0
  215. package/dist/quota/index.d.ts +36 -0
  216. package/dist/quota/index.js +155 -0
  217. package/dist/quota/types.d.ts +48 -0
  218. package/dist/quota/types.js +13 -0
  219. package/dist/review/review-prompt.d.ts +28 -0
  220. package/dist/review/review-prompt.js +81 -0
  221. package/dist/run/FileRunStore.js +8 -3
  222. package/dist/run/RunApprovalBackend.js +25 -5
  223. package/dist/run/RunManager.d.ts +12 -0
  224. package/dist/run/RunManager.js +35 -0
  225. package/dist/run/factory.d.ts +1 -1
  226. package/dist/runtime/background-shell.d.ts +139 -0
  227. package/dist/runtime/background-shell.js +495 -0
  228. package/dist/runtime/output-clean.d.ts +24 -0
  229. package/dist/runtime/output-clean.js +41 -0
  230. package/dist/runtime/ring-file.d.ts +64 -0
  231. package/dist/runtime/ring-file.js +174 -0
  232. package/dist/runtime/safe-spawn.js +15 -34
  233. package/dist/runtime/spawn-common.d.ts +148 -0
  234. package/dist/runtime/spawn-common.js +303 -0
  235. package/dist/runtime/truncate-output.d.ts +22 -0
  236. package/dist/runtime/truncate-output.js +49 -0
  237. package/dist/runtime/utf8-cut.d.ts +11 -0
  238. package/dist/runtime/utf8-cut.js +23 -0
  239. package/dist/services/auto-dream.d.ts +4 -0
  240. package/dist/services/auto-dream.js +26 -26
  241. package/dist/services/diagnostics.d.ts +1 -2
  242. package/dist/services/diagnostics.js +12 -7
  243. package/dist/services/extract-memories.d.ts +14 -1
  244. package/dist/services/extract-memories.js +45 -6
  245. package/dist/services/memory-orchestrator.d.ts +21 -0
  246. package/dist/services/memory-orchestrator.js +125 -50
  247. package/dist/services/session-memory.js +24 -12
  248. package/dist/session/file-history.d.ts +124 -1
  249. package/dist/session/file-history.js +222 -6
  250. package/dist/session/memory.d.ts +116 -2
  251. package/dist/session/memory.js +250 -28
  252. package/dist/session/session-manager.d.ts +40 -0
  253. package/dist/session/session-manager.js +113 -3
  254. package/dist/session/simple-diff.d.ts +23 -0
  255. package/dist/session/simple-diff.js +84 -0
  256. package/dist/session/transcript.d.ts +25 -1
  257. package/dist/session/transcript.js +35 -2
  258. package/dist/session/undo-target.d.ts +67 -0
  259. package/dist/session/undo-target.js +144 -0
  260. package/dist/settings/disk-defaults.d.ts +9 -2
  261. package/dist/settings/disk-defaults.js +11 -2
  262. package/dist/settings/feature-flags.d.ts +64 -0
  263. package/dist/settings/feature-flags.js +61 -0
  264. package/dist/settings/manager.d.ts +68 -1
  265. package/dist/settings/manager.js +266 -29
  266. package/dist/settings/migrate-config.d.ts +45 -0
  267. package/dist/settings/migrate-config.js +125 -0
  268. package/dist/settings/schema-export.d.ts +25 -0
  269. package/dist/settings/schema-export.js +38 -0
  270. package/dist/settings/schema.d.ts +1186 -771
  271. package/dist/settings/schema.js +268 -97
  272. package/dist/skills/scanner.d.ts +9 -0
  273. package/dist/skills/scanner.js +30 -2
  274. package/dist/stt/resolve-transcribe.d.ts +31 -0
  275. package/dist/stt/resolve-transcribe.js +108 -0
  276. package/dist/stt/transcribe.d.ts +51 -0
  277. package/dist/stt/transcribe.js +65 -0
  278. package/dist/tool-system/browser-bridge.d.ts +226 -0
  279. package/dist/tool-system/browser-bridge.js +163 -0
  280. package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
  281. package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
  282. package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
  283. package/dist/tool-system/builtin/agent-notifications.js +9 -3
  284. package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
  285. package/dist/tool-system/builtin/agent-output-file.js +72 -0
  286. package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
  287. package/dist/tool-system/builtin/agent-registry.js +8 -0
  288. package/dist/tool-system/builtin/agent.d.ts +21 -1
  289. package/dist/tool-system/builtin/agent.js +489 -42
  290. package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
  291. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
  292. package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
  293. package/dist/tool-system/builtin/apply-patch/index.js +0 -15
  294. package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
  295. package/dist/tool-system/builtin/background-jobs.js +124 -0
  296. package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
  297. package/dist/tool-system/builtin/background-shell-tools.js +108 -0
  298. package/dist/tool-system/builtin/background-work.d.ts +67 -0
  299. package/dist/tool-system/builtin/background-work.js +86 -0
  300. package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
  301. package/dist/tool-system/builtin/bash-output-style.js +40 -0
  302. package/dist/tool-system/builtin/bash.d.ts +5 -2
  303. package/dist/tool-system/builtin/bash.js +98 -62
  304. package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
  305. package/dist/tool-system/builtin/browser-tools.js +312 -0
  306. package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
  307. package/dist/tool-system/builtin/cancel-goal.js +64 -0
  308. package/dist/tool-system/builtin/check-quota.d.ts +15 -0
  309. package/dist/tool-system/builtin/check-quota.js +34 -0
  310. package/dist/tool-system/builtin/config.js +7 -0
  311. package/dist/tool-system/builtin/cron.d.ts +7 -0
  312. package/dist/tool-system/builtin/cron.js +55 -3
  313. package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
  314. package/dist/tool-system/builtin/drive-claude-code.js +157 -0
  315. package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
  316. package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
  317. package/dist/tool-system/builtin/edit.js +20 -16
  318. package/dist/tool-system/builtin/eol.d.ts +29 -0
  319. package/dist/tool-system/builtin/eol.js +37 -0
  320. package/dist/tool-system/builtin/file-cache.d.ts +6 -0
  321. package/dist/tool-system/builtin/file-cache.js +8 -0
  322. package/dist/tool-system/builtin/generate-image.d.ts +35 -0
  323. package/dist/tool-system/builtin/generate-image.js +278 -50
  324. package/dist/tool-system/builtin/generate-video.d.ts +55 -0
  325. package/dist/tool-system/builtin/generate-video.js +361 -0
  326. package/dist/tool-system/builtin/glob.js +0 -7
  327. package/dist/tool-system/builtin/grep.js +6 -8
  328. package/dist/tool-system/builtin/image-providers.d.ts +86 -0
  329. package/dist/tool-system/builtin/image-providers.js +190 -0
  330. package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
  331. package/dist/tool-system/builtin/image-uploader.js +74 -0
  332. package/dist/tool-system/builtin/index.d.ts +7 -1
  333. package/dist/tool-system/builtin/index.js +249 -25
  334. package/dist/tool-system/builtin/mcp-tools.js +23 -3
  335. package/dist/tool-system/builtin/memory.js +45 -7
  336. package/dist/tool-system/builtin/notebook-edit.js +0 -7
  337. package/dist/tool-system/builtin/powershell.js +4 -1
  338. package/dist/tool-system/builtin/read.js +10 -10
  339. package/dist/tool-system/builtin/repl.js +4 -1
  340. package/dist/tool-system/builtin/skill.js +9 -0
  341. package/dist/tool-system/builtin/sleep.js +3 -2
  342. package/dist/tool-system/builtin/tool-search.js +25 -7
  343. package/dist/tool-system/builtin/video-providers.d.ts +154 -0
  344. package/dist/tool-system/builtin/video-providers.js +235 -0
  345. package/dist/tool-system/builtin/web-fetch.js +12 -2
  346. package/dist/tool-system/builtin/web-search.js +21 -7
  347. package/dist/tool-system/builtin/worktree.d.ts +2 -1
  348. package/dist/tool-system/builtin/worktree.js +31 -4
  349. package/dist/tool-system/builtin/write.js +0 -6
  350. package/dist/tool-system/context.d.ts +154 -6
  351. package/dist/tool-system/executor.d.ts +3 -1
  352. package/dist/tool-system/executor.js +172 -88
  353. package/dist/tool-system/investigation-guard.js +1 -1
  354. package/dist/tool-system/mcp-manager.d.ts +42 -4
  355. package/dist/tool-system/mcp-manager.js +186 -20
  356. package/dist/tool-system/path-policy.d.ts +5 -0
  357. package/dist/tool-system/path-policy.js +307 -8
  358. package/dist/tool-system/permission.d.ts +36 -1
  359. package/dist/tool-system/permission.js +281 -69
  360. package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
  361. package/dist/tool-system/plan-mode-allowlist.js +24 -2
  362. package/dist/tool-system/registry.d.ts +1 -0
  363. package/dist/tool-system/registry.js +16 -2
  364. package/dist/tool-system/sandbox/index.d.ts +8 -0
  365. package/dist/tool-system/sandbox/index.js +7 -2
  366. package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
  367. package/dist/tool-system/validate-tool-metadata.js +63 -0
  368. package/dist/types.d.ts +217 -6
  369. package/dist/updater.js +20 -9
  370. package/dist/utils/envUtils.d.ts +0 -9
  371. package/dist/utils/envUtils.js +3 -28
  372. package/dist/utils/exec.d.ts +40 -0
  373. package/dist/utils/exec.js +144 -0
  374. package/dist/utils/json.d.ts +12 -0
  375. package/dist/utils/json.js +92 -0
  376. package/dist/utils/theme.d.ts +1 -1
  377. package/dist/utils/theme.js +1 -1
  378. package/dist/utils/toolDisplay.js +0 -1
  379. package/package.json +13 -7
  380. package/dist/agent/coordinator.d.ts +0 -49
  381. package/dist/agent/coordinator.js +0 -77
  382. package/dist/settings/manager.test.js +0 -73
  383. package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
  384. package/dist/tool-system/builtin/remote-trigger.js +0 -54
  385. package/dist/tool-system/builtin/send-message.d.ts +0 -6
  386. package/dist/tool-system/builtin/send-message.js +0 -47
  387. /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * Permission system — classifier + approval backend.
3
3
  */
4
+ import { resolve as resolvePath, dirname } from "node:path";
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, } from "node:fs";
6
+ import { randomUUID } from "node:crypto";
4
7
  import { logger as rootPermLogger } from "../logging/logger.js";
8
+ import { READ_ONLY_TOOLS } from "./plan-mode-allowlist.js";
5
9
  export class HeadlessApprovalBackend {
6
10
  mode;
7
11
  constructor(mode) {
@@ -14,8 +18,7 @@ export class HeadlessApprovalBackend {
14
18
  case "deny-all":
15
19
  return { approved: false, reason: "headless deny-all mode" };
16
20
  case "approve-read-only": {
17
- const readOnlyTools = ["Read", "Glob", "Grep", "WebSearch", "WebFetch", "ToolSearch"];
18
- if (readOnlyTools.includes(req.toolName)) {
21
+ if (READ_ONLY_TOOLS.has(req.toolName)) {
19
22
  return { approved: true };
20
23
  }
21
24
  return { approved: false, reason: "read-only mode: write operations denied" };
@@ -103,7 +106,13 @@ export class AutoApprovalBackend {
103
106
  * the next time the user opens the project.
104
107
  */
105
108
  export class InteractiveApprovalBackend {
106
- sessionRules = new Map();
109
+ // Session-scoped grants, keyed on the OPERATION (tool + narrowed argsPattern
110
+ // via buildProjectRule), NOT just the tool name. Approving `git status` for
111
+ // the session must not auto-allow `rm -rf /` (both are "Bash"). Allow and
112
+ // deny are tracked separately so a one-off deny of `curl evil` never blocks
113
+ // an unrelated `git status`. (TODO 5.1 会话级权限缓存.)
114
+ sessionAllowRules = [];
115
+ sessionDenyRules = [];
107
116
  promptFn = null;
108
117
  cwd = null;
109
118
  // Project rules saved during this session. Kept in-memory (not re-read from
@@ -113,6 +122,13 @@ export class InteractiveApprovalBackend {
113
122
  // the same session. We accumulate here and hand the full list to the callback.
114
123
  savedProjectRules = [];
115
124
  onProjectRules = null;
125
+ // Serialize prompts so a burst of parallel tool calls doesn't queue N
126
+ // identical cards: while one ask is outstanding, later requests wait here
127
+ // and RE-CHECK the session rules when their turn comes — the first
128
+ // "本会话一直允许" answer then silently absorbs the queued duplicates.
129
+ // (All callers were already serialized at the UI anyway — the renderer
130
+ // shows one approval card at a time.)
131
+ promptTurn = Promise.resolve();
116
132
  setPromptFn(fn) {
117
133
  this.promptFn = fn;
118
134
  }
@@ -140,25 +156,75 @@ export class InteractiveApprovalBackend {
140
156
  this.onProjectRules = fn;
141
157
  }
142
158
  async requestApproval(req) {
143
- // Check session rules first
144
- const toolRule = this.sessionRules.get(req.toolName);
145
- if (toolRule === "allow")
146
- return { approved: true };
147
- if (toolRule === "deny")
159
+ // Fast path: the operation may already be covered by a session rule.
160
+ const cached = this.checkSessionRules(req);
161
+ if (cached)
162
+ return cached;
163
+ if (!this.promptFn) {
164
+ return { approved: false, reason: "interactive approval backend has no prompt function" };
165
+ }
166
+ // Serialize prompts and re-check rules when our turn comes (see promptTurn
167
+ // field doc): a burst of parallel tool calls all passes the fast path
168
+ // before the first decision lands; without the re-check the user had to
169
+ // answer one card per duplicate.
170
+ const prevTurn = this.promptTurn;
171
+ let release;
172
+ this.promptTurn = new Promise((r) => (release = r));
173
+ try {
174
+ await prevTurn;
175
+ const nowCached = this.checkSessionRules(req);
176
+ if (nowCached)
177
+ return nowCached;
178
+ return await this.promptAndRecord(req);
179
+ }
180
+ finally {
181
+ release();
182
+ }
183
+ }
184
+ /** Session-rule lookup — operation-scoped (see sessionAllowRules doc). Deny
185
+ * wins over allow if both somehow match (conservative). Null = no rule. */
186
+ checkSessionRules(req) {
187
+ if (this.sessionDenyRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
148
188
  return { approved: false };
189
+ }
190
+ if (this.sessionAllowRules.some((r) => ruleMatches(r, req.toolName, req.args))) {
191
+ return { approved: true };
192
+ }
193
+ return null;
194
+ }
195
+ /** The actual interactive ask + rule recording (runs inside the prompt turn). */
196
+ async promptAndRecord(req) {
149
197
  if (!this.promptFn) {
150
198
  return { approved: false, reason: "interactive approval backend has no prompt function" };
151
199
  }
152
200
  const result = await this.promptFn(req);
153
201
  const scope = result.scope ?? (result.always ? "session" : "once");
202
+ // Path narrowing only rides on an APPROVE: a path-scoped deny is confusing
203
+ // (deny stays tool-wide). pathScope is ignored by buildProjectRule for
204
+ // non-file tools / when absent.
205
+ const ruleOpts = result.approved
206
+ ? { pathScope: result.pathScope, cwd: this.cwd ?? undefined }
207
+ : undefined;
154
208
  if (scope === "session" && result.always) {
155
- this.sessionRules.set(req.toolName, result.approved ? "allow" : "deny");
209
+ // Narrow to the operation (Bash → head command, file tools path scope)
210
+ // so the session grant is scoped, not tool-wide. A rule with no
211
+ // argsPattern keeps the prior tool-granularity behavior.
212
+ const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
213
+ if (rule) {
214
+ const target = result.approved ? this.sessionAllowRules : this.sessionDenyRules;
215
+ const dup = target.some((r) => r.tool === rule.tool &&
216
+ JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
217
+ if (!dup) {
218
+ // buildProjectRule always returns decision:"allow"; flip for deny.
219
+ target.push(result.approved ? rule : { ...rule, decision: "deny" });
220
+ }
221
+ }
156
222
  }
157
223
  else if (scope === "project" && result.approved) {
158
224
  // Project-scope: persist a PermissionRule to settings.local.json.
159
225
  // We only persist allow rules — denies stay session-only because a
160
226
  // persisted deny is harder to recover from than a session deny.
161
- const rule = buildProjectRule(req.toolName, req.args);
227
+ const rule = buildProjectRule(req.toolName, req.args, ruleOpts);
162
228
  if (rule && this.cwd) {
163
229
  try {
164
230
  persistProjectRule(this.cwd, rule);
@@ -189,20 +255,54 @@ export class InteractiveApprovalBackend {
189
255
  console.error("Failed to persist project permission rule:", err.message);
190
256
  }
191
257
  }
192
- // Also seed session map so the rest of this REPL session benefits
193
- // even if the classifier path doesn't pick the rule up immediately.
194
- this.sessionRules.set(req.toolName, "allow");
258
+ // Also seed the session allow list (operation-scoped) so the rest of
259
+ // this REPL session benefits even if the classifier path doesn't pick
260
+ // the rule up immediately.
261
+ if (rule) {
262
+ const dup = this.sessionAllowRules.some((r) => r.tool === rule.tool &&
263
+ JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
264
+ if (!dup)
265
+ this.sessionAllowRules.push(rule);
266
+ }
195
267
  }
196
268
  return result;
197
269
  }
198
270
  }
271
+ /** File tools whose approvals can be narrowed to a path prefix. */
272
+ const PATH_SCOPED_TOOLS = new Set(["Write", "Edit"]);
273
+ /**
274
+ * Build the `file_path` argsPattern that narrows a remembered file-tool grant
275
+ * to a path scope. Pure: takes an ALREADY-RESOLVED absolute path (the caller
276
+ * normalizes against cwd so `../` can't slip past the prefix) and the scope.
277
+ *
278
+ * "file" → `^<esc(abs)>$` only this exact file
279
+ * "dir" → `^<esc(dir(abs))>/` this file's directory + subdirectories
280
+ * "tool" → null no narrowing (tool-wide; legacy behavior)
281
+ *
282
+ * The dir form keeps a trailing slash so `/repo/src/` can't match a sibling
283
+ * like `/repo/src-secret/x.ts`. Regex metacharacters in the path are escaped.
284
+ */
285
+ export function pathRuleArgsPattern(absPath, scope) {
286
+ if (scope === "tool")
287
+ return null;
288
+ if (scope === "file")
289
+ return { file_path: `^${escapeRegex(absPath)}$` };
290
+ // dir: strip the basename, ensure a single trailing slash, anchor as prefix.
291
+ const slash = absPath.lastIndexOf("/");
292
+ const dir = slash > 0 ? absPath.slice(0, slash) : absPath;
293
+ return { file_path: `^${escapeRegex(dir + "/")}` };
294
+ }
199
295
  /**
200
296
  * Build a PermissionRule from a single approval. Bash narrows to the head
201
297
  * command (first whitespace token) so "git status" → allow all `git ...`.
202
- * Other tools currently allow at tool granularity — file-level whitelists
203
- * could be added later by extending argsPattern.
298
+ *
299
+ * File tools (Write/Edit) narrow to a PATH scope when `opts.pathScope` is
300
+ * "file"/"dir": "allow Write src/foo.ts for this session" then means exactly
301
+ * that file (or its directory), NOT every path. Without a pathScope (or
302
+ * "tool"), they keep the legacy tool-wide grant. Paths are resolved against
303
+ * `opts.cwd` first so `../` can't widen the prefix. Other tools stay tool-wide.
204
304
  */
205
- function buildProjectRule(toolName, args) {
305
+ function buildProjectRule(toolName, args, opts) {
206
306
  if (toolName === "Bash") {
207
307
  const cmd = String(args.command ?? "").trim();
208
308
  const head = cmd.split(/\s+/)[0];
@@ -215,6 +315,23 @@ function buildProjectRule(toolName, args) {
215
315
  reason: `Allowed via permission prompt: ${head}`,
216
316
  };
217
317
  }
318
+ if (PATH_SCOPED_TOOLS.has(toolName) && opts?.pathScope && opts.pathScope !== "tool") {
319
+ const raw = String(args.file_path ?? "");
320
+ if (raw) {
321
+ // Resolve against cwd so a relative or `../`-laden path becomes the real
322
+ // absolute path the prefix anchors to.
323
+ const abs = resolvePath(opts.cwd ?? process.cwd(), raw);
324
+ const argsPattern = pathRuleArgsPattern(abs, opts.pathScope);
325
+ if (argsPattern) {
326
+ return {
327
+ tool: toolName,
328
+ argsPattern,
329
+ decision: "allow",
330
+ reason: `Allowed via permission prompt: ${toolName} ${opts.pathScope === "dir" ? dirname(abs) + "/" : abs}`,
331
+ };
332
+ }
333
+ }
334
+ }
218
335
  return {
219
336
  tool: toolName,
220
337
  decision: "allow",
@@ -224,6 +341,66 @@ function buildProjectRule(toolName, args) {
224
341
  function escapeRegex(s) {
225
342
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
226
343
  }
344
+ /**
345
+ * Does `rule` match this tool call? Shared by the {@link PermissionClassifier}
346
+ * (project/user rules) and {@link InteractiveApprovalBackend}'s session cache
347
+ * so the two never diverge on what "the same operation" means. Handles the
348
+ * JSON round-trip where a persisted RegExp comes back as a string.
349
+ */
350
+ export function ruleMatches(rule, toolName, args) {
351
+ if (rule.tool !== toolName && rule.tool !== "*")
352
+ return false;
353
+ if (!rule.argsPattern)
354
+ return true;
355
+ // A Bash allow-rule is narrowed to a HEAD command (e.g. `^git(\s|$)`), but a
356
+ // raw regex test would let a chained command smuggle a dangerous tail past a
357
+ // grant for its head: `git status && rm -rf /` starts with `git `, so the
358
+ // grant for `git status` would otherwise auto-allow the whole thing. A
359
+ // head-narrowed grant must only cover a SINGLE, non-dangerous command — if the
360
+ // candidate chains (`&&`/`||`/`;`), pipes (`| sh`), or uses substitution/
361
+ // redirection, refuse the match so it re-prompts. (We only gate narrowed Bash
362
+ // grants; a tool-wide `*`/no-pattern rule, or an explicit user `argsPattern`,
363
+ // is the user's own choice and untouched.)
364
+ if (toolName === "Bash" && rule.tool === "Bash" && rule.argsPattern.command) {
365
+ const cmd = String(args.command ?? "");
366
+ const scan = scanShellCommand(cmd);
367
+ // scanShellCommand flags `;`/`&&`/`||`/substitution/redirection (multi-segment
368
+ // or dangerous), but a single pipe stays IN-segment (it's per-segment data
369
+ // flow, not a statement boundary) — so `git log | sh` is one non-dangerous
370
+ // segment. A head grant must not cover a pipe either: reject any segment
371
+ // carrying an (unquoted-enough) pipe so pipe-to-shell can't ride the grant.
372
+ if (scan.dangerous || scan.segments.length > 1 || scan.segments.some((s) => s.includes("|"))) {
373
+ return false;
374
+ }
375
+ }
376
+ for (const [key, pattern] of Object.entries(rule.argsPattern)) {
377
+ const argVal = String(args[key] ?? "");
378
+ if (pattern instanceof RegExp) {
379
+ if (!pattern.test(argVal))
380
+ return false;
381
+ }
382
+ else if (typeof pattern === "string") {
383
+ const looksLikeRegex = /[\\^$.*+?()[\]|{}]/.test(pattern);
384
+ if (looksLikeRegex) {
385
+ let re;
386
+ try {
387
+ re = new RegExp(pattern);
388
+ }
389
+ catch {
390
+ if (!argVal.includes(pattern))
391
+ return false;
392
+ continue;
393
+ }
394
+ if (!re.test(argVal))
395
+ return false;
396
+ }
397
+ else if (!argVal.includes(pattern)) {
398
+ return false;
399
+ }
400
+ }
401
+ }
402
+ return true;
403
+ }
227
404
  /**
228
405
  * Append a rule to <cwd>/.code-shell/settings.local.json. Local file because
229
406
  * permission grants are per-developer and shouldn't be checked into git.
@@ -232,16 +409,17 @@ function escapeRegex(s) {
232
409
  function persistProjectRule(cwd, rule) {
233
410
  const dir = `${cwd}/.code-shell`;
234
411
  const file = `${dir}/settings.local.json`;
235
- // Lazy node imports keep permission.ts free of fs at module load time
236
- // so it can be used in non-Node contexts (tests, browsers via shim).
237
- // eslint-disable-next-line @typescript-eslint/no-require-imports
238
- const fs = require("node:fs");
239
- if (!fs.existsSync(dir))
240
- fs.mkdirSync(dir, { recursive: true });
412
+ // Don't resurrect a deleted project root: a recursive mkdir of
413
+ // <cwd>/.code-shell recreates `cwd` itself as an empty shell when cwd is gone.
414
+ // Persistence here is best-effort, so skip when the root is missing.
415
+ if (!existsSync(cwd))
416
+ return;
417
+ if (!existsSync(dir))
418
+ mkdirSync(dir, { recursive: true });
241
419
  let settings = {};
242
- if (fs.existsSync(file)) {
420
+ if (existsSync(file)) {
243
421
  try {
244
- settings = JSON.parse(fs.readFileSync(file, "utf-8"));
422
+ settings = JSON.parse(readFileSync(file, "utf-8"));
245
423
  }
246
424
  catch {
247
425
  /* start fresh on parse error */
@@ -260,9 +438,11 @@ function persistProjectRule(cwd, rule) {
260
438
  settings.permissions.rules.push(rule);
261
439
  // Atomic write: stage to .tmp, then rename, so a crash mid-write can't
262
440
  // truncate settings.local.json and lose every saved permission grant.
263
- const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
264
- fs.writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n", "utf-8");
265
- fs.renameSync(tmp, file);
441
+ // randomUUID() (not pid+Date.now()) guards against temp-name collisions when
442
+ // two writers fire within the same millisecond.
443
+ const tmp = `${file}.${randomUUID()}.tmp`;
444
+ writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n", "utf-8");
445
+ renameSync(tmp, file);
266
446
  }
267
447
  // Singleton interactive backend for use by the UI
268
448
  let _interactiveBackend = null;
@@ -300,9 +480,16 @@ const DANGEROUS_PATTERNS = [
300
480
  ];
301
481
  const SAFE_READ_PATTERNS = [
302
482
  /^(cat|head|tail|less|more|wc|file|stat|du|df)\s/,
303
- /^(ls|tree|find|locate|which|whereis|type)\s/,
483
+ /^(ls|tree|locate|which|whereis|type)\s/,
484
+ // `find` is read-only ONLY without an action that executes or mutates:
485
+ // -delete, -exec/-execdir/-ok/-okdir run commands; -fprint/-fprintf/-fls
486
+ // write files. Reject those so `find . -delete` / `find . -exec rm {} +`
487
+ // are NOT classified safe-read. Plain predicates (-name, -type, -print) stay.
488
+ /^find\s(?!.*\s-(delete|exec(dir)?|ok(dir)?|fprintf?|fls)\b)/,
304
489
  /^(grep|rg|ag|ack|fgrep|egrep)\s/,
305
- /^(git\s+(status|log|diff|branch|show|blame|remote|tag|stash\s+list|rev-parse|describe))/,
490
+ // Word-boundary the git read subcommands so `git difftool -x <cmd>` (which
491
+ // runs an arbitrary external command) does NOT match on the `diff` branch.
492
+ /^(git\s+(status|log|diff|branch|show|blame|remote|tag|stash\s+list|rev-parse|describe))\b/,
306
493
  /^(node|python|ruby|go|rustc|java|javac)\s+--version/,
307
494
  /^(npm|pnpm|yarn|cargo|pip|gem|brew)\s+(list|ls|info|show|view|outdated|audit)/,
308
495
  /^pwd$/,
@@ -313,6 +500,46 @@ const SAFE_READ_PATTERNS = [
313
500
  /^env$/,
314
501
  /^printenv/,
315
502
  ];
503
+ // ─── Sensitive safe-read downgrade ────────────────────────────────
504
+ //
505
+ // `Read ~/.ssh/id_rsa` routes through path-policy (asks); the equivalent
506
+ // `cat ~/.ssh/id_rsa` was YOLO-classified safe-read and auto-allowed with the
507
+ // desktop's default sandbox=off — a zero-approval credential exfil channel.
508
+ // A segment that would otherwise be safe-read is downgraded to `unsafe` (→
509
+ // ask) when it dumps the process env or its arguments touch a sensitive path.
510
+ // Conservative-and-narrow: only known credential/secret shapes trip it, so
511
+ // ordinary reads (README.md, src/*.ts) stay safe-read.
512
+ // Whole-command env dumps that leak Credential.exposeAsEnv / top-level `env`
513
+ // API keys. `env FOO=bar cmd` (env used as a launcher) is intentionally NOT
514
+ // matched — only a bare dump.
515
+ const ENV_DUMP_RE = /^(env|printenv)(\s+[A-Za-z_][A-Za-z0-9_]*)*\s*$/;
516
+ const SENSITIVE_PATH_PATTERNS = [
517
+ /\.ssh\//, // ~/.ssh/id_rsa, authorized_keys, known_hosts
518
+ /(^|\/)id_(rsa|dsa|ecdsa|ed25519)\b/,
519
+ /(^|\/|\s)\.env(\.[A-Za-z0-9]|\b)/, // .env, .env.local, .env.production
520
+ /\.aws\/credentials\b/,
521
+ /\.code-shell\/credentials\b/,
522
+ /credentials\.json\b/,
523
+ /\.npmrc\b/,
524
+ /\.netrc\b/,
525
+ /\.pgpass\b/,
526
+ /\.docker\/config\.json\b/,
527
+ /\.kube\/config\b/,
528
+ /\.config\/gh\/hosts\b/,
529
+ /\.gnupg\//,
530
+ /(^|\/)\.git-credentials\b/,
531
+ ];
532
+ /**
533
+ * True when an otherwise-safe-read shell segment must be downgraded to `unsafe`
534
+ * because it dumps the environment or reads a sensitive credential/secret path.
535
+ * Exported for direct unit testing of the predicate.
536
+ */
537
+ export function segmentIsSensitiveRead(segment) {
538
+ const s = segment.trim();
539
+ if (ENV_DUMP_RE.test(s))
540
+ return true;
541
+ return SENSITIVE_PATH_PATTERNS.some((re) => re.test(s));
542
+ }
316
543
  const SAFE_WRITE_PATTERNS = [
317
544
  /^(git\s+(add|commit|stash\s+(save|push)))/,
318
545
  /^mkdir\s/,
@@ -433,6 +660,12 @@ function scanShellCommand(input) {
433
660
  i++;
434
661
  }
435
662
  flush();
663
+ // An unclosed quote means we stopped recognizing separators/metacharacters
664
+ // partway through (pipes, `;`, redirection inside the dangling quote were
665
+ // swallowed). Treat malformed shell syntax as dangerous rather than risk
666
+ // misclassifying it as safe.
667
+ if (quote !== null)
668
+ dangerous = true;
436
669
  return { segments, dangerous };
437
670
  }
438
671
  function classifySegment(segment) {
@@ -454,12 +687,17 @@ function classifySegment(segment) {
454
687
  const parts = segment.split("|").map((p) => p.trim());
455
688
  const partIsSafeRead = (p) => SAFE_READ_PATTERNS.some((re) => re.test(p) || re.test(`${p} `));
456
689
  if (parts.every(partIsSafeRead)) {
690
+ // A sensitive read anywhere in the pipe (`cat ~/.ssh/id_rsa | base64`,
691
+ // `env | grep KEY`) downgrades the whole pipe.
692
+ if (parts.some(segmentIsSensitiveRead))
693
+ return "unsafe";
457
694
  return "safe-read";
458
695
  }
459
696
  return "unsafe";
460
697
  }
461
- if (SAFE_READ_PATTERNS.some((p) => p.test(segment)))
462
- return "safe-read";
698
+ if (SAFE_READ_PATTERNS.some((p) => p.test(segment))) {
699
+ return segmentIsSensitiveRead(segment) ? "unsafe" : "safe-read";
700
+ }
463
701
  if (SAFE_WRITE_PATTERNS.some((p) => p.test(segment)))
464
702
  return "safe-write";
465
703
  return "unsafe";
@@ -696,43 +934,10 @@ export class PermissionClassifier {
696
934
  return undefined;
697
935
  }
698
936
  matchesRule(rule, toolName, args) {
699
- if (rule.tool !== toolName && rule.tool !== "*")
700
- return false;
701
- if (rule.argsPattern) {
702
- for (const [key, pattern] of Object.entries(rule.argsPattern)) {
703
- const argVal = String(args[key] ?? "");
704
- if (pattern instanceof RegExp) {
705
- if (!pattern.test(argVal))
706
- return false;
707
- }
708
- else if (typeof pattern === "string") {
709
- // Rules persisted in settings.local.json lose their RegExp type
710
- // through JSON round-trip — they come back as strings. buildProjectRule
711
- // produces regex sources like `^cd(\\s|$)`; treat any string with
712
- // regex metacharacters as a pattern and only fall back to substring
713
- // match for plain literals (kept for backward compat with hand-edited
714
- // settings files that used plain substrings).
715
- const looksLikeRegex = /[\\^$.*+?()[\]|{}]/.test(pattern);
716
- if (looksLikeRegex) {
717
- let re;
718
- try {
719
- re = new RegExp(pattern);
720
- }
721
- catch {
722
- if (!argVal.includes(pattern))
723
- return false;
724
- continue;
725
- }
726
- if (!re.test(argVal))
727
- return false;
728
- }
729
- else if (!argVal.includes(pattern)) {
730
- return false;
731
- }
732
- }
733
- }
734
- }
735
- return true;
937
+ // Delegate to the shared matcher so the classifier and the session cache
938
+ // agree on rule semantics (the JSON-string-vs-RegExp handling, the head-
939
+ // command narrowing for Bash, etc.).
940
+ return ruleMatches(rule, toolName, args);
736
941
  }
737
942
  isDangerousCommand(args) {
738
943
  const command = String(args.command ?? "");
@@ -745,7 +950,14 @@ export class PermissionClassifier {
745
950
  return "medium";
746
951
  if (toolName === "Bash")
747
952
  return "medium";
748
- return "low";
953
+ // Only genuinely read-only built-ins are "low" (the sole tier
954
+ // AutoApprovalBackend approves with no delegate). Everything else —
955
+ // crucially every MCP tool, which can delete records, send messages, or
956
+ // deploy — defaults to "medium" so auto mode delegates it (UI prompt) or
957
+ // fails closed, instead of blind-approving an unknown side-effecting tool.
958
+ if (READ_ONLY_TOOLS.has(toolName))
959
+ return "low";
960
+ return "medium";
749
961
  }
750
962
  /**
751
963
  * Short, human-friendly subtitle. The UI shows this under the tool title;
@@ -15,7 +15,18 @@
15
15
  * Membership policy: read-only tools, planning/agent tools, and
16
16
  * non-destructive task-tracking tools. Bash is included so the model sees it;
17
17
  * the executor additionally gates Bash to read-only commands at call time
18
- * (see executor.isReadOnlyBashCommand). Write/Edit/ApplyPatch/NotebookEdit and
19
- * other mutating tools are intentionally excluded.
18
+ * (executor defers to classifyBashCommand, admitting only "safe-read").
19
+ * Write/Edit/ApplyPatch/NotebookEdit and other mutating tools are
20
+ * intentionally excluded.
20
21
  */
22
+ /**
23
+ * The built-in tools that only READ — no file writes, no side effects, no
24
+ * network mutations. Single source of truth shared by:
25
+ * - investigation-guard.ts (which tools count as "just looking")
26
+ * - permission.ts HeadlessApprovalBackend (approve-read-only mode)
27
+ * - permission.ts assessRisk (these are genuinely low-risk; everything else,
28
+ * including MCP tools, is at least medium so it isn't auto-approved blind)
29
+ * These three lists were independent byte-for-byte copies that could drift.
30
+ */
31
+ export declare const READ_ONLY_TOOLS: ReadonlySet<string>;
21
32
  export declare const PLAN_MODE_ALLOWED_TOOLS: ReadonlySet<string>;
@@ -15,9 +15,27 @@
15
15
  * Membership policy: read-only tools, planning/agent tools, and
16
16
  * non-destructive task-tracking tools. Bash is included so the model sees it;
17
17
  * the executor additionally gates Bash to read-only commands at call time
18
- * (see executor.isReadOnlyBashCommand). Write/Edit/ApplyPatch/NotebookEdit and
19
- * other mutating tools are intentionally excluded.
18
+ * (executor defers to classifyBashCommand, admitting only "safe-read").
19
+ * Write/Edit/ApplyPatch/NotebookEdit and other mutating tools are
20
+ * intentionally excluded.
20
21
  */
22
+ /**
23
+ * The built-in tools that only READ — no file writes, no side effects, no
24
+ * network mutations. Single source of truth shared by:
25
+ * - investigation-guard.ts (which tools count as "just looking")
26
+ * - permission.ts HeadlessApprovalBackend (approve-read-only mode)
27
+ * - permission.ts assessRisk (these are genuinely low-risk; everything else,
28
+ * including MCP tools, is at least medium so it isn't auto-approved blind)
29
+ * These three lists were independent byte-for-byte copies that could drift.
30
+ */
31
+ export const READ_ONLY_TOOLS = new Set([
32
+ "Read",
33
+ "Glob",
34
+ "Grep",
35
+ "WebSearch",
36
+ "WebFetch",
37
+ "ToolSearch",
38
+ ]);
21
39
  export const PLAN_MODE_ALLOWED_TOOLS = new Set([
22
40
  // Plan lifecycle
23
41
  "EnterPlanMode",
@@ -32,6 +50,10 @@ export const PLAN_MODE_ALLOWED_TOOLS = new Set([
32
50
  "AskUserQuestion",
33
51
  "Agent",
34
52
  "ToolSearch",
53
+ // Skill: loads a skill's guidance text into context — read-only, no file
54
+ // writes. Planning is exactly when methodology skills (brainstorming,
55
+ // writing-plans, …) are most useful, so it belongs in the allow-list.
56
+ "Skill",
35
57
  // Task tracking (non-destructive) — both the TodoWrite and the Task* family
36
58
  "TodoWrite",
37
59
  "TaskCreate",
@@ -19,6 +19,7 @@ export declare class ToolRegistry {
19
19
  constructor(options?: ToolRegistryOptions);
20
20
  private registerBuiltins;
21
21
  registerTool(tool: RegisteredTool, executor?: BuiltinToolFn): void;
22
+ unregisterTool(name: string): void;
22
23
  getToolDefinitions(): ToolDefinition[];
23
24
  getTool(name: string): RegisteredTool | undefined;
24
25
  hasTool(name: string): boolean;
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { ConfigError, ToolNotFoundError, ToolExecutionError, ToolTimeoutError } from "../exceptions.js";
5
5
  import { BUILTIN_TOOLS } from "./builtin/index.js";
6
+ import { validateToolMetadata } from "./validate-tool-metadata.js";
6
7
  /**
7
8
  * Default execution timeout for any tool that does not declare its own
8
9
  * timeoutMs at registration time. Tools that need to run longer (Agent,
@@ -28,16 +29,24 @@ export class ToolRegistry {
28
29
  if (selectedNames && !selectedNames.has(tool.definition.name)) {
29
30
  continue;
30
31
  }
32
+ // Fail loud on pathPolicy.arg drift rather than silently disabling path
33
+ // protection (assessment §4.1).
34
+ validateToolMetadata(tool.definition);
31
35
  this.tools.set(tool.definition.name, tool.definition);
32
36
  this.builtinExecutors.set(tool.definition.name, tool.execute);
33
37
  }
34
38
  }
35
39
  registerTool(tool, executor) {
40
+ validateToolMetadata(tool);
36
41
  this.tools.set(tool.name, tool);
37
42
  if (executor) {
38
43
  this.builtinExecutors.set(tool.name, executor);
39
44
  }
40
45
  }
46
+ unregisterTool(name) {
47
+ this.tools.delete(name);
48
+ this.builtinExecutors.delete(name);
49
+ }
41
50
  getToolDefinitions() {
42
51
  return [...this.tools.values()].map((t) => ({
43
52
  name: t.name,
@@ -107,11 +116,16 @@ export class ToolRegistry {
107
116
  if (typeof result === "string") {
108
117
  return { id, toolName: name, result };
109
118
  }
119
+ // 结构化返回:图片块(view_image)或沙箱标记(Bash 等)。两者互斥,
120
+ // 各取所有,缺的字段为 undefined。
121
+ const contentBlocks = "contentBlocks" in result ? result.contentBlocks : undefined;
122
+ const sandbox = "sandbox" in result ? result.sandbox : undefined;
110
123
  return {
111
124
  id,
112
125
  toolName: name,
113
- result: result.result ?? "(image)",
114
- contentBlocks: result.contentBlocks,
126
+ result: result.result ?? (contentBlocks ? "(image)" : ""),
127
+ contentBlocks,
128
+ sandbox,
115
129
  };
116
130
  }
117
131
  catch (err) {
@@ -27,6 +27,14 @@ export interface SandboxConfig {
27
27
  export interface SandboxBackend {
28
28
  /** Resolved backend name — "off" means no sandboxing. */
29
29
  name: "off" | "seatbelt" | "bwrap";
30
+ /**
31
+ * Network policy that applied for this run. Not part of the backend's
32
+ * isolation mechanism — the Engine stamps it onto the resolved backend
33
+ * (it owns the resolved SandboxConfig) so tools can surface "网络 deny"
34
+ * on their result without threading the whole config through ToolContext.
35
+ * Absent on "off" (no policy enforced).
36
+ */
37
+ network?: SandboxNetworkPolicy;
30
38
  /**
31
39
  * Decide the binary + argv that should actually be spawned to run `command`
32
40
  * under this sandbox. The shell still interprets `command`; we only swap
@@ -17,7 +17,7 @@
17
17
  * when no sandbox is available.
18
18
  */
19
19
  import { accessSync, constants, realpathSync } from "node:fs";
20
- import { homedir } from "node:os";
20
+ import { homedir, tmpdir } from "node:os";
21
21
  import { SandboxUnavailableError } from "../../exceptions.js";
22
22
  /**
23
23
  * Detect which sandbox mechanisms are available on the current host. Used by
@@ -111,9 +111,14 @@ function warnAutoDowngrade() {
111
111
  `Set sandbox.mode=off in settings.json to silence, or CODE_SHELL_SANDBOX_QUIET=1.\n`);
112
112
  }
113
113
  export function defaultSandboxConfig(mode = "auto") {
114
+ // Temp roots are writable so tool-result spillover / scratch files land
115
+ // somewhere. POSIX: the canonical /tmp family. Windows has no OS sandbox
116
+ // backend (auto → off), so writableRoots is effectively unused there, but
117
+ // keep it host-correct by using the platform temp dir instead of /tmp.
118
+ const tmpRoots = process.platform === "win32" ? [tmpdir()] : ["/tmp", "/private/tmp", "/var/tmp"];
114
119
  return {
115
120
  mode,
116
- writableRoots: ["${workspace}", "/tmp", "/private/tmp", "/var/tmp"],
121
+ writableRoots: ["${workspace}", ...tmpRoots],
117
122
  deniedReads: [
118
123
  "~/.ssh",
119
124
  "~/.aws",