@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
@@ -5,6 +5,7 @@
5
5
  * pre_check → model_call → post_check → tool_exec → context_mgmt → hook_notify → next turn
6
6
  */
7
7
  import { initialTurnState, newTurnId } from "./turn-state.js";
8
+ import { formatFriendlyError } from "./friendly-error.js";
8
9
  import { wrapHookMessages } from "../hooks/inject.js";
9
10
  import { ContextLimitError } from "../exceptions.js";
10
11
  import { logger } from "../logging/logger.js";
@@ -15,7 +16,8 @@ import { isTruncatedStop } from "../llm/stop-reason.js";
15
16
  import { isAbortError } from "../llm/client-base.js";
16
17
  import { crossedReactiveThreshold } from "./reactive-threshold.js";
17
18
  import { COMPLETE_GOAL_TOOL_NAME } from "../tool-system/builtin/complete-goal.js";
18
- import { createGoalBudgetTracker, recordGoalUsage, goalBudgetExceeded, } from "./goal.js";
19
+ import { CANCEL_GOAL_TOOL_NAME } from "../tool-system/builtin/cancel-goal.js";
20
+ import { createGoalBudgetTracker, recordGoalUsage, goalBudgetExceeded, applyGoalExtension, limitProximity, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "./goal.js";
19
21
  /**
20
22
  * 把一个 ToolResult 映射成发给 LLM 的 tool_result ContentBlock。
21
23
  * 有 contentBlocks(view_image 的图片块)就原样用作 content;否则
@@ -54,7 +56,93 @@ export class TurnLoop {
54
56
  * loop forces a stop so a stuck goal can't loop forever.
55
57
  */
56
58
  stopBlockCount = 0;
57
- constructor(deps, config) {
59
+ /**
60
+ * Run-scoped goal budget tracker (Goal mode). Hoisted to an instance field
61
+ * (not a run() local) so extend() can bump its budgets mid-run. Null when no
62
+ * goal or between runs.
63
+ */
64
+ goalTracker = null;
65
+ /**
66
+ * Whether an approaching_limit marker was already emitted for the current
67
+ * ceiling. Reset when the run advances past the moment (a fresh extension, or
68
+ * the goal completing) so the next approach re-announces. Prevents the marker
69
+ * being re-emitted every turn while still within the approach threshold.
70
+ */
71
+ approachAnnounced = false;
72
+ /**
73
+ * Extend the in-flight run's limits (TODO 3.1 — 运行中续轮/加预算). Mutates
74
+ * the maxTurns ceiling, the maxStopBlocks cap, and/or the live goal budgets;
75
+ * the loop re-reads all of them each turn so the change takes effect on the
76
+ * next iteration. No-op for fields not supplied. Returns the resulting
77
+ * effective limits.
78
+ */
79
+ extend(opts) {
80
+ const elapsedMs = this.goalTracker ? Date.now() - this.goalTracker.startedAtMs : 0;
81
+ const next = applyGoalExtension(this.config.maxTurns, this.goalTracker?.goal, this.goalTracker?.tokensUsed ?? 0, elapsedMs, opts);
82
+ this.config = { ...this.config, maxTurns: next.maxTurns };
83
+ if (this.goalTracker) {
84
+ // Replace the goal object rather than mutating its fields: the tracker's
85
+ // goal may be a shared/frozen reference, and goalBudgetExceeded reads
86
+ // tracker.goal.{tokenBudget,timeBudgetMs} live, so a fresh object with the
87
+ // new caps takes effect on the next turn either way.
88
+ this.goalTracker.goal = {
89
+ ...this.goalTracker.goal,
90
+ tokenBudget: next.tokenBudget,
91
+ timeBudgetMs: next.timeBudgetMs,
92
+ };
93
+ }
94
+ // Raise the consecutive-stop-block cap — for a re-blocked goal this is the
95
+ // limit that actually bites, so an extend that only bumped maxTurns/budgets
96
+ // couldn't keep it going. Resolve the current cap the same way the loop does.
97
+ const curCap = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
98
+ const nextCap = typeof opts.addStopBlocks === "number" && opts.addStopBlocks > 0
99
+ ? curCap + Math.floor(opts.addStopBlocks)
100
+ : curCap;
101
+ this.config = { ...this.config, maxStopBlocks: nextCap };
102
+ // ANY extension resets the consecutive stop-block streak: the user just
103
+ // asked to keep going, so a goal that was repeatedly re-blocked shouldn't be
104
+ // immediately re-capped. (Previously only addTurns reset it, leaving a
105
+ // budget-only extension unable to un-stick a capped goal.)
106
+ const extended = (opts.addTurns ?? 0) > 0 ||
107
+ (opts.addStopBlocks ?? 0) > 0 ||
108
+ (opts.addTokenBudget ?? 0) > 0 ||
109
+ (opts.addTimeBudgetMs ?? 0) > 0;
110
+ if (extended) {
111
+ this.stopBlockCount = 0;
112
+ // Let the next approach re-announce against the raised ceilings.
113
+ this.approachAnnounced = false;
114
+ }
115
+ return { ...next, maxStopBlocks: nextCap };
116
+ }
117
+ /**
118
+ * Goal mode only: if the run is nearing EITHER stop ceiling (maxTurns or
119
+ * maxStopBlocks) and we haven't announced it yet, emit one approaching_limit
120
+ * marker so the UI can offer a "再续" button while the run is still live.
121
+ * Watches both limits because a re-blocked goal hits the stop-block cap long
122
+ * before maxTurns. Idempotent within an approach window via approachAnnounced.
123
+ */
124
+ maybeAnnounceApproachingLimit() {
125
+ if (!this.config.goal || this.approachAnnounced)
126
+ return;
127
+ const cap = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
128
+ const prox = limitProximity(this.turnCount, this.config.maxTurns, this.stopBlockCount, cap);
129
+ if (!prox.approaching)
130
+ return;
131
+ this.approachAnnounced = true;
132
+ this.config.onStream?.({
133
+ type: "goal_progress",
134
+ status: "approaching_limit",
135
+ round: this.stopBlockCount,
136
+ turnsRemaining: prox.turnsRemaining,
137
+ stopBlocksRemaining: prox.stopBlocksRemaining,
138
+ nearest: prox.nearest,
139
+ });
140
+ }
141
+ constructor(deps,
142
+ // Not readonly: extend() bumps maxTurns mid-run (TODO 3.1), and the
143
+ // constructor below rewrites onStream. The loop reads config fields fresh
144
+ // each turn so mutations take effect on the next iteration.
145
+ config) {
58
146
  this.deps = deps;
59
147
  this.config = config;
60
148
  // Wrap onStream so a single throwing handler can't silently break
@@ -95,11 +183,29 @@ export class TurnLoop {
95
183
  * `decision` / `stop`. Use this instead of `deps.hooks.emit` directly so
96
184
  * every emit carries the same context envelope.
97
185
  */
186
+ /**
187
+ * Persist a turn_stopped marker on a user Stop so a resume can rebuild the
188
+ * renderer's "你在 Ns 后停止了" line (the in-memory turn_end is lost on reload,
189
+ * which makes the interrupted turn fold behind the process-card header).
190
+ * Skipped for sub-agents — their signal.aborted is usually a parent abort,
191
+ * not a user Stop, and sub-agent turns aren't shown as interruptible. The
192
+ * underlying append is idempotent, so calling this at several abort returns
193
+ * for one Stop still writes a single marker.
194
+ */
195
+ markStopped() {
196
+ if (this.deps.isSubAgent === true)
197
+ return;
198
+ this.deps.transcript.appendTurnStopped();
199
+ }
98
200
  async emitHook(event, data = {}) {
99
201
  return this.deps.hooks.emit(event, {
100
202
  ...data,
101
203
  isSubAgent: this.deps.isSubAgent === true,
102
204
  sessionId: this.deps.sessionId,
205
+ // The run's abort signal, so handlers that make their own LLM calls
206
+ // (e.g. the Goal stop-judge) can be cut short when the user hits Stop
207
+ // mid-call instead of blocking until the sub-call returns.
208
+ signal: this.config.signal,
103
209
  });
104
210
  }
105
211
  /**
@@ -132,9 +238,10 @@ export class TurnLoop {
132
238
  this.lastCtxEmit = ctx;
133
239
  this.config.onStream({ type: "usage_update", promptTokens: ctx });
134
240
  }
135
- emitCtxFromUsage(promptTokens, messages) {
241
+ emitCtxFromUsage(usage, messages) {
136
242
  if (!this.config.onStream)
137
243
  return;
244
+ const promptTokens = usage.promptTokens;
138
245
  // Reverse-derive overhead from this authoritative reading so the next
139
246
  // estimate-based emit (post-tool-result) is calibrated.
140
247
  const msgsEstimate = estimateTokens(messages);
@@ -150,7 +257,21 @@ export class TurnLoop {
150
257
  if (promptTokens === this.lastCtxEmit)
151
258
  return;
152
259
  this.lastCtxEmit = promptTokens;
153
- this.config.onStream({ type: "usage_update", promptTokens });
260
+ // Forward the provider's cache counts so the UI can show a hit rate. Only
261
+ // attach fields the provider actually reported — a spread keeps them off
262
+ // the event entirely when undefined, so the renderer can tell "no cache
263
+ // info this turn" from "0 cached". Estimate-path emits don't call this and
264
+ // so carry no cache fields (correct: an estimate has no cache reading).
265
+ this.config.onStream({
266
+ type: "usage_update",
267
+ promptTokens,
268
+ ...(usage.cacheReadTokens !== undefined
269
+ ? { cacheReadTokens: usage.cacheReadTokens }
270
+ : {}),
271
+ ...(usage.cacheCreationTokens !== undefined
272
+ ? { cacheCreationTokens: usage.cacheCreationTokens }
273
+ : {}),
274
+ });
154
275
  }
155
276
  /**
156
277
  * Run the multi-turn agent loop until completion.
@@ -163,9 +284,12 @@ export class TurnLoop {
163
284
  // wall-clock start now and accumulates prompt+completion tokens across
164
285
  // every turn; the guardrail below force-stops the run once any configured
165
286
  // budget is blown — the unattended-safety backstop.
166
- const goalTracker = this.config.goal
287
+ this.goalTracker = this.config.goal
167
288
  ? createGoalBudgetTracker(this.config.goal, Date.now())
168
289
  : null;
290
+ const goalTracker = this.goalTracker;
291
+ // Fresh run: re-arm the approaching-limit announcement.
292
+ this.approachAnnounced = false;
169
293
  // run() must never reject: the engine's post-run bookkeeping (saveState
170
294
  // with the terminal reason, on_session_end hook) runs AFTER this call and
171
295
  // outside the engine's try, so a throw here would leave the session
@@ -187,8 +311,22 @@ export class TurnLoop {
187
311
  // boundary between turns. (Mirrors Claude Code's query.ts, where the
188
312
  // aborted check short-circuits before re-entering the streaming loop.)
189
313
  if (this.config.signal?.aborted) {
314
+ this.markStopped();
190
315
  return { text: finalText, reason: "aborted_streaming", messages };
191
316
  }
317
+ // Step-gap steering: messages the host queued via Engine.enqueueSteer
318
+ // while the previous step was running. Splice them in as user messages so
319
+ // they join THIS step's request — no abort, no lost in-flight work. Same
320
+ // loop-top user-push pattern as turnStartInjection / turn-limit warnings
321
+ // below. Push to transcript too so they persist + survive resume.
322
+ const steered = this.deps.consumeSteer?.() ?? [];
323
+ for (const { id, text } of steered) {
324
+ if (!text)
325
+ continue;
326
+ messages.push({ role: "user", content: text });
327
+ this.deps.transcript.appendMessage("user", text);
328
+ this.config.onStream?.({ type: "steer_injected", text, id });
329
+ }
192
330
  const state = initialTurnState(this.turnCount);
193
331
  // Per-turn correlation ID. Every log written through `tlog` (or any
194
332
  // child derived from it) is stamped with `turn` + `turnId`, so
@@ -211,7 +349,10 @@ export class TurnLoop {
211
349
  if (turnStartInjection) {
212
350
  messages.push(turnStartInjection);
213
351
  }
214
- // Approaching max turns: inject a warning so the model can wrap up
352
+ // Approaching max turns: inject a warning so the model can wrap up.
353
+ // (Model-facing only — the user-facing "再续" marker is handled by
354
+ // maybeAnnounceApproachingLimit below, which also watches the stop-block
355
+ // cap, the limit a re-blocked goal actually hits first.)
215
356
  const turnsRemaining = this.config.maxTurns - this.turnCount;
216
357
  if (turnsRemaining === 2) {
217
358
  messages.push({
@@ -220,6 +361,13 @@ export class TurnLoop {
220
361
  "Start wrapping up your work and prepare a summary of what you've accomplished and what remains to be done.</system-reminder>",
221
362
  });
222
363
  }
364
+ else if (turnsRemaining === 1) {
365
+ messages.push({
366
+ role: "user",
367
+ content: "<system-reminder>Warning: you have only 1 turn remaining before the turn limit is reached. " +
368
+ "Wrap up your work now — your next turn will be your last.</system-reminder>",
369
+ });
370
+ }
223
371
  else if (turnsRemaining === 0) {
224
372
  messages.push({
225
373
  role: "user",
@@ -227,6 +375,9 @@ export class TurnLoop {
227
375
  "Do NOT call any tools. Summarize what you have accomplished and list any remaining work.</system-reminder>",
228
376
  });
229
377
  }
378
+ // Goal mode: announce once when nearing EITHER stop ceiling (turns or
379
+ // stop-blocks) so the UI can offer a "再续" button while still live.
380
+ this.maybeAnnounceApproachingLimit();
230
381
  // Pre-check: context management (async — may trigger LLM summarization)
231
382
  messages = await this.deps.contextManager.manageAsync(messages);
232
383
  // manageAsync can itself issue an LLM summarization call lasting several
@@ -234,6 +385,7 @@ export class TurnLoop {
234
385
  // proceeding into the (expensive) main model call. Belt to the loop-top
235
386
  // brace: this catches an abort that landed *inside* context management.
236
387
  if (this.config.signal?.aborted) {
388
+ this.markStopped();
237
389
  return { text: finalText, reason: "aborted_streaming", messages };
238
390
  }
239
391
  // No pre-llm ctx emit here: the messages-only estimate would be ~16k
@@ -284,7 +436,7 @@ export class TurnLoop {
284
436
  }
285
437
  catch (retryErr) {
286
438
  if (!(retryErr instanceof ContextLimitError)) {
287
- this.config.onStream?.({ type: "error", error: retryErr.message });
439
+ this.config.onStream?.({ type: "error", error: formatFriendlyError(retryErr) });
288
440
  return { text: finalText, reason: "model_error", messages };
289
441
  }
290
442
  }
@@ -298,15 +450,27 @@ export class TurnLoop {
298
450
  return { text: finalText, reason: "prompt_too_long", messages };
299
451
  }
300
452
  }
453
+ else if (isAbortError(err) || this.config.signal?.aborted) {
454
+ // User pressed Stop: the in-flight model call rejected with an
455
+ // AbortError. This is NOT a failure — the UI already shows the
456
+ // "你在 Ns 后停止了" line, so emitting an error event would stack a
457
+ // spurious red "Error:" block on top of an intentional stop.
458
+ // Persist a turn_stopped marker so a resume can rebuild that line
459
+ // (the renderer's turn_end is in-memory only; without this the
460
+ // interrupted turn folds behind the process-card header on reload).
461
+ this.patchOrphanedToolUses(messages);
462
+ this.markStopped();
463
+ return { text: finalText, reason: "aborted_streaming", messages };
464
+ }
301
465
  else {
302
466
  this.patchOrphanedToolUses(messages);
303
- this.config.onStream?.({ type: "error", error: err.message });
467
+ this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
304
468
  return { text: finalText, reason: "model_error", messages };
305
469
  }
306
470
  }
307
471
  // UI ctx bar: prefer the provider's authoritative promptTokens.
308
472
  if (response.usage?.promptTokens !== undefined) {
309
- this.emitCtxFromUsage(response.usage.promptTokens, messages);
473
+ this.emitCtxFromUsage(response.usage, messages);
310
474
  }
311
475
  // Feed actual token usage back to the context manager so subsequent
312
476
  // compaction decisions use hybrid (actual + delta) estimation rather than
@@ -373,7 +537,7 @@ export class TurnLoop {
373
537
  }
374
538
  // After any continuation, send latest usage so ctx bar reflects real context
375
539
  if (response.usage?.promptTokens !== undefined) {
376
- this.emitCtxFromUsage(response.usage.promptTokens, messages);
540
+ this.emitCtxFromUsage(response.usage, messages);
377
541
  }
378
542
  // Goal-mode run-scoped accounting: add this turn's total token usage
379
543
  // (prompt + completion) to the running total. Done after continuation so
@@ -384,6 +548,7 @@ export class TurnLoop {
384
548
  }
385
549
  // Aborted?
386
550
  if (this.config.signal?.aborted) {
551
+ this.markStopped();
387
552
  return { text: finalText, reason: "aborted_streaming", messages };
388
553
  }
389
554
  // Accumulate text
@@ -427,7 +592,7 @@ export class TurnLoop {
427
592
  // handler returning continueSession=true injects its messages and
428
593
  // we run another turn instead of returning. Bounded by
429
594
  // maxStopBlocks (consecutive) and the outer maxTurns ceiling.
430
- const maxStopBlocks = this.config.maxStopBlocks ?? 8;
595
+ const maxStopBlocks = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
431
596
  const stopHook = await this.emitHook("on_stop", {
432
597
  goal: this.config.goal,
433
598
  finalText,
@@ -447,6 +612,10 @@ export class TurnLoop {
447
612
  round: this.stopBlockCount,
448
613
  gaps: goalVerdict?.gaps || undefined,
449
614
  });
615
+ // The streak just grew — we may now be nearing the stop-block cap.
616
+ // Announce here (before the next turn's top check) so the "再续"
617
+ // button shows up against the limit that's actually about to bite.
618
+ this.maybeAnnounceApproachingLimit();
450
619
  const injection = wrapHookMessages(stopHook.messages);
451
620
  if (injection) {
452
621
  messages.push(injection);
@@ -504,6 +673,12 @@ export class TurnLoop {
504
673
  // Tool execution phase
505
674
  tlog.info("turn.tool_use", { cat: "turn", tools: response.toolCalls.map((t) => t.toolName) });
506
675
  const toolCalls = response.toolCalls.slice(0, this.config.maxToolCallsPerTurn);
676
+ // Per-turn cap: any calls beyond maxToolCallsPerTurn are NOT executed and
677
+ // NOT added to the assistant message below, so the model never sees a
678
+ // result for them. Without a heads-up it assumes all ran (acting on
679
+ // tool output that never happened). Remember the dropped ones so we can
680
+ // inject a reminder after the executed batch's results. (B-3)
681
+ const droppedToolCalls = response.toolCalls.slice(this.config.maxToolCallsPerTurn);
507
682
  // Add assistant message with tool_use blocks to messages
508
683
  const assistantBlocks = [];
509
684
  if (response.text) {
@@ -537,19 +712,51 @@ export class TurnLoop {
537
712
  this.deps.transcript.appendToolResult(result.id, result.toolName, result.result, result.error);
538
713
  this.config.onStream?.({ type: "tool_result", result });
539
714
  }
540
- // Fire-and-forget tool use summary (non-blocking)
715
+ // Fire-and-forget tool use summary (non-blocking). The whole chain is
716
+ // best-effort observability — a thrown onStream handler, a failed dynamic
717
+ // import, or a rejecting summarize must never surface as an unhandled
718
+ // rejection (Node can treat those as fatal). Swallow at the tail. (B-2)
541
719
  if (this.config.onStream) {
542
- import("./tool-summary.js").then(({ generateToolUseSummary }) => {
720
+ void import("./tool-summary.js")
721
+ .then(({ generateToolUseSummary }) => {
543
722
  if (!this.deps.model.summarize)
544
723
  return;
545
- generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
724
+ return generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
546
725
  if (summary) {
547
726
  this.config.onStream?.({ type: "tool_summary", summary });
548
727
  }
549
728
  });
729
+ })
730
+ .catch((err) => {
731
+ this.currentTurnLog.warn("tool_summary.dispatch_failed", {
732
+ cat: "turn",
733
+ error: err.message,
734
+ });
550
735
  });
551
736
  }
552
737
  messages.push({ role: "user", content: resultBlocks });
738
+ // B-3: tell the model which of its requested tool calls were dropped by
739
+ // the per-turn cap so it can re-issue them, instead of silently assuming
740
+ // they ran. Appended to the same user message that carries the results.
741
+ if (droppedToolCalls.length > 0) {
742
+ tlog.info("turn.tool_calls_capped", {
743
+ cat: "turn",
744
+ executed: toolCalls.length,
745
+ dropped: droppedToolCalls.length,
746
+ cap: this.config.maxToolCallsPerTurn,
747
+ });
748
+ const droppedNames = droppedToolCalls.map((t) => t.toolName).join(", ");
749
+ // Separate user message (not folded into the tool_result blocks) so the
750
+ // OpenAI converter — which lifts tool_results into standalone role:tool
751
+ // messages — keeps the reminder as a plain user turn after them.
752
+ messages.push({
753
+ role: "user",
754
+ content: `<system-reminder>Only the first ${toolCalls.length} of your ${response.toolCalls.length} ` +
755
+ `tool calls ran this turn (per-turn limit is ${this.config.maxToolCallsPerTurn}). ` +
756
+ `These were NOT executed and produced no result — do NOT assume they ran: ${droppedNames}. ` +
757
+ `Re-issue the ones you still need in the next turn.</system-reminder>`,
758
+ });
759
+ }
553
760
  // Tool results just pushed; recompute ctx so the bar updates *before*
554
761
  // the next model round-trip — large tool outputs can move it sharply.
555
762
  this.emitCtxFromMessages(messages);
@@ -557,10 +764,27 @@ export class TurnLoop {
557
764
  // it has DECLARED the goal done — short-circuit to "completed" WITHOUT
558
765
  // running the judge hook. The tool's result is already in `messages`
559
766
  // above so the summary lands in the transcript. Reset the stop-block
560
- // counter so a prior judge-driven block streak doesn't leak out.
767
+ // counter so a prior judge-driven block streak doesn't leak out. Also
768
+ // clear the PERSISTED goal so a later bare send doesn't re-inherit a goal
769
+ // the model just declared finished (the judge's onMet does this when IT
770
+ // decides; a self-report must do the same, else the goal outlives its
771
+ // completion and re-arms every follow-up turn).
561
772
  if (goalTracker && toolCalls.some((tc) => tc.toolName === COMPLETE_GOAL_TOOL_NAME)) {
562
773
  tlog.info("turn.goal_self_reported_complete", { cat: "goal" });
563
774
  this.stopBlockCount = 0;
775
+ this.deps.clearPersistedGoal?.();
776
+ return { text: finalText, reason: "completed", messages };
777
+ }
778
+ // Goal mode: user-initiated cancellation. cancel_goal is the "strong
779
+ // intent" escape hatch — honor it ONLY when confirm===true (the guard the
780
+ // tool advertises). A confirmed cancel stops the run AND clears the
781
+ // persisted goal so it never re-arms; an unconfirmed call is a no-op here
782
+ // (the tool's own result string already told the model it was ignored).
783
+ const confirmedCancel = toolCalls.some((tc) => tc.toolName === CANCEL_GOAL_TOOL_NAME && tc.args?.confirm === true);
784
+ if (goalTracker && confirmedCancel) {
785
+ tlog.info("turn.goal_user_cancelled", { cat: "goal" });
786
+ this.stopBlockCount = 0;
787
+ this.deps.clearPersistedGoal?.();
564
788
  return { text: finalText, reason: "completed", messages };
565
789
  }
566
790
  // Token budget check
@@ -632,12 +856,19 @@ export class TurnLoop {
632
856
  // engine's post-run saveState records model_error instead of leaving
633
857
  // the session stuck at "active".
634
858
  this.patchOrphanedToolUses(messages);
859
+ // A user-initiated Stop also bubbles here (an AbortError thrown from the
860
+ // per-turn scaffolding). Treat it as a clean abort, not a model_error —
861
+ // no error event, so the UI shows only the "你停止了本轮" line.
862
+ if (isAbortError(err) || this.config.signal?.aborted) {
863
+ this.markStopped();
864
+ return { text: finalText, reason: "aborted_streaming", messages };
865
+ }
635
866
  this.currentTurnLog.error("turn.unhandled_error", {
636
867
  cat: "turn",
637
868
  error: err.message,
638
869
  stack: err.stack?.split("\n").slice(0, 4).join("\n"),
639
870
  });
640
- this.config.onStream?.({ type: "error", error: err.message });
871
+ this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
641
872
  return { text: finalText, reason: "model_error", messages };
642
873
  }
643
874
  // Max turns reached — do one final summarization call (no tools)
@@ -719,7 +950,15 @@ export class TurnLoop {
719
950
  // non-streaming call here re-sends the whole request the user just
720
951
  // aborted. Propagate so the run unwinds cleanly instead of doing
721
952
  // more work after cancellation.
722
- if (isAbortError(err))
953
+ //
954
+ // Check the run signal too, not only isAbortError(err): when a streaming
955
+ // request is aborted the error sometimes surfaces as a generic
956
+ // `Error: Request was aborted.` whose `name` is NOT "AbortError"
957
+ // (wrapping strips it), so isAbortError misses it and we'd fall back —
958
+ // re-issuing the cancelled request, which then throws "Aborted before
959
+ // LLM request" from withRetry and surfaces as a scary error on what was
960
+ // really just a cancel. The signal is the authoritative cancel source.
961
+ if (isAbortError(err) || this.config.signal?.aborted)
723
962
  throw err;
724
963
  // Streaming might have partially emitted — send tombstone to revoke
725
964
  this.config.onStream?.({ type: "tombstone", messageId: `turn_${this.turnCount}` });
@@ -740,45 +979,57 @@ export class TurnLoop {
740
979
  * Prevents model confusion on the next turn.
741
980
  */
742
981
  patchOrphanedToolUses(messages) {
743
- // Find the last assistant message with tool_use blocks
744
- for (let i = messages.length - 1; i >= 0; i--) {
745
- const msg = messages[i];
746
- if (msg.role !== "assistant" || !Array.isArray(msg.content))
982
+ // Pre-compute every answered tool_use id across the WHOLE array, then scan
983
+ // forward and patch EVERY assistant message with a gap not just the most
984
+ // recent one. The old version scanned backward and returned at the first
985
+ // all-answered assistant message, so when several earlier turns each left
986
+ // orphaned tool_uses (multi-turn API failures) only the latest got patched
987
+ // and the rest stayed unpaired → a 400 on the next call / on resume.
988
+ const answeredIds = new Set();
989
+ for (const msg of messages) {
990
+ if (!Array.isArray(msg.content))
747
991
  continue;
748
- const toolUseIds = [];
749
992
  for (const block of msg.content) {
750
- if (block.type === "tool_use" && block.id)
751
- toolUseIds.push(block.id);
993
+ if (block.type === "tool_result" && block.tool_use_id) {
994
+ answeredIds.add(block.tool_use_id);
995
+ }
752
996
  }
753
- if (toolUseIds.length === 0)
997
+ }
998
+ let totalPatched = 0;
999
+ for (let i = 0; i < messages.length; i++) {
1000
+ const msg = messages[i];
1001
+ if (msg.role !== "assistant" || !Array.isArray(msg.content))
754
1002
  continue;
755
- // Check if all tool_use IDs have corresponding tool_results
756
- const answeredIds = new Set();
757
- for (let j = i + 1; j < messages.length; j++) {
758
- const rm = messages[j];
759
- if (!Array.isArray(rm.content))
760
- continue;
761
- for (const block of rm.content) {
762
- if (block.type === "tool_result" && block.tool_use_id) {
763
- answeredIds.add(block.tool_use_id);
764
- }
1003
+ const orphanedIds = [];
1004
+ for (const block of msg.content) {
1005
+ if (block.type === "tool_use" && block.id && !answeredIds.has(block.id)) {
1006
+ orphanedIds.push(block.id);
765
1007
  }
766
1008
  }
767
- const orphanedIds = toolUseIds.filter((id) => !answeredIds.has(id));
768
1009
  if (orphanedIds.length === 0)
769
- return;
770
- // Inject synthetic error results
1010
+ continue;
1011
+ // Synthetic error results, spliced in RIGHT AFTER the offending assistant
1012
+ // message (not appended to the end) so each tool_use is paired in place.
1013
+ // is_error must be set: the Anthropic provider only emits is_error when
1014
+ // the flag is present, otherwise the model reads the cancellation as plain
1015
+ // text output and assumes the tool succeeded.
771
1016
  const errorBlocks = orphanedIds.map((id) => ({
772
1017
  type: "tool_result",
773
1018
  tool_use_id: id,
774
1019
  content: "Error: Tool execution was cancelled because the previous API call failed.",
1020
+ is_error: true,
775
1021
  }));
776
- messages.push({ role: "user", content: errorBlocks });
1022
+ messages.splice(i + 1, 0, { role: "user", content: errorBlocks });
1023
+ for (const id of orphanedIds)
1024
+ answeredIds.add(id);
1025
+ totalPatched += orphanedIds.length;
1026
+ i += 1; // skip the message we just spliced in
1027
+ }
1028
+ if (totalPatched > 0) {
777
1029
  this.currentTurnLog.warn("turn.patched_orphaned_tool_uses", {
778
1030
  cat: "turn",
779
- count: orphanedIds.length,
1031
+ count: totalPatched,
780
1032
  });
781
- return; // Only patch the most recent orphaned set
782
1033
  }
783
1034
  }
784
1035
  }