@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
@@ -15,9 +15,14 @@
15
15
  */
16
16
  import { Methods, ErrorCodes, createResponse, createErrorResponse, createNotification, isRequest, } from "./types.js";
17
17
  import { diskDefaultsFrom } from "../engine/engine.js";
18
+ import { isProtectedSettingKey } from "../settings/manager.js";
18
19
  import { setInteractiveApprovalFn } from "../tool-system/permission.js";
19
20
  import { getArenaStatus } from "../tool-system/builtin/arena.js";
20
- import { agentNotificationBus } from "../tool-system/builtin/agent-notifications.js";
21
+ import { agentNotificationBus, notificationQueue, buildNotificationMessage, } from "../tool-system/builtin/agent-notifications.js";
22
+ import { backgroundShellManager } from "../runtime/background-shell.js";
23
+ import { backgroundJobRegistry } from "../tool-system/builtin/background-jobs.js";
24
+ import { listBackgroundWorkForUI } from "../tool-system/builtin/background-work.js";
25
+ import { logger } from "../logging/logger.js";
21
26
  import { nanoid } from "nanoid";
22
27
  import { redactLlmConfig, maskSecretValue } from "./redact.js";
23
28
  import { redactSecrets } from "../logging/sanitize-messages.js";
@@ -28,6 +33,8 @@ export class AgentServer {
28
33
  transport;
29
34
  /** Fresh-disk settings reader for config hot-reload; null when not wired. */
30
35
  settingsReader;
36
+ /** Disk-only active-goal reader for agent/goalGet on a non-live session. */
37
+ readActiveGoalFromDisk;
31
38
  /**
32
39
  * Monotonic config-reload version, bumped per reloadSettings request so each
33
40
  * Engine.refreshRuntimeConfig can drop out-of-order (stale) deliveries (Q5).
@@ -64,6 +71,7 @@ export class AgentServer {
64
71
  this.chatManager = options.chatManager ?? null;
65
72
  this.legacyEngine = options.engine ?? null;
66
73
  this.settingsReader = options.settingsReader ?? null;
74
+ this.readActiveGoalFromDisk = options.readActiveGoalFromDisk ?? null;
67
75
  if (!this.chatManager && !this.legacyEngine) {
68
76
  throw new Error("AgentServer: either chatManager or engine must be supplied");
69
77
  }
@@ -93,15 +101,121 @@ export class AgentServer {
93
101
  // B2.2 — forward background sub-agent completion events through the
94
102
  // protocol so Desktop / SDK / remote AgentClients see them too. The
95
103
  // bus is fed from `NotificationQueue.enqueue`, so this single
96
- // subscription covers every enqueue site (today: only agent.ts). The
97
- // bus now guarantees a real sessionId (no more legacy-bucket coercion
98
- // to ""), so we forward whatever sid it hands us.
104
+ // subscription covers every enqueue site background sub-agents
105
+ // (agent.ts), background shells (background-shell.ts), and video/job
106
+ // polls (background-jobs.ts). The bus now guarantees a real sessionId
107
+ // (no more legacy-bucket coercion to ""), so we forward whatever sid it
108
+ // hands us.
99
109
  this.bgAgentBusUnsubscribe = agentNotificationBus.subscribe((sessionId, event) => {
100
110
  this.notify(Methods.StreamEvent, { sessionId, event });
111
+ // Background work that finishes while the session is IDLE (a
112
+ // run_in_background Bash like a download, a background sub-agent, or a
113
+ // video poll — the engine no longer parks on any of them) would otherwise
114
+ // leave its completion sitting in the queue until the user manually sends.
115
+ // Wake the session with one run carrying the notification so the model
116
+ // reads "download complete" and continues on its own (the persisted goal
117
+ // is judged that turn). If the work finishes while a run is still in
118
+ // flight, the idle guard below skips it and the run-boundary re-check
119
+ // (trigger B) drains it at end-of-turn instead. A never-exiting dev server
120
+ // emits no completion, so it never wakes anything (no task/service
121
+ // classification needed).
122
+ this.maybeWakeIdleSession(sessionId);
101
123
  });
102
124
  // Notify client we're ready
103
125
  this.notify(Methods.Status, { status: "ready" });
104
126
  }
127
+ /**
128
+ * Wake an IDLE chatManager session that has pending background-completion
129
+ * notifications, by enqueueing one turn whose task is the drained
130
+ * notification(s). The model then sees the completion and continues; the
131
+ * session's persisted goal (if any) is judged on that turn.
132
+ *
133
+ * Guards:
134
+ * - chatManager path only (the legacy single-engine / headless path drives
135
+ * its own loop and has no idle-session-resume concept).
136
+ * - Session must exist AND be idle. If it's busy, we do nothing: the in-flight
137
+ * run's end-of-turn `drainAll` already collects every pending notification,
138
+ * so a second run would be redundant — and `enqueueTurn` while busy would
139
+ * queue a spurious extra turn.
140
+ * - We `drainAll` exactly here and feed the items into the woken turn. This
141
+ * also merges a burst of near-simultaneous completions into one wakeup:
142
+ * the first drains all currently-pending items; subsequent bus events for
143
+ * the same session find it busy (or find an empty queue) and no-op.
144
+ *
145
+ * INVARIANT (the burst-merge correctness depends on it): the merge only
146
+ * holds because (a) the bus fans out to subscribers SYNCHRONOUSLY, and (b)
147
+ * `enqueueTurn` sets the session's `active` (→ isBusy()===true) SYNCHRONOUSLY
148
+ * before its first await. So when bus events #2..N arrive — still on the same
149
+ * synchronous fan-out as #1 — they already see the session as busy and no-op.
150
+ * If either path is ever made async (e.g. an await is added inside
151
+ * enqueueTurn before `active` is set, or the bus starts deferring delivery),
152
+ * this degrades into N concurrent wakeups for N completions. Keep both
153
+ * synchronous, or replace this comment's assumption with an explicit
154
+ * "wakeup in flight" guard flag.
155
+ */
156
+ maybeWakeIdleSession(sessionId) {
157
+ if (!this.chatManager)
158
+ return;
159
+ const session = this.chatManager.get(sessionId);
160
+ if (!session || session.isBusy())
161
+ return;
162
+ // Headless / automation runs are one-shot: the caller takes result.text and
163
+ // is gone, so there's no consumer for a woken continuation turn. Headless
164
+ // already drained its background sub-agents inside engine.run before
165
+ // returning; any remaining queued notification (video/shell) must NOT spin
166
+ // an orphan turn. Only the interactive path auto-continues.
167
+ if (session.engine.isHeadless())
168
+ return;
169
+ // Don't resurrect a session the user just Stopped: cancel() leaves it idle
170
+ // (active=null) so isBusy() reads false, but auto-running a fresh turn here
171
+ // would defeat the Stop. The flag clears the moment the user sends again.
172
+ if (session.wasCancelledSinceLastTurn())
173
+ return;
174
+ const pending = notificationQueue.drainAll(sessionId);
175
+ if (pending.length === 0)
176
+ return;
177
+ const task = `<system-reminder>\n${buildNotificationMessage(pending)}\n</system-reminder>`;
178
+ void session
179
+ .enqueueTurn(task, {
180
+ // Synthetic notification, not the user's own input: persisted with an
181
+ // `injected` flag so a disk rebuild doesn't render it as a phantom user
182
+ // bubble (the live UI shows only the woken assistant's reply).
183
+ injected: true,
184
+ onStream: (event) => this.notify(Methods.StreamEvent, { sessionId, event }),
185
+ })
186
+ .catch((err) => {
187
+ // A wakeup turn failing must not crash the bus fan-out. The drained
188
+ // notifications are already in the transcript via the run's messages;
189
+ // log and move on.
190
+ logger.warn("bg_wakeup.turn_failed", {
191
+ sessionId,
192
+ error: err.message,
193
+ });
194
+ // Belt-and-braces, mirroring the send() path's run().then(clear-busy):
195
+ // the renderer set the composer "working" spinner on this run's
196
+ // session_started, and clears it on turn_complete/error. A failure
197
+ // BEFORE the turn-loop runs (e.g. a setup error) emits neither, which
198
+ // would leave the spinner stuck. Emit a terminal `error` (NOT a
199
+ // turn_complete) so the renderer clears busy AND a woken automation
200
+ // session's runStatus flips to "failed" rather than being mislabeled
201
+ // "completed". If the turn-loop already emitted its own `error`, this is
202
+ // a harmless duplicate (busy already cleared, status already failed).
203
+ this.notify(Methods.StreamEvent, {
204
+ sessionId,
205
+ event: { type: "error", error: err?.message ?? "background wakeup failed" },
206
+ });
207
+ })
208
+ .finally(() => {
209
+ // Run-boundary re-check (trigger B): the woken summarize turn may have
210
+ // spawned NEW background work (e.g. goal "generate 2 videos" → after #1
211
+ // completes, this turn submits #2). When #2 finishes its notification
212
+ // arrives while we're idle again — but if it arrived DURING this turn
213
+ // (busy), trigger A skipped it. Re-checking at the run boundary drains
214
+ // anything that landed while busy, chaining wakeups until the queue is
215
+ // truly empty. Replaces the old engine for(;;) outer loop.
216
+ this.maybeWakeIdleSession(sessionId);
217
+ });
218
+ }
105
219
  // ─── Request Dispatch ───────────────────────────────────────────
106
220
  async handleRequest(req) {
107
221
  switch (req.method) {
@@ -123,9 +237,30 @@ export class AgentServer {
123
237
  case Methods.Inject:
124
238
  this.handleInject(req);
125
239
  break;
240
+ case Methods.Steer:
241
+ this.handleSteer(req);
242
+ break;
243
+ case Methods.Unsteer:
244
+ this.handleUnsteer(req);
245
+ break;
126
246
  case Methods.CloseSession:
127
247
  this.handleCloseSession(req);
128
248
  break;
249
+ case Methods.GoalExtend:
250
+ this.handleGoalExtend(req);
251
+ break;
252
+ case Methods.GoalClear:
253
+ this.handleGoalClear(req);
254
+ break;
255
+ case Methods.GoalGet:
256
+ this.handleGoalGet(req);
257
+ break;
258
+ case Methods.BackgroundShells:
259
+ this.handleBackgroundShells(req);
260
+ break;
261
+ case Methods.BackgroundWork:
262
+ this.handleBackgroundWork(req);
263
+ break;
129
264
  default:
130
265
  this.transport.send(createErrorResponse(req.id, ErrorCodes.MethodNotFound, `Unknown method: ${req.method}`));
131
266
  }
@@ -155,6 +290,7 @@ export class AgentServer {
155
290
  session = cm.getOrCreate(params.sessionId, {
156
291
  permissionMode: params.permissionMode,
157
292
  cwd: params.cwd,
293
+ projectTrusted: params.projectTrusted,
158
294
  });
159
295
  }
160
296
  catch (err) {
@@ -162,6 +298,15 @@ export class AgentServer {
162
298
  this.transport.send(createErrorResponse(req.id, code, err.message));
163
299
  return;
164
300
  }
301
+ // `requireExisting`: reject if the target session isn't on disk rather than
302
+ // running the prompt against a freshly-created blank session. A cron
303
+ // "continue this conversation" job whose session the user deleted must fail
304
+ // loudly here (SessionNotFound) so the scheduler can auto-disable it,
305
+ // instead of silently executing with no transcript/goal/context.
306
+ if (params.requireExisting === true && !session.engine.sessionExistsOnDisk(params.sessionId)) {
307
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionNotFound, `session ${params.sessionId} does not exist`));
308
+ return;
309
+ }
165
310
  if (typeof params.planMode === "boolean") {
166
311
  session.engine.setPlanMode(params.planMode);
167
312
  }
@@ -176,6 +321,15 @@ export class AgentServer {
176
321
  // headless engines (no human to answer).
177
322
  if (!session.engine.isHeadless()) {
178
323
  session.engine.setAskUser((question, opts) => this.requestAskUserForSession(session, sid, question, opts));
324
+ // Browser automation bridge: each method routes a browser action to the
325
+ // client (Electron main drives the webview via CDP) over the SAME
326
+ // request/response channel as askUser (pendingApprovals + requestId),
327
+ // reusing its proven resolve/timeout/cleanup. The main-side handler
328
+ // replies with the action's JSON result.
329
+ session.engine.setBrowserBridge(this.makeBrowserBridge(session, sid));
330
+ // Cookie→browser injection (InjectCredential tool): same cross-process
331
+ // channel; main restores the cookie jar into the built-in browser.
332
+ session.engine.setInjectCredential((credentialId) => this.requestCredentialInjectForSession(session, sid, credentialId));
179
333
  }
180
334
  try {
181
335
  const result = await session.enqueueTurn(params.task, {
@@ -194,9 +348,18 @@ export class AgentServer {
194
348
  usage: result.usage,
195
349
  };
196
350
  this.transport.send(createResponse(req.id, runResult));
351
+ // Run-boundary re-check (trigger B): the session is idle now. If a
352
+ // background task (shell/video) completed DURING this run, its bus event
353
+ // fired while we were busy and trigger A skipped it — drain it now and
354
+ // wake a continuation turn. Interactive path only; headless already
355
+ // drained its sub-agents inside engine.run before returning.
356
+ this.maybeWakeIdleSession(sid);
197
357
  }
198
358
  catch (err) {
199
359
  this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
360
+ // Even on a failed run the session goes idle — re-check so a background
361
+ // completion that landed during the failed run isn't orphaned.
362
+ this.maybeWakeIdleSession(sid);
200
363
  }
201
364
  }
202
365
  async handleRunLegacy(req) {
@@ -302,6 +465,11 @@ export class AgentServer {
302
465
  return;
303
466
  }
304
467
  s.pendingApprovals.delete(params.requestId);
468
+ // Cancel the pending timeout for THIS request (the ask path armed one).
469
+ // The legacy single-engine branch below already does this; without it
470
+ // here a decided request leaves a live timer that fires at
471
+ // APPROVAL_TIMEOUT_MS and churns a dead map entry.
472
+ this.clearApprovalTimer(params.requestId);
305
473
  resolve(params.decision);
306
474
  this.transport.send(createResponse(req.id, { ok: true }));
307
475
  return;
@@ -332,6 +500,14 @@ export class AgentServer {
332
500
  return;
333
501
  }
334
502
  s.cancel();
503
+ // s.cancel() only aborts the engine controller + drains queued turns. The
504
+ // session's pendingApprovals (askUser / browser_action / tool approvals)
505
+ // are NOT driven by the abort signal — they only settle on a client reply
506
+ // or the 5-minute APPROVAL_TIMEOUT_MS. Left alone, the awaiting tool call
507
+ // hangs until that timeout (delaying run wind-down) and the server-side
508
+ // approvalTimers entry leaks. Resolve them as cancelled now and clear the
509
+ // matching timers, mirroring the legacy path below.
510
+ this.cancelSessionApprovals(s);
335
511
  this.transport.send(createResponse(req.id, { ok: true }));
336
512
  return;
337
513
  }
@@ -348,6 +524,163 @@ export class AgentServer {
348
524
  this.clearAllApprovalTimers();
349
525
  this.transport.send(createResponse(req.id, { ok: true }));
350
526
  }
527
+ /**
528
+ * Extend a running goal's turn/budget ceilings mid-run (TODO 3.1). Routed to
529
+ * a specific session (multi-session path). Returns the resulting effective
530
+ * limits, or an error if no session / no active run.
531
+ */
532
+ handleGoalExtend(req) {
533
+ const params = (req.params ?? {});
534
+ const ext = {
535
+ addTurns: params.addTurns,
536
+ addTokenBudget: params.addTokenBudget,
537
+ addTimeBudgetMs: params.addTimeBudgetMs,
538
+ addStopBlocks: params.addStopBlocks,
539
+ };
540
+ // Multi-session path: route to the named session.
541
+ const session = this.chatManager && typeof params.sessionId === "string"
542
+ ? this.chatManager.get(params.sessionId)
543
+ : undefined;
544
+ // Legacy single-engine path (no chatManager): extend the one engine's run,
545
+ // mirroring handleCancel which also supports both paths. Without this a
546
+ // legacy host can cancel but never extend a goal.
547
+ const result = session
548
+ ? session.extendGoalRun(ext)
549
+ : this.legacyEngine
550
+ ? this.legacyEngine.extendGoalRun(ext)
551
+ : undefined;
552
+ if (result === undefined) {
553
+ // Couldn't even resolve a target (no session and no legacy engine).
554
+ if (!session && !this.legacyEngine) {
555
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, params.sessionId ? `No such session: ${params.sessionId}` : "sessionId is required"));
556
+ return;
557
+ }
558
+ }
559
+ if (!result) {
560
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "No active run to extend"));
561
+ return;
562
+ }
563
+ this.transport.send(createResponse(req.id, { ok: true, limits: result }));
564
+ }
565
+ /**
566
+ * Clear a session's persisted active goal (CC /goal clear). Routes to the
567
+ * named session, falling back to the legacy single engine (mirrors
568
+ * handleGoalExtend / handleCancel's dual path). Returns { ok, cleared }.
569
+ */
570
+ handleGoalClear(req) {
571
+ const params = (req.params ?? {});
572
+ const session = this.chatManager && typeof params.sessionId === "string"
573
+ ? this.chatManager.get(params.sessionId)
574
+ : undefined;
575
+ if (!session && !this.legacyEngine) {
576
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, params.sessionId ? `No such session: ${params.sessionId}` : "sessionId is required"));
577
+ return;
578
+ }
579
+ const cleared = session
580
+ ? session.clearGoal()
581
+ : params.sessionId
582
+ ? (this.legacyEngine.clearGoal(params.sessionId) ?? false)
583
+ : false;
584
+ this.transport.send(createResponse(req.id, { ok: true, cleared }));
585
+ }
586
+ /**
587
+ * Read a session's persisted active goal so the host can re-surface the goal
588
+ * block + Cancel button on session load. A persistent goal lives only in
589
+ * state.activeGoal and is never replayed from the transcript, so a reloaded
590
+ * (or disk-rebuilt) session has no other way to learn it. Prefers a live
591
+ * chatManager session, else falls through to a disk read (readActiveGoalFromDisk
592
+ * in worker mode, or the legacy engine's disk read in single-engine mode) — the
593
+ * bug case is an aborted/reloaded session that is NOT live. Returns { ok, goal } where goal
594
+ * is the objective string, or null when there's no goal / unknown session.
595
+ * Never errors on "no goal" — null is the normal "nothing to show" answer.
596
+ */
597
+ handleGoalGet(req) {
598
+ const params = (req.params ?? {});
599
+ if (typeof params.sessionId !== "string" || !params.sessionId) {
600
+ // Missing/empty param is InvalidParams, not SessionClosed (no session was
601
+ // ever named) — matches handleSteer/handleUnsteer/handleBackgroundShells.
602
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
603
+ return;
604
+ }
605
+ // Prefer the live session; otherwise read straight off disk. The goal we
606
+ // want to recover belongs to a session that is, by definition of this bug,
607
+ // usually NOT currently live: reopening after restart doesn't create a
608
+ // ChatSession (that only happens on a send), so chatManager.get() misses.
609
+ // In worker (chatManager) mode there is no legacyEngine, so the disk read
610
+ // MUST come from readActiveGoalFromDisk — relying on legacyEngine there
611
+ // silently returned null and the UI goal block never re-surfaced.
612
+ const live = this.chatManager ? this.chatManager.get(params.sessionId) : undefined;
613
+ const goal = live
614
+ ? live.getGoal()
615
+ : (this.readActiveGoalFromDisk?.(params.sessionId) ??
616
+ this.legacyEngine?.getGoal(params.sessionId) ??
617
+ undefined);
618
+ this.transport.send(createResponse(req.id, { ok: true, goal: goal ? goal.objective : null }));
619
+ }
620
+ /**
621
+ * Query/control a session's background shells for the desktop UI panel (TODO
622
+ * 3.2). Reads the singleton BackgroundShellManager directly (shells outlive
623
+ * the run that spawned them, so we don't need a live ChatSession). Actions:
624
+ * - (default) list → the session's shells
625
+ * - output → full retained output of one shell
626
+ * - kill → terminate one shell's process group
627
+ * All scoped by sessionId (manager enforces ownership on output/kill).
628
+ */
629
+ handleBackgroundShells(req) {
630
+ const params = (req.params ?? {});
631
+ const sessionId = params.sessionId;
632
+ if (typeof sessionId !== "string" || !sessionId) {
633
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
634
+ return;
635
+ }
636
+ const action = params.action ?? "list";
637
+ if (action === "list") {
638
+ const shells = backgroundShellManager.listForSession(sessionId);
639
+ this.transport.send(createResponse(req.id, { shells }));
640
+ return;
641
+ }
642
+ if (!params.shellId) {
643
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "shellId is required"));
644
+ return;
645
+ }
646
+ if (action === "output") {
647
+ const res = backgroundShellManager.readOutput(params.shellId, "all", sessionId);
648
+ if (!res.ok) {
649
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, res.error));
650
+ return;
651
+ }
652
+ this.transport.send(createResponse(req.id, { header: res.header, text: res.text }));
653
+ return;
654
+ }
655
+ if (action === "kill") {
656
+ void backgroundShellManager.kill(params.shellId, sessionId).then((res) => {
657
+ if (!res.ok) {
658
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, res.error));
659
+ }
660
+ else {
661
+ this.transport.send(createResponse(req.id, { ok: true }));
662
+ }
663
+ });
664
+ return;
665
+ }
666
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, `unknown action: ${action}`));
667
+ }
668
+ /**
669
+ * BackgroundWork — unified, list-only view of a session's background work
670
+ * across all three registries (shells + sub-agents + jobs) for the desktop
671
+ * background panel. Per-shell output/kill still flow through BackgroundShells
672
+ * (by shellId); this just answers "what's running in the background right now".
673
+ */
674
+ handleBackgroundWork(req) {
675
+ const params = (req.params ?? {});
676
+ const sessionId = params.sessionId;
677
+ if (typeof sessionId !== "string" || !sessionId) {
678
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
679
+ return;
680
+ }
681
+ const items = listBackgroundWorkForUI(sessionId);
682
+ this.transport.send(createResponse(req.id, { items }));
683
+ }
351
684
  // ─── CloseSession ───────────────────────────────────────────────
352
685
  handleCloseSession(req) {
353
686
  const params = (req.params ?? {});
@@ -358,6 +691,14 @@ export class AgentServer {
358
691
  if (this.chatManager) {
359
692
  this.chatManager.close(params.sessionId);
360
693
  }
694
+ // Explicit session teardown — reap that session's background shells
695
+ // (design §6 "session 被显式删除 → killSession"). This is the RPC path
696
+ // (agent/closeSession from the host on delete), distinct from the idle
697
+ // sweeper's chatManager.close() which must NOT kill (§6). Fire-and-forget.
698
+ void backgroundShellManager.killSession(params.sessionId);
699
+ // Drop this session's retained background jobs too (#2/#5): finished jobs
700
+ // are kept for the panel, so explicit teardown is where they're released.
701
+ backgroundJobRegistry.dropForSession(params.sessionId);
361
702
  this.transport.send(createResponse(req.id, { ok: true }));
362
703
  }
363
704
  // ─── Configure ──────────────────────────────────────────────────
@@ -397,7 +738,7 @@ export class AgentServer {
397
738
  }
398
739
  const settings = this.settingsReader();
399
740
  const version = ++this.configVersion;
400
- s.engine.refreshRuntimeConfig(diskDefaultsFrom(settings), version);
741
+ s.engine.refreshRuntimeConfig(diskDefaultsFrom(settings, s.engine.getEffectiveDisabledLists().disabledPlugins), version);
401
742
  }
402
743
  this.transport.send(createResponse(req.id, { ok: true }));
403
744
  return;
@@ -405,9 +746,22 @@ export class AgentServer {
405
746
  // Global configure — delegate to legacyEngine if available,
406
747
  // or to any session's engine from chatManager for settings ops
407
748
  const engine = this.legacyEngine ?? this.anyEngine();
408
- if (params.reloadModels && engine) {
749
+ if (params.reloadModels) {
409
750
  try {
410
- engine.reloadModelPool();
751
+ const seen = new Set();
752
+ const reload = (target) => {
753
+ if (!target || seen.has(target))
754
+ return;
755
+ seen.add(target);
756
+ target.reloadModelPool();
757
+ };
758
+ reload(this.legacyEngine);
759
+ reload(this.globalQueryEngine);
760
+ if (this.chatManager) {
761
+ this.chatManager.forEachSession((s) => reload(s.engine));
762
+ }
763
+ if (seen.size === 0)
764
+ reload(engine);
411
765
  }
412
766
  catch (err) {
413
767
  this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
@@ -445,23 +799,29 @@ export class AgentServer {
445
799
  // Mirrors how reloadModels/model/planMode above act on `engine`.
446
800
  if (engine) {
447
801
  const settings = this.settingsReader();
448
- engine.refreshRuntimeConfig(diskDefaultsFrom(settings), ++this.configVersion);
802
+ engine.refreshRuntimeConfig(diskDefaultsFrom(settings, engine.getEffectiveDisabledLists().disabledPlugins), ++this.configVersion);
449
803
  }
450
804
  this.transport.send(createResponse(req.id, { ok: true }));
451
805
  return;
452
806
  }
453
807
  const settings = this.settingsReader();
454
- const patch = diskDefaultsFrom(settings);
455
- // #6: content short-circuit. If the disk-default patch is byte-identical
456
- // to the last one we broadcast, the disk config that matters to running
457
- // sessions hasn't changed — skip the whole forEachSession (and the per-
458
- // session reloadHooks churn it triggers). Still return ok; a genuine
459
- // change differs in JSON and propagates normally.
460
- const patchJson = JSON.stringify(patch);
808
+ // The MCP merge folds each session's PROJECT capabilityOverrides (a
809
+ // project-level "on" must override the global disabledPlugins), so the
810
+ // patch is cwd-dependent compute it per session, not once.
811
+ const perSession = [];
812
+ this.chatManager.forEachSession((s) => {
813
+ const patch = diskDefaultsFrom(settings, s.engine.getEffectiveDisabledLists().disabledPlugins);
814
+ perSession.push({ apply: (v) => s.engine.refreshRuntimeConfig(patch, v), patch });
815
+ });
816
+ // #6: content short-circuit — now keyed on ALL per-session patches, so a
817
+ // change visible to only one session (e.g. its project's overrides)
818
+ // still propagates while a true no-op skips the reloadHooks churn.
819
+ const patchJson = JSON.stringify(perSession.map((p) => p.patch));
461
820
  if (patchJson !== this.lastBroadcastPatch) {
462
821
  this.lastBroadcastPatch = patchJson;
463
822
  const version = ++this.configVersion;
464
- this.chatManager.forEachSession((s) => s.engine.refreshRuntimeConfig(patch, version));
823
+ for (const p of perSession)
824
+ p.apply(version);
465
825
  }
466
826
  }
467
827
  this.transport.send(createResponse(req.id, { ok: true }));
@@ -528,6 +888,11 @@ export class AgentServer {
528
888
  // apiKey is redacted at this boundary: clients only get
529
889
  // hasApiKey + optional apiKeyPreview. See protocol/redact.ts.
530
890
  llm: redactLlmConfig(config.llm),
891
+ // Resolved feature flags (defaults merged with settings overlay)
892
+ // so the /features command can list current state.
893
+ featureFlags: engine.getFeatureFlags(),
894
+ // Effective permission rules so /permissions can list them (TODO 5.1).
895
+ permissionRules: engine.getPermissionRules(),
531
896
  },
532
897
  }));
533
898
  break;
@@ -654,6 +1019,15 @@ export class AgentServer {
654
1019
  const { key, value } = params;
655
1020
  if (!key)
656
1021
  throw new Error("key is required for config_set");
1022
+ // Refuse writes to trust-root fields via the generic config path. A
1023
+ // protocol peer (external driver, paired phone, compromised renderer)
1024
+ // could otherwise self-authorize tools or inject env/hooks/MCP servers,
1025
+ // which is equivalent to remotely bypassing workspace trust. Legit
1026
+ // writes to these go through the dedicated settings UI, not config_set;
1027
+ // provider/model edits use provider_add / models handlers below.
1028
+ if (isProtectedSettingKey(key)) {
1029
+ throw new Error(`config_set refused: "${key}" is a protected trust/permission field and cannot be written through this path.`);
1030
+ }
657
1031
  engine.updateConfig(key, value);
658
1032
  // Echo back through the same secret-aware masker as config_get so
659
1033
  // a `config_set("llm.apiKey", "...")` confirmation doesn't ship
@@ -861,6 +1235,55 @@ export class AgentServer {
861
1235
  this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
862
1236
  }
863
1237
  }
1238
+ // ─── Steer ──────────────────────────────────────────────────────
1239
+ // Queue a user message into the in-flight run's turn loop (不打断). Unlike
1240
+ // Inject (appends an assistant context msg for the NEXT run), Steer feeds a
1241
+ // user message to the CURRENT run's next step. No abort, no LLM trigger by
1242
+ // itself — the running loop picks it up at its next step boundary.
1243
+ handleSteer(req) {
1244
+ const params = (req.params ?? {});
1245
+ if (!params.text || !params.sessionId) {
1246
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "text and sessionId required"));
1247
+ return;
1248
+ }
1249
+ const engine = this.chatManager
1250
+ ? this.chatManager.get(params.sessionId)?.engine
1251
+ : this.legacyEngine;
1252
+ if (!engine) {
1253
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, `No such session: ${params.sessionId}`));
1254
+ return;
1255
+ }
1256
+ try {
1257
+ engine.enqueueSteer(params.sessionId, params.text, params.id);
1258
+ this.transport.send(createResponse(req.id, { ok: true }));
1259
+ }
1260
+ catch (err) {
1261
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
1262
+ }
1263
+ }
1264
+ handleUnsteer(req) {
1265
+ const params = (req.params ?? {});
1266
+ if (!params.id || !params.sessionId) {
1267
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "id and sessionId required"));
1268
+ return;
1269
+ }
1270
+ const engine = this.chatManager
1271
+ ? this.chatManager.get(params.sessionId)?.engine
1272
+ : this.legacyEngine;
1273
+ if (!engine) {
1274
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.SessionClosed, `No such session: ${params.sessionId}`));
1275
+ return;
1276
+ }
1277
+ try {
1278
+ // removed=false means the loop already consumed it — not an error, the
1279
+ // host just learns it couldn't be revoked (it will arrive as a bubble).
1280
+ const removed = engine.unsteer(params.sessionId, params.id);
1281
+ this.transport.send(createResponse(req.id, { ok: true, removed }));
1282
+ }
1283
+ catch (err) {
1284
+ this.transport.send(createErrorResponse(req.id, ErrorCodes.InternalError, err.message));
1285
+ }
1286
+ }
864
1287
  // ─── Client Communication ───────────────────────────────────────
865
1288
  /**
866
1289
  * Ask the client to approve a tool operation (legacy single-engine path).
@@ -919,6 +1342,8 @@ export class AgentServer {
919
1342
  args.options = opts.options;
920
1343
  if (opts?.multiSelect !== undefined)
921
1344
  args.multiSelect = opts.multiSelect;
1345
+ if (opts?.optionsOnly !== undefined)
1346
+ args.optionsOnly = opts.optionsOnly;
922
1347
  this.notify(Methods.ApprovalRequest, {
923
1348
  sessionId,
924
1349
  requestId,
@@ -931,6 +1356,129 @@ export class AgentServer {
931
1356
  });
932
1357
  });
933
1358
  }
1359
+ /**
1360
+ * Build a BrowserBridge whose every method routes a browser action to the
1361
+ * client over the session's pendingApprovals channel (same as askUser). The
1362
+ * client (Electron main) drives the webview via CDP and replies with a JSON
1363
+ * result string we parse back into the typed result. On timeout / malformed
1364
+ * reply we degrade to a safe error result rather than throwing.
1365
+ */
1366
+ makeBrowserBridge(session, sessionId) {
1367
+ const call = (action, payload) => this.requestBrowserActionForSession(session, sessionId, action, payload);
1368
+ return {
1369
+ snapshot: () => call("snapshot", {}),
1370
+ click: (ref) => call("click", { ref }),
1371
+ type: (ref, text) => call("type", { ref, text }),
1372
+ navigate: (url) => call("navigate", { url }),
1373
+ scroll: (dir, amount) => call("scroll", { dir, amount }),
1374
+ readContent: () => call("readContent", {}),
1375
+ extractLinks: () => call("extractLinks", {}),
1376
+ waitForLoad: (timeoutMs) => call("waitForLoad", { timeoutMs }),
1377
+ hover: (ref) => call("hover", { ref }),
1378
+ selectOption: (ref, value) => call("selectOption", { ref, value }),
1379
+ pressKey: (key, ref) => call("pressKey", { key, ref }),
1380
+ fetchImages: (refs) => call("fetchImages", { refs }),
1381
+ screenshot: (ref) => call("screenshot", { ref }),
1382
+ listTabs: () => call("listTabs", {}),
1383
+ switchTab: (tabId) => call("switchTab", { tabId }),
1384
+ };
1385
+ }
1386
+ requestBrowserActionForSession(session, sessionId, action, payload) {
1387
+ return new Promise((resolve) => {
1388
+ const requestId = nanoid(12);
1389
+ session.pendingApprovals.set(requestId, (decision) => {
1390
+ this.clearApprovalTimer(requestId);
1391
+ // Main replies with { approved:true, answer:<json string> } (reusing the
1392
+ // ApprovalResult shape) or a raw json string. Parse → typed result.
1393
+ let raw;
1394
+ if (decision && typeof decision === "object" && "approved" in decision) {
1395
+ const r = decision;
1396
+ raw = r.approved ? r.answer : undefined;
1397
+ }
1398
+ else if (typeof decision === "string") {
1399
+ raw = decision;
1400
+ }
1401
+ if (raw === undefined) {
1402
+ resolve({ ok: false, detail: "browser action declined or unavailable" });
1403
+ return;
1404
+ }
1405
+ try {
1406
+ resolve(JSON.parse(raw));
1407
+ }
1408
+ catch {
1409
+ resolve({ ok: false, detail: "malformed browser action result" });
1410
+ }
1411
+ });
1412
+ const timer = setTimeout(() => {
1413
+ if (session.pendingApprovals.has(requestId)) {
1414
+ session.pendingApprovals.delete(requestId);
1415
+ this.approvalTimers.delete(requestId);
1416
+ resolve({ ok: false, detail: "browser action timed out" });
1417
+ }
1418
+ }, AgentServer.APPROVAL_TIMEOUT_MS);
1419
+ this.approvalTimers.set(requestId, timer);
1420
+ this.notify(Methods.ApprovalRequest, {
1421
+ sessionId,
1422
+ requestId,
1423
+ request: {
1424
+ toolName: "__browser_action__",
1425
+ args: { action, ...payload },
1426
+ description: `browser:${action}`,
1427
+ riskLevel: "low",
1428
+ },
1429
+ });
1430
+ });
1431
+ }
1432
+ /**
1433
+ * Ask the client (Electron main) to inject a cookie credential into the
1434
+ * built-in browser. Same pendingApprovals channel as browser actions; main
1435
+ * calls restoreCookiesToBrowser and replies with a JSON result string we
1436
+ * parse into { ok, count?, error? }. Degrades to ok:false on timeout/malformed.
1437
+ */
1438
+ requestCredentialInjectForSession(session, sessionId, credentialId) {
1439
+ return new Promise((resolve) => {
1440
+ const requestId = nanoid(12);
1441
+ session.pendingApprovals.set(requestId, (decision) => {
1442
+ this.clearApprovalTimer(requestId);
1443
+ let raw;
1444
+ if (decision && typeof decision === "object" && "approved" in decision) {
1445
+ const r = decision;
1446
+ raw = r.approved ? r.answer : undefined;
1447
+ }
1448
+ else if (typeof decision === "string") {
1449
+ raw = decision;
1450
+ }
1451
+ if (raw === undefined) {
1452
+ resolve({ ok: false, error: "credential inject declined or unavailable" });
1453
+ return;
1454
+ }
1455
+ try {
1456
+ resolve(JSON.parse(raw));
1457
+ }
1458
+ catch {
1459
+ resolve({ ok: false, error: "malformed credential inject result" });
1460
+ }
1461
+ });
1462
+ const timer = setTimeout(() => {
1463
+ if (session.pendingApprovals.has(requestId)) {
1464
+ session.pendingApprovals.delete(requestId);
1465
+ this.approvalTimers.delete(requestId);
1466
+ resolve({ ok: false, error: "credential inject timed out" });
1467
+ }
1468
+ }, AgentServer.APPROVAL_TIMEOUT_MS);
1469
+ this.approvalTimers.set(requestId, timer);
1470
+ this.notify(Methods.ApprovalRequest, {
1471
+ sessionId,
1472
+ requestId,
1473
+ request: {
1474
+ toolName: "__credential_action__",
1475
+ args: { action: "injectCookie", credentialId },
1476
+ description: `credential:inject:${credentialId}`,
1477
+ riskLevel: "low",
1478
+ },
1479
+ });
1480
+ });
1481
+ }
934
1482
  requestAskUserFromClient(question, opts) {
935
1483
  return new Promise((resolve) => {
936
1484
  const requestId = nanoid(12);
@@ -958,6 +1506,8 @@ export class AgentServer {
958
1506
  args.options = opts.options;
959
1507
  if (opts?.multiSelect !== undefined)
960
1508
  args.multiSelect = opts.multiSelect;
1509
+ if (opts?.optionsOnly !== undefined)
1510
+ args.optionsOnly = opts.optionsOnly;
961
1511
  this.notify(Methods.ApprovalRequest, {
962
1512
  requestId,
963
1513
  request: {
@@ -1036,6 +1586,26 @@ export class AgentServer {
1036
1586
  this.approvalTimers.delete(requestId);
1037
1587
  }
1038
1588
  }
1589
+ /**
1590
+ * Resolve all of a chat session's pending approvals as cancelled and clear
1591
+ * the matching server-side approval timers. Used by handleCancel's
1592
+ * per-session path so a Stop while a tool is awaiting approval doesn't leave
1593
+ * the tool hanging until APPROVAL_TIMEOUT_MS (and leak the timer). Each
1594
+ * requestId in the session map has a same-keyed entry in this.approvalTimers
1595
+ * (see requestAskUserForSession / makeBrowserBridge).
1596
+ */
1597
+ cancelSessionApprovals(session) {
1598
+ for (const [requestId, resolve] of session.pendingApprovals) {
1599
+ this.clearApprovalTimer(requestId);
1600
+ try {
1601
+ resolve({ approved: false, reason: "cancelled" });
1602
+ }
1603
+ catch {
1604
+ /* a resolver must never break cancel cleanup */
1605
+ }
1606
+ }
1607
+ session.pendingApprovals.clear();
1608
+ }
1039
1609
  clearAllApprovalTimers() {
1040
1610
  for (const timer of this.approvalTimers.values()) {
1041
1611
  clearTimeout(timer);