@cjhyy/code-shell-core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (621) hide show
  1. package/README.md +36 -23
  2. package/dist/agent/agent-definition-registry.d.ts +2 -2
  3. package/dist/agent/agent-definition-registry.js +47 -10
  4. package/dist/agent/agent-definition.d.ts +5 -0
  5. package/dist/agent/agent-definition.js +14 -0
  6. package/dist/automation/index.d.ts +7 -4
  7. package/dist/automation/index.js +4 -2
  8. package/dist/automation/runner.js +3 -1
  9. package/dist/automation/scheduler.d.ts +57 -1
  10. package/dist/automation/scheduler.js +123 -23
  11. package/dist/automation/store.d.ts +6 -2
  12. package/dist/automation/store.js +7 -3
  13. package/dist/capabilities/index.d.ts +109 -0
  14. package/dist/capabilities/index.js +80 -0
  15. package/dist/capability-control/disabled-lists.d.ts +2 -1
  16. package/dist/capability-control/disabled-lists.js +3 -5
  17. package/dist/capability-control/overlay.d.ts +14 -1
  18. package/dist/capability-control/overlay.js +40 -0
  19. package/dist/capability-control/service.js +6 -4
  20. package/dist/cli/agent-server-stdio.d.ts +1 -1
  21. package/dist/cli/agent-server-stdio.js +66 -6
  22. package/dist/cli/agent-server-tcp.js +20 -5
  23. package/dist/context/compaction.d.ts +39 -3
  24. package/dist/context/compaction.js +151 -39
  25. package/dist/context/manager.d.ts +20 -4
  26. package/dist/context/manager.js +42 -7
  27. package/dist/context/token-counter.js +5 -3
  28. package/dist/context/tool-result-storage.d.ts +6 -0
  29. package/dist/context/tool-result-storage.js +25 -4
  30. package/dist/credentials/access.d.ts +13 -2
  31. package/dist/credentials/access.js +99 -4
  32. package/dist/credentials/index.d.ts +3 -2
  33. package/dist/credentials/index.js +2 -1
  34. package/dist/credentials/oauth.d.ts +9 -1
  35. package/dist/credentials/oauth.js +80 -1
  36. package/dist/credentials/store.d.ts +16 -2
  37. package/dist/credentials/store.js +33 -11
  38. package/dist/credentials/types.d.ts +62 -1
  39. package/dist/credentials/types.js +16 -1
  40. package/dist/credentials/use-credential-tool.js +15 -1
  41. package/dist/engine/auxiliary-pipeline.d.ts +40 -0
  42. package/dist/engine/auxiliary-pipeline.js +181 -0
  43. package/dist/{settings → engine}/disk-defaults.d.ts +2 -2
  44. package/dist/{settings → engine}/disk-defaults.js +5 -2
  45. package/dist/engine/dynamic-tool-defs.d.ts +4 -0
  46. package/dist/engine/dynamic-tool-defs.js +4 -0
  47. package/dist/engine/engine.d.ts +272 -184
  48. package/dist/engine/engine.js +1869 -1864
  49. package/dist/engine/file-history-hook.d.ts +11 -0
  50. package/dist/engine/file-history-hook.js +31 -0
  51. package/dist/engine/goal-judge-context.d.ts +9 -0
  52. package/dist/engine/goal-judge-context.js +234 -0
  53. package/dist/engine/injected-context-cache.d.ts +6 -0
  54. package/dist/engine/injected-context-cache.js +13 -0
  55. package/dist/engine/input-attachments.d.ts +1 -1
  56. package/dist/engine/model-facade.d.ts +3 -3
  57. package/dist/engine/model-facade.js +15 -14
  58. package/dist/engine/permission-controller.d.ts +55 -0
  59. package/dist/engine/permission-controller.js +148 -0
  60. package/dist/engine/prompt-cache-diagnostics.d.ts +58 -0
  61. package/dist/engine/prompt-cache-diagnostics.js +178 -0
  62. package/dist/engine/run-accounting.d.ts +41 -0
  63. package/dist/engine/run-accounting.js +126 -0
  64. package/dist/engine/run-context.d.ts +44 -0
  65. package/dist/engine/run-context.js +88 -0
  66. package/dist/engine/run-environment.d.ts +30 -0
  67. package/dist/engine/run-environment.js +77 -0
  68. package/dist/engine/run-finalize.d.ts +58 -0
  69. package/dist/engine/run-finalize.js +245 -0
  70. package/dist/engine/run-goal.d.ts +58 -0
  71. package/dist/engine/run-goal.js +188 -0
  72. package/dist/engine/run-image-input.d.ts +1 -1
  73. package/dist/engine/run-image-input.js +5 -3
  74. package/dist/engine/run-session-open.d.ts +41 -0
  75. package/dist/engine/run-session-open.js +186 -0
  76. package/dist/engine/run-setup.d.ts +42 -0
  77. package/dist/engine/run-setup.js +58 -0
  78. package/dist/engine/run-tooling.d.ts +125 -0
  79. package/dist/engine/run-tooling.js +283 -0
  80. package/dist/engine/run-types.d.ts +139 -0
  81. package/dist/engine/run-types.js +12 -0
  82. package/dist/engine/run-workspace.d.ts +50 -0
  83. package/dist/engine/run-workspace.js +114 -0
  84. package/dist/engine/session-title.d.ts +2 -1
  85. package/dist/engine/session-title.js +4 -1
  86. package/dist/engine/steer-queue.d.ts +1 -1
  87. package/dist/engine/subagent-spawner.d.ts +32 -0
  88. package/dist/engine/subagent-spawner.js +287 -0
  89. package/dist/engine/tool-summary.d.ts +2 -2
  90. package/dist/engine/tool-summary.js +2 -2
  91. package/dist/engine/turn-loop.d.ts +67 -10
  92. package/dist/engine/turn-loop.js +538 -97
  93. package/dist/engine/types.d.ts +35 -4
  94. package/dist/engine/types.js +1 -1
  95. package/dist/{engine/goal.d.ts → goal/lifecycle.d.ts} +80 -6
  96. package/dist/goal/lifecycle.js +482 -0
  97. package/dist/hooks/events.d.ts +26 -9
  98. package/dist/hooks/events.js +0 -3
  99. package/dist/hooks/goal-stop-hook.d.ts +28 -29
  100. package/dist/hooks/goal-stop-hook.js +344 -144
  101. package/dist/hooks/registry.js +4 -1
  102. package/dist/hooks/shell-runner.d.ts +12 -1
  103. package/dist/hooks/shell-runner.js +160 -9
  104. package/dist/index.d.ts +51 -110
  105. package/dist/index.extension.d.ts +51 -0
  106. package/dist/index.extension.js +39 -0
  107. package/dist/index.internal.d.ts +86 -0
  108. package/dist/index.internal.js +92 -0
  109. package/dist/index.js +37 -125
  110. package/dist/links/cli.d.ts +48 -0
  111. package/dist/links/cli.js +617 -0
  112. package/dist/links/http.d.ts +40 -0
  113. package/dist/links/http.js +166 -0
  114. package/dist/links/index.d.ts +4 -0
  115. package/dist/links/index.js +3 -0
  116. package/dist/links/link-action-tool.d.ts +5 -0
  117. package/dist/links/link-action-tool.js +241 -0
  118. package/dist/links/providers.d.ts +8 -0
  119. package/dist/links/providers.js +665 -0
  120. package/dist/links/types.d.ts +44 -0
  121. package/dist/llm/client-base.d.ts +7 -0
  122. package/dist/llm/client-base.js +72 -10
  123. package/dist/llm/model-pool.d.ts +1 -1
  124. package/dist/llm/model-pool.js +1 -1
  125. package/dist/llm/providers/anthropic.d.ts +1 -0
  126. package/dist/llm/providers/anthropic.js +9 -0
  127. package/dist/llm/providers/openai.d.ts +1 -0
  128. package/dist/llm/providers/openai.js +17 -0
  129. package/dist/llm/types.d.ts +12 -5
  130. package/dist/model-catalog/index.js +15 -2
  131. package/dist/model-catalog/resolve.d.ts +13 -1
  132. package/dist/model-catalog/resolve.js +37 -2
  133. package/dist/model-catalog/types.d.ts +74 -48
  134. package/dist/model-catalog/types.js +16 -5
  135. package/dist/model-catalog/upsert.d.ts +3 -1
  136. package/dist/model-catalog/upsert.js +9 -0
  137. package/dist/onboarding.d.ts +0 -1
  138. package/dist/onboarding.js +1 -22
  139. package/dist/panel-apps/bindings.d.ts +24 -0
  140. package/dist/panel-apps/bindings.js +69 -0
  141. package/dist/panel-apps/github-archive.d.ts +21 -0
  142. package/dist/panel-apps/github-archive.js +98 -0
  143. package/dist/panel-apps/index.d.ts +4 -0
  144. package/dist/panel-apps/index.js +4 -0
  145. package/dist/panel-apps/installer.d.ts +68 -0
  146. package/dist/panel-apps/installer.js +622 -0
  147. package/dist/panel-apps/manifest.d.ts +364 -0
  148. package/dist/panel-apps/manifest.js +189 -0
  149. package/dist/panel-apps/paths.d.ts +13 -0
  150. package/dist/panel-apps/paths.js +38 -0
  151. package/dist/panel-apps/registry.d.ts +51 -0
  152. package/dist/panel-apps/registry.js +99 -0
  153. package/dist/panel-apps/runtime.d.ts +80 -0
  154. package/dist/panel-apps/runtime.js +154 -0
  155. package/dist/plugins/gitOps.js +8 -1
  156. package/dist/plugins/installedPlugins.d.ts +2 -1
  157. package/dist/plugins/installedPlugins.js +146 -8
  158. package/dist/plugins/installer/codex/convertAgents.d.ts +4 -1
  159. package/dist/plugins/installer/codex/convertAgents.js +62 -8
  160. package/dist/plugins/installer/codex/convertCommands.d.ts +5 -5
  161. package/dist/plugins/installer/codex/convertCommands.js +35 -12
  162. package/dist/plugins/installer/codex/convertHooks.d.ts +10 -0
  163. package/dist/plugins/installer/codex/convertHooks.js +82 -0
  164. package/dist/plugins/installer/codex/convertSkills.js +41 -6
  165. package/dist/plugins/installer/install.d.ts +7 -1
  166. package/dist/plugins/installer/install.js +21 -87
  167. package/dist/plugins/installer/installFromArchive.d.ts +26 -4
  168. package/dist/plugins/installer/installFromArchive.js +44 -27
  169. package/dist/plugins/installer/installFromNpm.d.ts +31 -0
  170. package/dist/plugins/installer/installFromNpm.js +273 -0
  171. package/dist/plugins/installer/loadPluginAgents.js +17 -4
  172. package/dist/plugins/installer/loadPluginMcp.d.ts +14 -0
  173. package/dist/plugins/installer/loadPluginMcp.js +245 -35
  174. package/dist/plugins/installer/normalizeManifest.d.ts +13 -0
  175. package/dist/plugins/installer/normalizeManifest.js +306 -0
  176. package/dist/plugins/installer/npmTar.d.ts +13 -0
  177. package/dist/plugins/installer/npmTar.js +321 -0
  178. package/dist/plugins/installer/parseSource.d.ts +11 -0
  179. package/dist/plugins/installer/parseSource.js +48 -0
  180. package/dist/plugins/installer/preview.d.ts +101 -0
  181. package/dist/plugins/installer/preview.js +336 -0
  182. package/dist/plugins/installer/projectPluginSource.d.ts +14 -0
  183. package/dist/plugins/installer/projectPluginSource.js +140 -0
  184. package/dist/plugins/installer/types.d.ts +775 -0
  185. package/dist/plugins/installer/types.js +100 -0
  186. package/dist/plugins/installer/unzip.d.ts +11 -0
  187. package/dist/plugins/installer/unzip.js +123 -9
  188. package/dist/plugins/installer/update.d.ts +2 -2
  189. package/dist/plugins/installer/update.js +43 -4
  190. package/dist/plugins/loadPluginHooks.d.ts +19 -2
  191. package/dist/plugins/loadPluginHooks.js +97 -86
  192. package/dist/plugins/pluginAutomationTemplates.d.ts +18 -0
  193. package/dist/plugins/pluginAutomationTemplates.js +50 -0
  194. package/dist/plugins/pluginCatalog.d.ts +45 -0
  195. package/dist/plugins/pluginCatalog.js +87 -0
  196. package/dist/plugins/pluginCommandHook.d.ts +10 -4
  197. package/dist/plugins/pluginCommandHook.js +133 -18
  198. package/dist/plugins/pluginCommandsLoader.d.ts +19 -0
  199. package/dist/plugins/pluginCommandsLoader.js +181 -7
  200. package/dist/plugins/pluginContent.d.ts +10 -1
  201. package/dist/plugins/pluginContent.js +87 -30
  202. package/dist/plugins/pluginHookApproval.d.ts +30 -0
  203. package/dist/plugins/pluginHookApproval.js +160 -0
  204. package/dist/plugins/pluginHookIntegrity.d.ts +69 -0
  205. package/dist/plugins/pluginHookIntegrity.js +361 -0
  206. package/dist/plugins/pluginInstaller.js +71 -12
  207. package/dist/plugins/pluginMcpApproval.d.ts +19 -0
  208. package/dist/plugins/pluginMcpApproval.js +110 -0
  209. package/dist/plugins/pluginMcpIntegrity.d.ts +21 -0
  210. package/dist/plugins/pluginMcpIntegrity.js +74 -0
  211. package/dist/plugins/types.d.ts +30 -0
  212. package/dist/plugins/varRewrite.d.ts +9 -5
  213. package/dist/plugins/varRewrite.js +26 -17
  214. package/dist/preset/index.d.ts +11 -8
  215. package/dist/preset/index.js +62 -203
  216. package/dist/product/define.js +9 -3
  217. package/dist/product/types.d.ts +0 -2
  218. package/dist/profile/activation.d.ts +36 -0
  219. package/dist/profile/activation.js +57 -0
  220. package/dist/profile/catalog-store.d.ts +77 -0
  221. package/dist/profile/catalog-store.js +346 -0
  222. package/dist/profile/catalog.d.ts +54 -0
  223. package/dist/profile/catalog.js +152 -0
  224. package/dist/profile/index.d.ts +7 -0
  225. package/dist/profile/index.js +7 -0
  226. package/dist/profile/requirements.d.ts +76 -0
  227. package/dist/profile/requirements.js +117 -0
  228. package/dist/profile/resolve.d.ts +11 -0
  229. package/dist/profile/resolve.js +41 -0
  230. package/dist/profile/store.d.ts +17 -0
  231. package/dist/profile/store.js +167 -0
  232. package/dist/profile/types.d.ts +321 -0
  233. package/dist/profile/types.js +104 -0
  234. package/dist/prompt/composer.d.ts +46 -7
  235. package/dist/prompt/composer.js +78 -48
  236. package/dist/prompt/instruction-scanner.d.ts +6 -5
  237. package/dist/prompt/instruction-scanner.js +8 -23
  238. package/dist/prompt/section-loader.d.ts +2 -2
  239. package/dist/prompt/section-loader.js +7 -4
  240. package/dist/prompt/sections/browser.md +10 -9
  241. package/dist/prompt/sections/harness-base.md +9 -0
  242. package/dist/protocol/chat-session-manager.d.ts +77 -3
  243. package/dist/protocol/chat-session-manager.js +194 -18
  244. package/dist/protocol/chat-session.d.ts +72 -9
  245. package/dist/protocol/chat-session.js +151 -16
  246. package/dist/protocol/client.d.ts +37 -4
  247. package/dist/protocol/client.js +118 -6
  248. package/dist/protocol/index.d.ts +2 -2
  249. package/dist/protocol/index.js +2 -2
  250. package/dist/protocol/mobile-remote-types.d.ts +406 -0
  251. package/dist/protocol/mobile-remote-types.js +1 -0
  252. package/dist/protocol/server.d.ts +152 -14
  253. package/dist/protocol/server.js +1574 -229
  254. package/dist/protocol/types.d.ts +182 -32
  255. package/dist/protocol/types.js +18 -0
  256. package/dist/run/ArtifactTracker.d.ts +6 -1
  257. package/dist/run/ArtifactTracker.js +29 -21
  258. package/dist/run/EngineRunner.d.ts +2 -0
  259. package/dist/run/EngineRunner.js +1 -0
  260. package/dist/run/RunManager.d.ts +26 -0
  261. package/dist/run/RunManager.js +82 -11
  262. package/dist/run/factory.d.ts +3 -0
  263. package/dist/run/factory.js +2 -0
  264. package/dist/runtime/safe-spawn.d.ts +6 -0
  265. package/dist/runtime/safe-spawn.js +8 -13
  266. package/dist/services/auto-dream.d.ts +13 -1
  267. package/dist/services/auto-dream.js +56 -8
  268. package/dist/services/dream-consolidation.d.ts +3 -0
  269. package/dist/services/dream-consolidation.js +14 -2
  270. package/dist/services/index.d.ts +1 -1
  271. package/dist/services/index.js +1 -1
  272. package/dist/services/memory-orchestrator.js +6 -2
  273. package/dist/services/oauth.d.ts +34 -10
  274. package/dist/services/oauth.js +236 -98
  275. package/dist/services/session-memory.d.ts +4 -4
  276. package/dist/services/session-memory.js +13 -10
  277. package/dist/session/memory.d.ts +26 -7
  278. package/dist/session/memory.js +79 -21
  279. package/dist/session/session-manager.d.ts +208 -25
  280. package/dist/session/session-manager.js +1232 -116
  281. package/dist/session/session-message.d.ts +22 -0
  282. package/dist/session/session-message.js +1 -0
  283. package/dist/session/transcript.d.ts +76 -6
  284. package/dist/session/transcript.js +278 -7
  285. package/dist/settings/feature-flags.d.ts +40 -0
  286. package/dist/settings/feature-flags.js +40 -0
  287. package/dist/settings/manager.d.ts +39 -1
  288. package/dist/settings/manager.js +102 -41
  289. package/dist/settings/migrate-config.js +4 -2
  290. package/dist/settings/schema.d.ts +511 -177
  291. package/dist/settings/schema.js +98 -25
  292. package/dist/skills/scanner.d.ts +6 -2
  293. package/dist/skills/scanner.js +160 -9
  294. package/dist/sources/adapter.d.ts +14 -0
  295. package/dist/sources/adapter.js +7 -0
  296. package/dist/sources/adapters/local-files.d.ts +10 -0
  297. package/dist/sources/adapters/local-files.js +127 -0
  298. package/dist/sources/adapters/mcp-resource.d.ts +17 -0
  299. package/dist/sources/adapters/mcp-resource.js +41 -0
  300. package/dist/sources/adapters/mock.d.ts +3 -0
  301. package/dist/sources/adapters/mock.js +29 -0
  302. package/dist/sources/binding.d.ts +6 -0
  303. package/dist/sources/binding.js +24 -0
  304. package/dist/sources/catalog.d.ts +6 -0
  305. package/dist/sources/catalog.js +64 -0
  306. package/dist/sources/context-summary.d.ts +8 -0
  307. package/dist/sources/context-summary.js +13 -0
  308. package/dist/sources/credential-status.d.ts +2 -0
  309. package/dist/sources/credential-status.js +13 -0
  310. package/dist/sources/index.d.ts +10 -0
  311. package/dist/sources/index.js +10 -0
  312. package/dist/sources/resolve.d.ts +28 -0
  313. package/dist/sources/resolve.js +47 -0
  314. package/dist/sources/truncate-utf8.d.ts +7 -0
  315. package/dist/sources/truncate-utf8.js +18 -0
  316. package/dist/sources/types.d.ts +71 -0
  317. package/dist/sources/types.js +26 -0
  318. package/dist/testing/fetch-stub.d.ts +32 -0
  319. package/dist/testing/fetch-stub.js +24 -0
  320. package/dist/themes/image.d.ts +18 -0
  321. package/dist/themes/image.js +117 -0
  322. package/dist/themes/index.d.ts +4 -0
  323. package/dist/themes/index.js +4 -0
  324. package/dist/themes/installer.d.ts +50 -0
  325. package/dist/themes/installer.js +290 -0
  326. package/dist/themes/manifest.d.ts +102 -0
  327. package/dist/themes/manifest.js +86 -0
  328. package/dist/themes/paths.d.ts +14 -0
  329. package/dist/themes/paths.js +33 -0
  330. package/dist/tool-system/browser-bridge.d.ts +53 -11
  331. package/dist/tool-system/browser-bridge.js +8 -9
  332. package/dist/tool-system/builtin/agent-heartbeat.d.ts +2 -2
  333. package/dist/tool-system/builtin/agent-heartbeat.js +20 -6
  334. package/dist/tool-system/builtin/agent-notifications.d.ts +127 -72
  335. package/dist/tool-system/builtin/agent-notifications.js +291 -101
  336. package/dist/tool-system/builtin/agent-output-file.js +1 -3
  337. package/dist/tool-system/builtin/agent-progress.d.ts +6 -0
  338. package/dist/tool-system/builtin/agent-progress.js +83 -0
  339. package/dist/tool-system/builtin/agent-registry.d.ts +38 -2
  340. package/dist/tool-system/builtin/agent-registry.js +178 -9
  341. package/dist/tool-system/builtin/agent-transcript-translator.js +1 -1
  342. package/dist/tool-system/builtin/agent.d.ts +2 -0
  343. package/dist/tool-system/builtin/agent.js +279 -34
  344. package/dist/tool-system/builtin/background-jobs.d.ts +63 -7
  345. package/dist/tool-system/builtin/background-jobs.js +111 -15
  346. package/dist/tool-system/builtin/background-work.d.ts +12 -1
  347. package/dist/tool-system/builtin/background-work.js +12 -1
  348. package/dist/tool-system/builtin/bash.d.ts +3 -5
  349. package/dist/tool-system/builtin/bash.js +14 -6
  350. package/dist/tool-system/builtin/browser-tools.d.ts +8 -5
  351. package/dist/tool-system/builtin/browser-tools.js +89 -23
  352. package/dist/tool-system/builtin/edit-model-catalog.d.ts +6 -0
  353. package/dist/tool-system/builtin/edit-model-catalog.js +172 -12
  354. package/dist/tool-system/builtin/edit.d.ts +2 -1
  355. package/dist/tool-system/builtin/edit.js +14 -10
  356. package/dist/tool-system/builtin/file-cache.d.ts +2 -0
  357. package/dist/tool-system/builtin/file-cache.js +4 -0
  358. package/dist/tool-system/builtin/generate-image.js +17 -10
  359. package/dist/tool-system/builtin/generate-video.d.ts +4 -0
  360. package/dist/tool-system/builtin/generate-video.js +164 -28
  361. package/dist/tool-system/builtin/glob.d.ts +2 -1
  362. package/dist/tool-system/builtin/glob.js +28 -3
  363. package/dist/tool-system/builtin/grep.d.ts +1 -0
  364. package/dist/tool-system/builtin/grep.js +82 -17
  365. package/dist/tool-system/builtin/image-uploader.js +7 -1
  366. package/dist/tool-system/builtin/index.d.ts +57 -18
  367. package/dist/tool-system/builtin/index.js +280 -167
  368. package/dist/tool-system/builtin/memory.d.ts +6 -0
  369. package/dist/tool-system/builtin/memory.js +36 -15
  370. package/dist/tool-system/builtin/panel.d.ts +9 -0
  371. package/dist/tool-system/builtin/panel.js +314 -0
  372. package/dist/tool-system/builtin/read.js +5 -1
  373. package/dist/tool-system/builtin/send-message-to-session.d.ts +6 -0
  374. package/dist/tool-system/builtin/send-message-to-session.js +67 -0
  375. package/dist/tool-system/builtin/sources.d.ts +8 -0
  376. package/dist/tool-system/builtin/sources.js +137 -0
  377. package/dist/tool-system/builtin/tool-search.js +7 -2
  378. package/dist/tool-system/builtin/update-automation-memory.js +2 -0
  379. package/dist/tool-system/builtin/video-providers.d.ts +12 -15
  380. package/dist/tool-system/builtin/video-providers.js +1 -0
  381. package/dist/tool-system/builtin/view-image.d.ts +2 -2
  382. package/dist/tool-system/builtin/web-fetch.js +60 -15
  383. package/dist/tool-system/builtin/web-search.js +1 -3
  384. package/dist/tool-system/builtin/write.d.ts +2 -1
  385. package/dist/tool-system/builtin/write.js +14 -4
  386. package/dist/tool-system/capability-module.d.ts +93 -0
  387. package/dist/tool-system/capability-module.js +53 -0
  388. package/dist/tool-system/context.d.ts +94 -24
  389. package/dist/tool-system/executor.js +99 -58
  390. package/dist/tool-system/external-tool-exposure.d.ts +60 -0
  391. package/dist/tool-system/external-tool-exposure.js +304 -0
  392. package/dist/tool-system/mcp-manager.d.ts +17 -1
  393. package/dist/tool-system/mcp-manager.js +72 -7
  394. package/dist/tool-system/mcp-tool-policy.d.ts +9 -0
  395. package/dist/tool-system/mcp-tool-policy.js +34 -0
  396. package/dist/tool-system/panel-bridge.d.ts +53 -0
  397. package/dist/tool-system/panel-bridge.js +1 -0
  398. package/dist/tool-system/path-policy.d.ts +19 -0
  399. package/dist/tool-system/path-policy.js +62 -1
  400. package/dist/tool-system/permission.d.ts +69 -3
  401. package/dist/tool-system/permission.js +429 -30
  402. package/dist/tool-system/registry.d.ts +15 -3
  403. package/dist/tool-system/registry.js +82 -40
  404. package/dist/tool-system/sandbox/index.js +3 -8
  405. package/dist/tool-system/sandbox/seatbelt.js +2 -6
  406. package/dist/tool-system/session-tool-host.d.ts +139 -0
  407. package/dist/tool-system/session-tool-host.js +167 -0
  408. package/dist/tool-system/testing/tool-registry-harness.d.ts +31 -0
  409. package/dist/tool-system/testing/tool-registry-harness.js +51 -0
  410. package/dist/tool-system/validation.d.ts +14 -7
  411. package/dist/tool-system/validation.js +701 -7
  412. package/dist/types.d.ts +201 -20
  413. package/dist/{cc-orchestrator → utils}/cwd-normalize.d.ts +1 -0
  414. package/dist/{cc-orchestrator → utils}/cwd-normalize.js +1 -0
  415. package/dist/utils/file-mutex.d.ts +65 -0
  416. package/dist/utils/file-mutex.js +145 -0
  417. package/dist/utils/json.d.ts +2 -4
  418. package/dist/utils/json.js +2 -4
  419. package/dist/utils/lockfile.d.ts +30 -1
  420. package/dist/utils/lockfile.js +2 -2
  421. package/dist/utils/secret-scrubber.d.ts +8 -0
  422. package/dist/utils/secret-scrubber.js +227 -0
  423. package/dist/utils/toolDisplay.js +1 -1
  424. package/package.json +20 -5
  425. package/THIRD_PARTY_NOTICES.md +0 -206
  426. package/dist/arena/arena.d.ts +0 -43
  427. package/dist/arena/arena.js +0 -333
  428. package/dist/arena/context/context-tools.d.ts +0 -16
  429. package/dist/arena/context/context-tools.js +0 -272
  430. package/dist/arena/context/within-root.d.ts +0 -7
  431. package/dist/arena/context/within-root.js +0 -15
  432. package/dist/arena/detect-mode.d.ts +0 -20
  433. package/dist/arena/detect-mode.js +0 -78
  434. package/dist/arena/digest-builder.d.ts +0 -25
  435. package/dist/arena/digest-builder.js +0 -120
  436. package/dist/arena/index.d.ts +0 -29
  437. package/dist/arena/index.js +0 -29
  438. package/dist/arena/iterate/convergence.d.ts +0 -25
  439. package/dist/arena/iterate/convergence.js +0 -103
  440. package/dist/arena/iterate/formats/index.d.ts +0 -22
  441. package/dist/arena/iterate/formats/index.js +0 -283
  442. package/dist/arena/iterate/index.d.ts +0 -11
  443. package/dist/arena/iterate/index.js +0 -9
  444. package/dist/arena/iterate/iterative-arena.d.ts +0 -23
  445. package/dist/arena/iterate/iterative-arena.js +0 -237
  446. package/dist/arena/iterate/parse.d.ts +0 -42
  447. package/dist/arena/iterate/parse.js +0 -123
  448. package/dist/arena/iterate/phases/argue.d.ts +0 -22
  449. package/dist/arena/iterate/phases/argue.js +0 -165
  450. package/dist/arena/iterate/phases/revise.d.ts +0 -16
  451. package/dist/arena/iterate/phases/revise.js +0 -62
  452. package/dist/arena/iterate/phases/tournament.d.ts +0 -34
  453. package/dist/arena/iterate/phases/tournament.js +0 -113
  454. package/dist/arena/iterate/tools/web-tools.d.ts +0 -13
  455. package/dist/arena/iterate/tools/web-tools.js +0 -54
  456. package/dist/arena/iterate/types.d.ts +0 -152
  457. package/dist/arena/iterate/types.js +0 -8
  458. package/dist/arena/ledger.d.ts +0 -47
  459. package/dist/arena/ledger.js +0 -159
  460. package/dist/arena/lenses/architecture.d.ts +0 -5
  461. package/dist/arena/lenses/architecture.js +0 -22
  462. package/dist/arena/lenses/engineering.d.ts +0 -5
  463. package/dist/arena/lenses/engineering.js +0 -22
  464. package/dist/arena/lenses/general.d.ts +0 -5
  465. package/dist/arena/lenses/general.js +0 -20
  466. package/dist/arena/lenses/index.d.ts +0 -16
  467. package/dist/arena/lenses/index.js +0 -47
  468. package/dist/arena/lenses/product.d.ts +0 -5
  469. package/dist/arena/lenses/product.js +0 -22
  470. package/dist/arena/model-presets.d.ts +0 -23
  471. package/dist/arena/model-presets.js +0 -44
  472. package/dist/arena/phases/adjudication.d.ts +0 -24
  473. package/dist/arena/phases/adjudication.js +0 -141
  474. package/dist/arena/phases/build-consensus.d.ts +0 -29
  475. package/dist/arena/phases/build-consensus.js +0 -83
  476. package/dist/arena/phases/claim-registry.d.ts +0 -26
  477. package/dist/arena/phases/claim-registry.js +0 -60
  478. package/dist/arena/phases/cross-review.d.ts +0 -45
  479. package/dist/arena/phases/cross-review.js +0 -218
  480. package/dist/arena/phases/debate-rounds.d.ts +0 -27
  481. package/dist/arena/phases/debate-rounds.js +0 -159
  482. package/dist/arena/phases/participant-research.d.ts +0 -38
  483. package/dist/arena/phases/participant-research.js +0 -314
  484. package/dist/arena/phases/planning-detail-expansion.d.ts +0 -38
  485. package/dist/arena/phases/planning-detail-expansion.js +0 -119
  486. package/dist/arena/planner.d.ts +0 -27
  487. package/dist/arena/planner.js +0 -311
  488. package/dist/arena/providers/docs.d.ts +0 -7
  489. package/dist/arena/providers/docs.js +0 -111
  490. package/dist/arena/providers/git.d.ts +0 -8
  491. package/dist/arena/providers/git.js +0 -174
  492. package/dist/arena/providers/index.d.ts +0 -32
  493. package/dist/arena/providers/index.js +0 -132
  494. package/dist/arena/providers/none.d.ts +0 -7
  495. package/dist/arena/providers/none.js +0 -11
  496. package/dist/arena/providers/repo.d.ts +0 -7
  497. package/dist/arena/providers/repo.js +0 -258
  498. package/dist/arena/render/session.d.ts +0 -17
  499. package/dist/arena/render/session.js +0 -190
  500. package/dist/arena/render/terminal.d.ts +0 -34
  501. package/dist/arena/render/terminal.js +0 -286
  502. package/dist/arena/strategies/discussion.d.ts +0 -25
  503. package/dist/arena/strategies/discussion.js +0 -143
  504. package/dist/arena/strategies/index.d.ts +0 -15
  505. package/dist/arena/strategies/index.js +0 -28
  506. package/dist/arena/strategies/language-wrapper.d.ts +0 -17
  507. package/dist/arena/strategies/language-wrapper.js +0 -102
  508. package/dist/arena/strategies/lens-wrapper.d.ts +0 -16
  509. package/dist/arena/strategies/lens-wrapper.js +0 -236
  510. package/dist/arena/strategies/planning.d.ts +0 -30
  511. package/dist/arena/strategies/planning.js +0 -225
  512. package/dist/arena/strategies/review.d.ts +0 -26
  513. package/dist/arena/strategies/review.js +0 -168
  514. package/dist/arena/strategies/utils.d.ts +0 -36
  515. package/dist/arena/strategies/utils.js +0 -603
  516. package/dist/arena/tools/selector.d.ts +0 -17
  517. package/dist/arena/tools/selector.js +0 -61
  518. package/dist/arena/transitions.d.ts +0 -53
  519. package/dist/arena/transitions.js +0 -97
  520. package/dist/arena/types.d.ts +0 -514
  521. package/dist/arena/types.js +0 -27
  522. package/dist/cc-orchestrator/agent-adapter.d.ts +0 -54
  523. package/dist/cc-orchestrator/agent-adapter.js +0 -143
  524. package/dist/cc-orchestrator/cc-capability.d.ts +0 -19
  525. package/dist/cc-orchestrator/cc-capability.js +0 -53
  526. package/dist/cc-orchestrator/codex-session-discovery.d.ts +0 -24
  527. package/dist/cc-orchestrator/codex-session-discovery.js +0 -191
  528. package/dist/cc-orchestrator/codex-session-history.d.ts +0 -38
  529. package/dist/cc-orchestrator/codex-session-history.js +0 -247
  530. package/dist/cc-orchestrator/external-agent-bindings.d.ts +0 -27
  531. package/dist/cc-orchestrator/external-agent-bindings.js +0 -150
  532. package/dist/cc-orchestrator/external-agent-changes.d.ts +0 -19
  533. package/dist/cc-orchestrator/external-agent-changes.js +0 -231
  534. package/dist/cc-orchestrator/external-agent-driver.d.ts +0 -19
  535. package/dist/cc-orchestrator/external-agent-driver.js +0 -284
  536. package/dist/cc-orchestrator/external-agent-session-store.d.ts +0 -23
  537. package/dist/cc-orchestrator/external-agent-session-store.js +0 -146
  538. package/dist/cc-orchestrator/index.d.ts +0 -8
  539. package/dist/cc-orchestrator/index.js +0 -8
  540. package/dist/cc-orchestrator/relevance-judge.d.ts +0 -15
  541. package/dist/cc-orchestrator/relevance-judge.js +0 -29
  542. package/dist/cc-orchestrator/session-discovery.d.ts +0 -46
  543. package/dist/cc-orchestrator/session-discovery.js +0 -125
  544. package/dist/cc-orchestrator/session-history.d.ts +0 -54
  545. package/dist/cc-orchestrator/session-history.js +0 -150
  546. package/dist/cron/cron-runtime.d.ts +0 -2
  547. package/dist/cron/cron-runtime.js +0 -2
  548. package/dist/cron/cron-store.d.ts +0 -2
  549. package/dist/cron/cron-store.js +0 -2
  550. package/dist/cron/scheduler.d.ts +0 -7
  551. package/dist/cron/scheduler.js +0 -7
  552. package/dist/engine/goal.js +0 -206
  553. package/dist/external-agents/config.d.ts +0 -2
  554. package/dist/external-agents/config.js +0 -15
  555. package/dist/external-agents/types.d.ts +0 -31
  556. package/dist/git/parse-log.d.ts +0 -13
  557. package/dist/git/parse-log.js +0 -21
  558. package/dist/git/utils.d.ts +0 -49
  559. package/dist/git/utils.js +0 -161
  560. package/dist/git/worktree/crud.d.ts +0 -69
  561. package/dist/git/worktree/crud.js +0 -206
  562. package/dist/git/worktree/diff.d.ts +0 -14
  563. package/dist/git/worktree/diff.js +0 -82
  564. package/dist/git/worktree/git-exec.d.ts +0 -7
  565. package/dist/git/worktree/git-exec.js +0 -51
  566. package/dist/git/worktree/index.d.ts +0 -5
  567. package/dist/git/worktree/index.js +0 -5
  568. package/dist/git/worktree/query.d.ts +0 -42
  569. package/dist/git/worktree/query.js +0 -121
  570. package/dist/git/worktree/slug.d.ts +0 -11
  571. package/dist/git/worktree/slug.js +0 -58
  572. package/dist/git/worktree.d.ts +0 -1
  573. package/dist/git/worktree.js +0 -5
  574. package/dist/lsp/client.d.ts +0 -41
  575. package/dist/lsp/client.js +0 -186
  576. package/dist/lsp/manager.d.ts +0 -40
  577. package/dist/lsp/manager.js +0 -155
  578. package/dist/lsp/root-path.d.ts +0 -9
  579. package/dist/lsp/root-path.js +0 -12
  580. package/dist/lsp/servers.d.ts +0 -16
  581. package/dist/lsp/servers.js +0 -60
  582. package/dist/prompt/sections/coding.md +0 -35
  583. package/dist/quota/credentials.d.ts +0 -3
  584. package/dist/quota/credentials.js +0 -80
  585. package/dist/quota/index.d.ts +0 -36
  586. package/dist/quota/index.js +0 -155
  587. package/dist/quota/types.d.ts +0 -48
  588. package/dist/quota/types.js +0 -13
  589. package/dist/review/review-prompt.d.ts +0 -28
  590. package/dist/review/review-prompt.js +0 -81
  591. package/dist/state.d.ts +0 -160
  592. package/dist/state.js +0 -267
  593. package/dist/tool-system/builtin/apply-patch/applier.d.ts +0 -26
  594. package/dist/tool-system/builtin/apply-patch/applier.js +0 -308
  595. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +0 -10
  596. package/dist/tool-system/builtin/apply-patch/backup-targets.js +0 -30
  597. package/dist/tool-system/builtin/apply-patch/index.d.ts +0 -20
  598. package/dist/tool-system/builtin/apply-patch/index.js +0 -105
  599. package/dist/tool-system/builtin/apply-patch/parser.d.ts +0 -17
  600. package/dist/tool-system/builtin/apply-patch/parser.js +0 -212
  601. package/dist/tool-system/builtin/apply-patch/seek-sequence.d.ts +0 -18
  602. package/dist/tool-system/builtin/apply-patch/seek-sequence.js +0 -123
  603. package/dist/tool-system/builtin/apply-patch/types.d.ts +0 -49
  604. package/dist/tool-system/builtin/apply-patch/types.js +0 -13
  605. package/dist/tool-system/builtin/arena.d.ts +0 -31
  606. package/dist/tool-system/builtin/arena.js +0 -415
  607. package/dist/tool-system/builtin/brief.d.ts +0 -6
  608. package/dist/tool-system/builtin/brief.js +0 -41
  609. package/dist/tool-system/builtin/check-quota.d.ts +0 -15
  610. package/dist/tool-system/builtin/check-quota.js +0 -34
  611. package/dist/tool-system/builtin/drive-claude-code.d.ts +0 -51
  612. package/dist/tool-system/builtin/drive-claude-code.js +0 -698
  613. package/dist/tool-system/builtin/lsp.d.ts +0 -7
  614. package/dist/tool-system/builtin/lsp.js +0 -144
  615. package/dist/tool-system/builtin/notebook-edit.d.ts +0 -7
  616. package/dist/tool-system/builtin/notebook-edit.js +0 -127
  617. package/dist/tool-system/builtin/worktree.d.ts +0 -11
  618. package/dist/tool-system/builtin/worktree.js +0 -345
  619. /package/dist/{external-agents → links}/types.js +0 -0
  620. /package/dist/{engine/session-usage.d.ts → session/usage.d.ts} +0 -0
  621. /package/dist/{engine/session-usage.js → session/usage.js} +0 -0
@@ -4,9 +4,11 @@
4
4
  * Following Claude Code's po_() pattern:
5
5
  * pre_check → model_call → post_check → tool_exec → context_mgmt → hook_notify → next turn
6
6
  */
7
- import { initialTurnState, newTurnId } from "./turn-state.js";
7
+ import { randomUUID } from "node:crypto";
8
+ import { buildAgentDirectionMessage } from "../tool-system/builtin/agent-notifications.js";
9
+ import { newTurnId } from "./turn-state.js";
8
10
  import { formatFriendlyError } from "./friendly-error.js";
9
- import { projectGoalJudgeToolResult, } from "../hooks/goal-stop-hook.js";
11
+ import { buildGoalJudgeRuntimeContext } from "./goal-judge-context.js";
10
12
  import { wrapHookMessages } from "../hooks/inject.js";
11
13
  import { ContextLimitError } from "../exceptions.js";
12
14
  import { logger } from "../logging/logger.js";
@@ -18,9 +20,9 @@ import { isAbortError } from "../llm/client-base.js";
18
20
  import { crossedReactiveThreshold } from "./reactive-threshold.js";
19
21
  import { COMPLETE_GOAL_TOOL_NAME } from "../tool-system/builtin/complete-goal.js";
20
22
  import { CANCEL_GOAL_TOOL_NAME } from "../tool-system/builtin/cancel-goal.js";
21
- import { redactSensitiveToolResultsInMessages, toolResultForDisplay, toolResultTranscriptText, toolResultsForDisplay, } from "../tool-system/tool-result-redaction.js";
22
- import { addTokenUsage, cacheHitRateFromUsage, cumulativeCacheHitRate, } from "./session-usage.js";
23
- import { createGoalBudgetTracker, recordGoalUsage, goalBudgetTerminationReason, applyGoalExtension, limitProximity, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "./goal.js";
23
+ import { redactSensitiveToolResultsInMessages, SENSITIVE_TOOL_RESULT_PLACEHOLDER, toolResultForDisplay, toolResultTranscriptText, toolResultsForDisplay, } from "../tool-system/tool-result-redaction.js";
24
+ import { addTokenUsage, cacheHitRateFromUsage, cumulativeCacheHitRate, } from "../session/usage.js";
25
+ import { createGoalBudgetTracker, recordGoalUsage, goalBudgetTerminationReason, applyGoalExtension, limitProximity, isSameGoalVersion, normalizeGoal, GOAL_DEFAULT_MAX_STOP_BLOCKS, } from "../goal/lifecycle.js";
24
26
  /**
25
27
  * 把一个 ToolResult 映射成发给 LLM 的 tool_result ContentBlock。
26
28
  * 有 contentBlocks(view_image 的图片块)就原样用作 content;否则
@@ -64,6 +66,10 @@ export class TurnLoop {
64
66
  sensitiveToolResultRedactions = new Map();
65
67
  pendingImageMessages = new Set();
66
68
  volatileContextMessages = new Set();
69
+ /** Last outer-loop turn for which the model returned a complete response. */
70
+ lastCompletedModelTurn = 0;
71
+ /** Last model turn whose transcript/persistence boundary was finalized. */
72
+ lastFinalizedTurn = 0;
67
73
  /**
68
74
  * Consecutive on_stop blocks (Goal mode kept the agent going). Reset to 0
69
75
  * on any unblocked completion. When it reaches config.maxStopBlocks the
@@ -83,6 +89,57 @@ export class TurnLoop {
83
89
  * being re-emitted every turn while still within the approach threshold.
84
90
  */
85
91
  approachAnnounced = false;
92
+ /** Latest coalesced Goal edit/resume reminder, consumed at a safe point. */
93
+ pendingGoalControlMessage = null;
94
+ /** Pause/delete requested while a Goal run is in flight. The current
95
+ * model/tool step is allowed to settle, then the loop stops before starting
96
+ * another model request. */
97
+ goalControlStopRequested = false;
98
+ /** Auxiliary tool-summary calls started by this loop and revoked by Goal pause/delete. */
99
+ activeToolSummaryControllers = new Set();
100
+ /**
101
+ * Apply a user goal edit/pause/resume to the live loop. Passing undefined
102
+ * pauses goal-driving at the next step boundary; a goal value resumes it.
103
+ * Usage accumulated before an edit is retained, while a pause/resume starts
104
+ * a fresh run-scoped budget window (matching a later persisted resume).
105
+ */
106
+ updateGoal(goal, controlMessage, limits) {
107
+ const normalized = normalizeGoal(goal);
108
+ if (!normalized) {
109
+ this.config = { ...this.config, goal: undefined };
110
+ this.goalTracker = null;
111
+ this.pendingGoalControlMessage = null;
112
+ this.goalControlStopRequested = true;
113
+ for (const controller of this.activeToolSummaryControllers)
114
+ controller.abort();
115
+ this.activeToolSummaryControllers.clear();
116
+ this.stopBlockCount = 0;
117
+ this.approachAnnounced = false;
118
+ return;
119
+ }
120
+ const next = { ...normalized, goalId: normalized.goalId ?? randomUUID() };
121
+ const wasPaused = !this.config.goal;
122
+ this.config = {
123
+ ...this.config,
124
+ goal: next,
125
+ ...(typeof limits?.maxTurns === "number" && limits.maxTurns > 0
126
+ ? { maxTurns: Math.max(this.config.maxTurns, Math.floor(limits.maxTurns)) }
127
+ : {}),
128
+ ...(typeof limits?.maxStopBlocks === "number" && limits.maxStopBlocks > 0
129
+ ? { maxStopBlocks: Math.floor(limits.maxStopBlocks) }
130
+ : {}),
131
+ };
132
+ this.goalControlStopRequested = false;
133
+ this.pendingGoalControlMessage = controlMessage?.trim()
134
+ ? { goalId: next.goalId, revision: next.revision, text: controlMessage.trim() }
135
+ : null;
136
+ if (this.goalTracker && !wasPaused)
137
+ this.goalTracker.goal = next;
138
+ else
139
+ this.goalTracker = createGoalBudgetTracker(next, Date.now());
140
+ this.stopBlockCount = 0;
141
+ this.approachAnnounced = false;
142
+ }
86
143
  /**
87
144
  * Extend the in-flight run's limits (TODO 3.1 — 运行中续轮/加预算). Mutates
88
145
  * the maxTurns ceiling, the maxStopBlocks cap, and/or the live goal budgets;
@@ -128,6 +185,20 @@ export class TurnLoop {
128
185
  }
129
186
  return { ...next, maxStopBlocks: nextCap };
130
187
  }
188
+ /**
189
+ * Private Goal-judge accounting seam. Judge calls bypass ModelFacade, so the
190
+ * Engine forwards their provider usage here while the run-scoped tracker is
191
+ * live. The returned reason lets the hook suppress verdict side effects when
192
+ * this very request crossed a hard token/time budget.
193
+ */
194
+ recordGoalJudgeUsage(usage) {
195
+ if (!this.goalTracker)
196
+ return undefined;
197
+ if (usage) {
198
+ recordGoalUsage(this.goalTracker, (usage.promptTokens ?? 0) + (usage.completionTokens ?? 0));
199
+ }
200
+ return goalBudgetTerminationReason(this.goalTracker, Date.now());
201
+ }
131
202
  /**
132
203
  * Goal mode only: if the run is nearing EITHER stop ceiling (maxTurns or
133
204
  * maxStopBlocks) and we haven't announced it yet, emit one approaching_limit
@@ -145,6 +216,8 @@ export class TurnLoop {
145
216
  this.approachAnnounced = true;
146
217
  this.config.onStream?.({
147
218
  type: "goal_progress",
219
+ goalId: this.config.goal.goalId,
220
+ revision: this.config.goal.revision,
148
221
  status: "approaching_limit",
149
222
  round: this.stopBlockCount,
150
223
  turnsRemaining: prox.turnsRemaining,
@@ -159,6 +232,16 @@ export class TurnLoop {
159
232
  config) {
160
233
  this.deps = deps;
161
234
  this.config = config;
235
+ const normalizedGoal = normalizeGoal(this.config.goal);
236
+ if (normalizedGoal) {
237
+ this.config = {
238
+ ...this.config,
239
+ goal: { ...normalizedGoal, goalId: normalizedGoal.goalId ?? randomUUID() },
240
+ };
241
+ }
242
+ else if (this.config.goal !== undefined) {
243
+ this.config = { ...this.config, goal: undefined };
244
+ }
162
245
  for (const msg of this.config.freshImageMessages ?? []) {
163
246
  this.pendingImageMessages.add(msg);
164
247
  }
@@ -217,6 +300,21 @@ export class TurnLoop {
217
300
  return;
218
301
  this.deps.transcript.appendTurnStopped();
219
302
  }
303
+ /**
304
+ * Close one completed model turn exactly once. The transcript boundary must
305
+ * be durable before the Engine flushes usage, heartbeat, and turnCount via
306
+ * onTurnBoundary, so crash recovery never folds two assistant responses into
307
+ * the same transcript turn or observes state ahead of the transcript.
308
+ */
309
+ finalizeModelTurn() {
310
+ if (this.lastCompletedModelTurn !== this.turnCount ||
311
+ this.lastFinalizedTurn === this.turnCount) {
312
+ return;
313
+ }
314
+ this.lastFinalizedTurn = this.turnCount;
315
+ this.deps.transcript.appendTurnBoundary();
316
+ this.config.onTurnBoundary?.(this.turnCount);
317
+ }
220
318
  prepareMessagesForModel(messages) {
221
319
  const preserveMessages = this.pendingImageMessages.size > 0 ? this.pendingImageMessages : undefined;
222
320
  const result = downgradeImagePayloadsInHistory(messages, { preserveMessages });
@@ -326,10 +424,17 @@ export class TurnLoop {
326
424
  promptTokensConfidence: overhead > 0 ? "medium" : "low",
327
425
  });
328
426
  }
329
- recordResponseUsage(usage) {
427
+ recordResponseUsage(usage, requestKind = "primary", recordCacheDiagnostics = true) {
330
428
  this.currentTurnUsage = addTokenUsage(this.currentTurnUsage, usage);
429
+ this.deps.onAgentUsage?.(usage);
331
430
  this.currentCumulativeUsage = this.deps.recordCumulativeUsage?.(usage);
332
- this.deps.recordCacheReadDiagnostics?.(usage);
431
+ if (recordCacheDiagnostics && this.deps.recordCacheReadDiagnostics) {
432
+ this.deps.recordCacheReadDiagnostics({
433
+ usage,
434
+ requestKind,
435
+ fingerprint: this.deps.model.getPromptPrefixFingerprint(this.deps.systemPrompt, this.deps.tools),
436
+ });
437
+ }
333
438
  }
334
439
  emitCtxFromUsage(usage, messages) {
335
440
  if (!this.config.onStream)
@@ -396,24 +501,28 @@ export class TurnLoop {
396
501
  */
397
502
  async run(initialMessages) {
398
503
  const result = await this.runUnredacted(initialMessages);
399
- return { ...result, messages: this.redactConsumedSensitiveToolResults(result.messages) };
504
+ return {
505
+ ...result,
506
+ ...(result.reason === "completed" && this.goalControlStopRequested
507
+ ? { completionKind: "goal_control_stop" }
508
+ : {}),
509
+ messages: this.redactConsumedSensitiveToolResults(result.messages),
510
+ };
400
511
  }
401
512
  async runUnredacted(initialMessages) {
402
513
  let messages = [...initialMessages];
403
514
  let finalText = "";
404
515
  const budgetTracker = createBudgetTracker();
405
- // Recent execution evidence for the built-in Goal judge. Values enter this
406
- // window only after irreversible sensitive-data removal, non-text omission,
407
- // and per-item truncation at the current-round ToolResult boundary.
408
- const goalToolResults = [];
516
+ const sensitiveGoalResultTools = this.config.goal
517
+ ? new Set(this.deps.tools.filter((tool) => tool.sensitiveResult).map((tool) => tool.name))
518
+ : undefined;
409
519
  // Goal-mode run-scoped budget tracker (P0). Null when no goal. Stamps a
410
520
  // wall-clock start now and accumulates prompt+completion tokens across
411
521
  // every turn; the guardrail below force-stops the run once any configured
412
522
  // budget is blown — the unattended-safety backstop.
413
- this.goalTracker = this.config.goal
414
- ? createGoalBudgetTracker(this.config.goal, Date.now())
415
- : null;
416
- const goalTracker = this.goalTracker;
523
+ if (!this.goalTracker && this.config.goal) {
524
+ this.goalTracker = createGoalBudgetTracker(this.config.goal, Date.now());
525
+ }
417
526
  // Fresh run: re-arm the approaching-limit announcement.
418
527
  this.approachAnnounced = false;
419
528
  // run() must never reject: the engine's post-run bookkeeping (saveState
@@ -425,6 +534,30 @@ export class TurnLoop {
425
534
  // hook emits, guards) and surfaces them as a model_error result.
426
535
  try {
427
536
  while (this.turnCount < this.config.maxTurns) {
537
+ if (this.goalControlStopRequested) {
538
+ messages = this.redactConsumedSensitiveToolResults(messages);
539
+ return { text: finalText, reason: "completed", messages };
540
+ }
541
+ const preTurnGoalTermination = this.goalTracker
542
+ ? goalBudgetTerminationReason(this.goalTracker, Date.now())
543
+ : undefined;
544
+ if (preTurnGoalTermination) {
545
+ this.config.onStream?.({
546
+ type: "assistant_message",
547
+ message: {
548
+ role: "assistant",
549
+ content: "(Goal 预算已耗尽,强制停止。)",
550
+ },
551
+ });
552
+ messages = this.redactConsumedSensitiveToolResults(messages);
553
+ return {
554
+ text: finalText,
555
+ reason: "goal_budget_exhausted",
556
+ messages,
557
+ goalTermination: preTurnGoalTermination,
558
+ goalTerminationRound: this.stopBlockCount,
559
+ };
560
+ }
428
561
  this.turnCount++;
429
562
  this.currentTurnUsage = {
430
563
  promptTokens: 0,
@@ -452,8 +585,18 @@ export class TurnLoop {
452
585
  // they join THIS step's request — no abort, no lost in-flight work. Same
453
586
  // loop-top user-push pattern as turnStartInjection / turn-limit warnings
454
587
  // below. Push to transcript too so they persist + survive resume.
588
+ // Bind self-reported Goal terminal tools to the revision that was
589
+ // actually visible when this model step began. An edit/pause/delete can
590
+ // arrive while context management, the model request, or its tool batch
591
+ // is in flight; a late complete_goal/cancel_goal from that old prompt
592
+ // must never terminate the newer revision.
593
+ const requestedGoalVersion = this.config.goal ? { ...this.config.goal } : undefined;
455
594
  await this.consumeQueuedSteer(messages, "normal_step");
456
- const state = initialTurnState(this.turnCount);
595
+ if (this.goalControlStopRequested) {
596
+ messages = this.redactConsumedSensitiveToolResults(messages);
597
+ return { text: finalText, reason: "completed", messages };
598
+ }
599
+ this.deps.onAgentControlState?.("model");
457
600
  // Per-turn correlation ID. Every log written through `tlog` (or any
458
601
  // child derived from it) is stamped with `turn` + `turnId`, so
459
602
  // `jq 'select(.turnId == "...")'` reconstructs one turn's timeline.
@@ -468,11 +611,6 @@ export class TurnLoop {
468
611
  tlog.info("turn.start", { cat: "turn", messageCount: messages.length });
469
612
  // Tag downstream tool-exec / permission lines with this turn's IDs.
470
613
  this.deps.toolExecutor.setLogger(tlog);
471
- this.config.onStream?.({
472
- type: "stream_request_start",
473
- turnNumber: this.turnCount,
474
- messageId: assistantMessageId,
475
- });
476
614
  const turnStartHook = await this.emitHook("on_turn_start", {
477
615
  turnNumber: this.turnCount,
478
616
  });
@@ -480,6 +618,10 @@ export class TurnLoop {
480
618
  if (turnStartInjection) {
481
619
  messages.push(turnStartInjection);
482
620
  }
621
+ if (this.goalControlStopRequested) {
622
+ messages = this.redactConsumedSensitiveToolResults(messages);
623
+ return { text: finalText, reason: "completed", messages };
624
+ }
483
625
  // Approaching max turns: inject a warning so the model can wrap up.
484
626
  // (Model-facing only — the user-facing "再续" marker is handled by
485
627
  // maybeAnnounceApproachingLimit below, which also watches the stop-block
@@ -523,7 +665,7 @@ export class TurnLoop {
523
665
  }
524
666
  else {
525
667
  // Context management (async — may trigger LLM summarization)
526
- messages = await this.deps.contextManager.manageAsync(messages);
668
+ messages = await this.deps.contextManager.manageAsync(messages, this.config.signal);
527
669
  // manageAsync can itself issue an LLM summarization call lasting several
528
670
  // seconds; if the signal aborted during it, stop here rather than
529
671
  // proceeding into the (expensive) main model call. Belt to the loop-top
@@ -532,6 +674,10 @@ export class TurnLoop {
532
674
  this.markStopped();
533
675
  return { text: finalText, reason: "aborted_streaming", messages };
534
676
  }
677
+ if (this.goalControlStopRequested) {
678
+ messages = this.redactConsumedSensitiveToolResults(messages);
679
+ return { text: finalText, reason: "completed", messages };
680
+ }
535
681
  }
536
682
  // No pre-llm ctx emit here: the messages-only estimate would be ~16k
537
683
  // smaller than the real prompt (system + tools not included), making
@@ -559,8 +705,17 @@ export class TurnLoop {
559
705
  messages.push(compactInjection);
560
706
  }
561
707
  }
708
+ if (this.goalControlStopRequested) {
709
+ messages = this.redactConsumedSensitiveToolResults(messages);
710
+ return { text: finalText, reason: "completed", messages };
711
+ }
562
712
  messages = this.appendVolatileContextMessages(messages);
563
713
  // Model call (with streaming fallback and max_output_tokens continuation)
714
+ this.config.onStream?.({
715
+ type: "stream_request_start",
716
+ turnNumber: this.turnCount,
717
+ messageId: assistantMessageId,
718
+ });
564
719
  // Track tool IDs streamed during this turn to avoid duplicate UI events
565
720
  this.streamedToolIds.clear();
566
721
  // Tool queue is created before the call, but enqueue happens only after
@@ -572,10 +727,18 @@ export class TurnLoop {
572
727
  }
573
728
  catch (err) {
574
729
  if (err instanceof ContextLimitError) {
730
+ if (this.goalControlStopRequested) {
731
+ messages = this.redactConsumedSensitiveToolResults(messages);
732
+ return { text: finalText, reason: "completed", messages };
733
+ }
575
734
  // Progressive recovery: drop oldest API rounds, up to 3 retries
576
735
  const { dropOldestRounds } = await import("../context/compaction.js");
577
736
  let recovered = false;
578
737
  for (let retry = 1; retry <= 3; retry++) {
738
+ if (this.goalControlStopRequested) {
739
+ messages = this.redactConsumedSensitiveToolResults(messages);
740
+ return { text: finalText, reason: "completed", messages };
741
+ }
579
742
  tlog.warn("turn.ptl_recovery", { cat: "turn", retry, roundsToDrop: retry });
580
743
  messages = dropOldestRounds(messages, retry);
581
744
  try {
@@ -621,12 +784,22 @@ export class TurnLoop {
621
784
  return { text: finalText, reason: "model_error", messages };
622
785
  }
623
786
  }
787
+ this.lastCompletedModelTurn = this.turnCount;
624
788
  messages = this.redactConsumedSensitiveToolResults(messages);
625
789
  // Record the response once into current-turn and whole-session counters.
626
790
  if (response.usage?.promptTokens !== undefined) {
627
791
  this.recordResponseUsage(response.usage);
628
792
  this.emitCtxFromUsage(response.usage, messages);
629
793
  }
794
+ // Charge every provider response to the run-scoped Goal budget before
795
+ // any truncation path can retry or continue the outer turn loop.
796
+ if (this.goalTracker && response.usage) {
797
+ const used = (response.usage.promptTokens ?? 0) + (response.usage.completionTokens ?? 0);
798
+ recordGoalUsage(this.goalTracker, used);
799
+ }
800
+ let budgetTermination = this.goalTracker
801
+ ? goalBudgetTerminationReason(this.goalTracker, Date.now())
802
+ : undefined;
630
803
  // Feed actual token usage back to the context manager so subsequent
631
804
  // compaction decisions use hybrid (actual + delta) estimation rather than
632
805
  // pure heuristics. Without this the manager falls back to char/4 estimates.
@@ -637,13 +810,24 @@ export class TurnLoop {
637
810
  this.deps.recordContextUsageAnchor?.(anchor);
638
811
  }
639
812
  messages = this.markPendingImagesConsumed(messages);
813
+ // stopBlockCount guards only the no-tool final-answer loop evaluated by
814
+ // on_stop (text -> block -> text -> block). A response that structurally
815
+ // contains tool_use has left that streak, regardless of whether the call
816
+ // will execute successfully. Reset here, before truncated-tool and later
817
+ // queued-steer paths can continue the outer loop. Repeated ineffective
818
+ // tool_use turns still consume turns and are bounded by maxTurns.
819
+ if (response.toolCalls.length > 0) {
820
+ this.stopBlockCount = 0;
821
+ }
640
822
  // Truncation that cut off a TOOL CALL: the model overflowed
641
823
  // max_output_tokens mid tool-call, so the arg JSON is incomplete (e.g. a
642
824
  // Write whose `content` was clipped, leaving file_path unset). Executing
643
825
  // it raised a misleading "Missing required parameter: file_path". Instead,
644
826
  // tell the model its output was truncated and let the next turn retry —
645
827
  // bounded by the outer maxTurns loop.
646
- if (isTruncatedStop(response.stopReason) && response.toolCalls.length > 0) {
828
+ if (!budgetTermination &&
829
+ isTruncatedStop(response.stopReason) &&
830
+ response.toolCalls.length > 0) {
647
831
  tlog.info("turn.truncated_tool_call", {
648
832
  cat: "turn",
649
833
  toolCount: response.toolCalls.length,
@@ -655,13 +839,15 @@ export class TurnLoop {
655
839
  role: "user",
656
840
  content: "<system-reminder>Your previous response was truncated by the max output token limit before the tool call finished, so its arguments are incomplete. Do not assume it ran. Either retry with a smaller/more focused tool call (e.g. write the file in sections via Edit), or raise this model's maxOutputTokens.</system-reminder>",
657
841
  });
842
+ this.finalizeModelTurn();
658
843
  continue;
659
844
  }
660
845
  // Handle max_output_tokens: if response was truncated, do continuation
661
846
  // (up to 3 times). Truncation is reported as finish_reason "length"
662
847
  // (OpenAI) or stop_reason "max_tokens" (Anthropic) — isTruncatedStop
663
848
  // accepts both, so the OpenAI streaming path triggers continuation too.
664
- if (isTruncatedStop(response.stopReason) &&
849
+ if (!budgetTermination &&
850
+ isTruncatedStop(response.stopReason) &&
665
851
  response.toolCalls.length === 0 &&
666
852
  response.text) {
667
853
  let combinedText = response.text;
@@ -670,7 +856,12 @@ export class TurnLoop {
670
856
  // Don't fire another continuation call if the user cancelled — without
671
857
  // this an abort during a truncated response could still issue up to 3
672
858
  // more model calls, emitting text after Stop.
673
- if (this.config.signal?.aborted)
859
+ if (this.config.signal?.aborted || this.goalControlStopRequested)
860
+ break;
861
+ budgetTermination = this.goalTracker
862
+ ? goalBudgetTerminationReason(this.goalTracker, Date.now())
863
+ : undefined;
864
+ if (budgetTermination)
674
865
  break;
675
866
  tlog.info("turn.max_tokens_continuation", { cat: "turn", retry: retry + 1 });
676
867
  const contMessages = [
@@ -683,18 +874,40 @@ export class TurnLoop {
683
874
  ];
684
875
  try {
685
876
  const contResponse = await this.deps.model.call(this.deps.systemPrompt, this.prepareMessagesForModel(contMessages), this.deps.tools, this.config.onStream, this.config.signal);
877
+ // Continuations are separate provider responses, so preserve the
878
+ // same structural tool_use invariant before processing this one.
879
+ if (contResponse.toolCalls.length > 0) {
880
+ this.stopBlockCount = 0;
881
+ }
686
882
  if (contResponse.usage?.promptTokens !== undefined) {
687
- this.recordResponseUsage(contResponse.usage);
883
+ this.recordResponseUsage(contResponse.usage, "continuation");
688
884
  continuedResponse = true;
689
885
  }
886
+ if (this.goalTracker && contResponse.usage) {
887
+ const used = (contResponse.usage.promptTokens ?? 0) +
888
+ (contResponse.usage.completionTokens ?? 0);
889
+ recordGoalUsage(this.goalTracker, used);
890
+ }
891
+ budgetTermination = this.goalTracker
892
+ ? goalBudgetTerminationReason(this.goalTracker, Date.now())
893
+ : undefined;
690
894
  combinedText += contResponse.text;
691
- if (!isTruncatedStop(contResponse.stopReason) || contResponse.toolCalls.length > 0) {
895
+ if (budgetTermination ||
896
+ !isTruncatedStop(contResponse.stopReason) ||
897
+ contResponse.toolCalls.length > 0) {
692
898
  response = { ...contResponse, text: combinedText };
693
899
  break;
694
900
  }
695
901
  }
696
- catch {
697
- break;
902
+ catch (err) {
903
+ if (isAbortError(err) || this.config.signal?.aborted) {
904
+ this.markStopped();
905
+ this.finalizeModelTurn();
906
+ return { text: finalText, reason: "aborted_streaming", messages };
907
+ }
908
+ this.config.onStream?.({ type: "error", error: formatFriendlyError(err) });
909
+ this.finalizeModelTurn();
910
+ return { text: finalText, reason: "model_error", messages };
698
911
  }
699
912
  }
700
913
  response = { ...response, text: combinedText };
@@ -702,16 +915,10 @@ export class TurnLoop {
702
915
  this.emitCtxFromUsage(response.usage, messages);
703
916
  }
704
917
  }
705
- // Goal-mode run-scoped accounting: add this turn's total token usage
706
- // (prompt + completion) to the running total. Done after continuation so
707
- // continued output is counted against the budget.
708
- if (goalTracker && response.usage) {
709
- const used = (response.usage.promptTokens ?? 0) + (response.usage.completionTokens ?? 0);
710
- recordGoalUsage(goalTracker, used);
711
- }
712
918
  // Aborted?
713
919
  if (this.config.signal?.aborted) {
714
920
  this.markStopped();
921
+ this.finalizeModelTurn();
715
922
  return { text: finalText, reason: "aborted_streaming", messages };
716
923
  }
717
924
  // Accumulate text
@@ -722,13 +929,13 @@ export class TurnLoop {
722
929
  // is force-stopped regardless of what the model wants to do next (stop OR
723
930
  // continue with tool calls). This is the unattended-safety backstop, so
724
931
  // it sits BEFORE the "tool calls?" branch — both paths pass the gate.
725
- const budgetTermination = goalTracker
726
- ? goalBudgetTerminationReason(goalTracker, Date.now())
932
+ budgetTermination = this.goalTracker
933
+ ? (budgetTermination ?? goalBudgetTerminationReason(this.goalTracker, Date.now()))
727
934
  : undefined;
728
- if (goalTracker && budgetTermination) {
935
+ if (this.goalTracker && budgetTermination) {
729
936
  tlog.info("turn.goal_budget_exhausted", {
730
937
  cat: "goal",
731
- tokensUsed: goalTracker.tokensUsed,
938
+ tokensUsed: this.goalTracker.tokensUsed,
732
939
  tokenBudget: this.config.goal?.tokenBudget,
733
940
  timeBudgetMs: this.config.goal?.timeBudgetMs,
734
941
  });
@@ -740,11 +947,13 @@ export class TurnLoop {
740
947
  content: "(Goal 预算已耗尽,强制停止。)",
741
948
  },
742
949
  });
950
+ this.finalizeModelTurn();
743
951
  return {
744
952
  text: finalText,
745
953
  reason: "goal_budget_exhausted",
746
954
  messages,
747
955
  goalTermination: budgetTermination,
956
+ goalTerminationRound: this.stopBlockCount,
748
957
  };
749
958
  }
750
959
  // Post-check: tool calls?
@@ -760,37 +969,93 @@ export class TurnLoop {
760
969
  hasToolUse: false,
761
970
  });
762
971
  messages.push({ role: "assistant", content: finalText });
972
+ this.finalizeModelTurn();
763
973
  if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
764
974
  continue;
765
975
  }
976
+ // A steer accepted while an async tool was launching must be
977
+ // answered before parking the run. Keep the tool's yield request
978
+ // pending across that extra model round, then park once the model
979
+ // has replied and there is still no background result to consume.
980
+ if (this.deps.consumeToolRunYield?.() === "background_notification") {
981
+ tlog.info("turn.background_notification_wait_after_steer", { cat: "turn" });
982
+ messages = this.redactConsumedSensitiveToolResults(messages);
983
+ return {
984
+ text: finalText,
985
+ reason: "completed",
986
+ messages,
987
+ completionKind: "background_wait",
988
+ };
989
+ }
766
990
  // on_stop seam: the model wants to stop. Give handlers (Goal mode)
767
991
  // a chance to BLOCK termination and keep the agent working. A
768
992
  // handler returning continueSession=true injects its messages and
769
993
  // we run another turn instead of returning. Bounded by
770
994
  // maxStopBlocks (consecutive) and the outer maxTurns ceiling.
771
995
  const maxStopBlocks = this.config.maxStopBlocks ?? GOAL_DEFAULT_MAX_STOP_BLOCKS;
772
- if (goalTracker) {
773
- this.deps.updateGoalJudgeContext?.({
774
- toolResults: goalToolResults.map((item) => ({
775
- ...item,
776
- })),
777
- progress: {
778
- turnCount: this.turnCount,
779
- stopRound: this.stopBlockCount + 1,
780
- elapsedMs: Math.max(0, Date.now() - goalTracker.startedAtMs),
781
- tokensUsed: goalTracker.tokensUsed,
782
- tokenBudget: goalTracker.goal.tokenBudget,
783
- timeBudgetMs: goalTracker.goal.timeBudgetMs,
784
- maxTurns: this.config.maxTurns,
785
- maxStopBlocks,
786
- },
787
- });
996
+ if (this.goalTracker) {
997
+ const judgeMessages = this.stripVolatileContextMessages(messages);
998
+ this.deps.publishGoalJudgeContext?.(buildGoalJudgeRuntimeContext(judgeMessages, {
999
+ sensitiveToolResultRedactions: this.sensitiveToolResultRedactions,
1000
+ }));
788
1001
  }
789
1002
  const stopHook = await this.emitHook("on_stop", {
790
1003
  goal: this.config.goal,
791
1004
  finalText,
792
1005
  turnCount: this.turnCount,
793
1006
  });
1007
+ // The primary-model judge is a real billed request. Its hook forwards
1008
+ // usage through recordGoalJudgeUsage above; re-check immediately so an
1009
+ // over-budget verdict cannot authorize another main-model turn.
1010
+ const hookGoalTermination = stopHook.goalTermination;
1011
+ const hookBudgetTermination = hookGoalTermination === "token_budget_exhausted" ||
1012
+ hookGoalTermination === "time_budget_exhausted"
1013
+ ? hookGoalTermination
1014
+ : undefined;
1015
+ const postJudgeBudgetTermination = this.goalTracker
1016
+ ? (hookBudgetTermination ?? goalBudgetTerminationReason(this.goalTracker, Date.now()))
1017
+ : undefined;
1018
+ if (this.goalTracker && postJudgeBudgetTermination) {
1019
+ tlog.info("turn.goal_budget_exhausted_after_judge", {
1020
+ cat: "goal",
1021
+ reason: postJudgeBudgetTermination,
1022
+ tokensUsed: this.goalTracker.tokensUsed,
1023
+ tokenBudget: this.goalTracker.goal.tokenBudget,
1024
+ timeBudgetMs: this.goalTracker.goal.timeBudgetMs,
1025
+ });
1026
+ this.config.onStream?.({
1027
+ type: "assistant_message",
1028
+ messageId: assistantMessageId,
1029
+ message: {
1030
+ role: "assistant",
1031
+ content: "(Goal 预算已耗尽,强制停止。)",
1032
+ },
1033
+ });
1034
+ return {
1035
+ text: finalText,
1036
+ reason: "goal_budget_exhausted",
1037
+ messages,
1038
+ goalTermination: postJudgeBudgetTermination,
1039
+ goalTerminationRound: this.stopBlockCount,
1040
+ };
1041
+ }
1042
+ if (this.goalTracker && hookGoalTermination === "judge_prompt_too_large") {
1043
+ tlog.warn("turn.goal_judge_prompt_too_large", { cat: "goal" });
1044
+ this.config.onStream?.({
1045
+ type: "assistant_message",
1046
+ messageId: assistantMessageId,
1047
+ message: {
1048
+ role: "assistant",
1049
+ content: "(Goal 裁判输入超过安全上限,无法判定;本次已停止,目标已保留。)",
1050
+ },
1051
+ });
1052
+ return {
1053
+ text: finalText,
1054
+ reason: "completed",
1055
+ messages,
1056
+ goalTermination: hookGoalTermination,
1057
+ };
1058
+ }
794
1059
  // The judge's structured verdict (set by GoalStopHook in result.data)
795
1060
  // rides back here so we can show goal progress WITHOUT a second LLM
796
1061
  // call — `gaps` is whatever the judge already computed.
@@ -801,6 +1066,8 @@ export class TurnLoop {
801
1066
  // up with stopBlockCount so the UI can show "第 N 轮".
802
1067
  this.config.onStream?.({
803
1068
  type: "goal_progress",
1069
+ goalId: this.config.goal?.goalId,
1070
+ revision: this.config.goal?.revision,
804
1071
  status: "not_met",
805
1072
  round: this.stopBlockCount,
806
1073
  gaps: goalVerdict?.gaps || undefined,
@@ -839,11 +1106,6 @@ export class TurnLoop {
839
1106
  stopBlockCount: this.stopBlockCount,
840
1107
  maxStopBlocks,
841
1108
  });
842
- this.config.onStream?.({
843
- type: "goal_progress",
844
- status: "exhausted",
845
- round: this.stopBlockCount,
846
- });
847
1109
  goalTermination = "stop_blocks_exhausted";
848
1110
  this.config.onStream?.({
849
1111
  type: "assistant_message",
@@ -859,18 +1121,28 @@ export class TurnLoop {
859
1121
  // rounds = prior blocks + this accepted final round.
860
1122
  this.config.onStream?.({
861
1123
  type: "goal_progress",
1124
+ goalId: this.config.goal.goalId,
1125
+ revision: this.config.goal.revision,
862
1126
  status: "met",
863
1127
  round: this.stopBlockCount + 1,
864
1128
  });
865
1129
  }
1130
+ const goalTerminationRound = goalTermination ? this.stopBlockCount : undefined;
866
1131
  this.stopBlockCount = 0;
867
1132
  if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
868
1133
  continue;
869
1134
  }
870
1135
  messages = this.redactConsumedSensitiveToolResults(messages);
871
- return { text: finalText, reason: "completed", messages, goalTermination };
1136
+ return {
1137
+ text: finalText,
1138
+ reason: "completed",
1139
+ messages,
1140
+ goalTermination,
1141
+ goalTerminationRound,
1142
+ };
872
1143
  }
873
1144
  // Tool execution phase
1145
+ this.deps.onAgentControlState?.("tool-batch");
874
1146
  tlog.info("turn.tool_use", {
875
1147
  cat: "turn",
876
1148
  tools: response.toolCalls.map((t) => t.toolName),
@@ -911,17 +1183,13 @@ export class TurnLoop {
911
1183
  // Record results in transcript and stream
912
1184
  const resultBlocks = [];
913
1185
  for (const result of results) {
914
- if (goalTracker) {
915
- goalToolResults.push(projectGoalJudgeToolResult(result, this.turnCount));
916
- // The prompt renderer keeps at most 12, but retaining a few extra
917
- // bounded projections lets its total-char budget choose the newest useful subset.
918
- if (goalToolResults.length > 20)
919
- goalToolResults.splice(0, goalToolResults.length - 20);
920
- }
921
1186
  resultBlocks.push(toolResultToBlock(result));
922
1187
  const streamResult = toolResultForDisplay(result);
923
- const transcriptResult = toolResultTranscriptText(result);
924
- if (result.sensitive && transcriptResult !== undefined) {
1188
+ const sensitiveByMetadata = sensitiveGoalResultTools?.has(result.toolName) === true;
1189
+ const transcriptResult = sensitiveByMetadata
1190
+ ? SENSITIVE_TOOL_RESULT_PLACEHOLDER
1191
+ : toolResultTranscriptText(result);
1192
+ if ((result.sensitive || sensitiveByMetadata) && transcriptResult !== undefined) {
925
1193
  this.sensitiveToolResultRedactions.set(result.id, transcriptResult);
926
1194
  }
927
1195
  this.deps.transcript.appendToolResult(result.id, result.toolName, transcriptResult, result.error, result.sensitive ? undefined : result.contentBlocks);
@@ -931,13 +1199,15 @@ export class TurnLoop {
931
1199
  // best-effort observability — a thrown onStream handler, a failed dynamic
932
1200
  // import, or a rejecting summarize must never surface as an unhandled
933
1201
  // rejection (Node can treat those as fatal). Swallow at the tail. (B-2)
934
- if (this.config.onStream) {
1202
+ if (this.config.onStream && this.deps.model.summarize && !this.goalControlStopRequested) {
1203
+ const summaryController = new AbortController();
1204
+ this.activeToolSummaryControllers.add(summaryController);
935
1205
  void import("./tool-summary.js")
936
1206
  .then(({ generateToolUseSummary }) => {
937
- if (!this.deps.model.summarize)
1207
+ if (this.goalControlStopRequested || summaryController.signal.aborted)
938
1208
  return;
939
- return generateToolUseSummary(toolCalls, toolResultsForDisplay(results), this.deps.model.summarize).then((summary) => {
940
- if (summary) {
1209
+ return generateToolUseSummary(toolCalls, toolResultsForDisplay(results), this.deps.model.summarize, summaryController.signal).then((summary) => {
1210
+ if (summary && !summaryController.signal.aborted) {
941
1211
  this.config.onStream?.({
942
1212
  type: "tool_summary",
943
1213
  summary,
@@ -951,6 +1221,9 @@ export class TurnLoop {
951
1221
  cat: "turn",
952
1222
  error: err.message,
953
1223
  });
1224
+ })
1225
+ .finally(() => {
1226
+ this.activeToolSummaryControllers.delete(summaryController);
954
1227
  });
955
1228
  }
956
1229
  const toolResultMessage = { role: "user", content: resultBlocks };
@@ -981,6 +1254,31 @@ export class TurnLoop {
981
1254
  // Tool results just pushed; recompute ctx so the bar updates *before*
982
1255
  // the next model round-trip — large tool outputs can move it sharply.
983
1256
  this.emitCtxFromMessages(messages);
1257
+ // A trusted tool launched asynchronous work whose completion is routed
1258
+ // back into this Session. End this run at the tool boundary instead of
1259
+ // asking the model for another step with no new evidence; the queued
1260
+ // completion notification will wake the Session and continue normally.
1261
+ // This precedes complete_goal so a single batch cannot launch unfinished
1262
+ // background work and simultaneously claim the enclosing Goal is done.
1263
+ if (this.deps.peekToolRunYield?.() === "background_notification") {
1264
+ // Close the current model turn before a queued steer re-drives it;
1265
+ // interrupt-and-redrive swaps the turn signal at this boundary.
1266
+ this.finalizeModelTurn();
1267
+ if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
1268
+ continue;
1269
+ }
1270
+ }
1271
+ if (this.deps.peekToolRunYield?.() === "background_notification" &&
1272
+ this.deps.consumeToolRunYield?.() === "background_notification") {
1273
+ tlog.info("turn.background_notification_wait", { cat: "turn" });
1274
+ messages = this.redactConsumedSensitiveToolResults(messages);
1275
+ return {
1276
+ text: finalText,
1277
+ reason: "completed",
1278
+ messages,
1279
+ completionKind: "background_wait",
1280
+ };
1281
+ }
984
1282
  // Goal mode P0: explicit completion. If the model called complete_goal,
985
1283
  // it has DECLARED the goal done — short-circuit to "completed" WITHOUT
986
1284
  // running the judge hook. The tool's result is already in `messages`
@@ -990,15 +1288,33 @@ export class TurnLoop {
990
1288
  // the model just declared finished (the judge's onMet does this when IT
991
1289
  // decides; a self-report must do the same, else the goal outlives its
992
1290
  // completion and re-arms every follow-up turn).
993
- if (goalTracker && toolCalls.some((tc) => tc.toolName === COMPLETE_GOAL_TOOL_NAME)) {
994
- tlog.info("turn.goal_self_reported_complete", { cat: "goal" });
995
- this.stopBlockCount = 0;
996
- this.deps.clearPersistedGoal?.();
997
- if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
998
- continue;
1291
+ const completeGoalRequested = !!this.goalTracker &&
1292
+ isSameGoalVersion(requestedGoalVersion, this.config.goal) &&
1293
+ toolCalls.some((tc) => tc.toolName === COMPLETE_GOAL_TOOL_NAME);
1294
+ if (completeGoalRequested) {
1295
+ const persisted = this.deps.clearPersistedGoal?.("completed") !== false;
1296
+ if (persisted) {
1297
+ tlog.info("turn.goal_self_reported_complete", { cat: "goal" });
1298
+ this.config.onStream?.({
1299
+ type: "goal_progress",
1300
+ goalId: this.config.goal?.goalId,
1301
+ revision: this.config.goal?.revision,
1302
+ status: "met",
1303
+ round: this.stopBlockCount + 1,
1304
+ });
1305
+ this.stopBlockCount = 0;
1306
+ this.finalizeModelTurn();
1307
+ if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
1308
+ continue;
1309
+ }
1310
+ messages = this.redactConsumedSensitiveToolResults(messages);
1311
+ return { text: finalText, reason: "completed", messages };
999
1312
  }
1000
- messages = this.redactConsumedSensitiveToolResults(messages);
1001
- return { text: finalText, reason: "completed", messages };
1313
+ tlog.warn("turn.goal_self_reported_complete_persist_failed", { cat: "goal" });
1314
+ messages.push({
1315
+ role: "user",
1316
+ content: "<system-reminder>complete_goal 未能持久化完成状态;目标仍处于活动状态。请不要宣称已经结束,并在会话状态恢复可写后重试。</system-reminder>",
1317
+ });
1002
1318
  }
1003
1319
  // Goal mode: user-initiated cancellation. cancel_goal is the "strong
1004
1320
  // intent" escape hatch — honor it ONLY when confirm===true (the guard the
@@ -1006,14 +1322,35 @@ export class TurnLoop {
1006
1322
  // persisted goal so it never re-arms; an unconfirmed call is a no-op here
1007
1323
  // (the tool's own result string already told the model it was ignored).
1008
1324
  const confirmedCancel = toolCalls.some((tc) => tc.toolName === CANCEL_GOAL_TOOL_NAME && tc.args?.confirm === true);
1009
- if (goalTracker && confirmedCancel) {
1010
- tlog.info("turn.goal_user_cancelled", { cat: "goal" });
1011
- this.stopBlockCount = 0;
1012
- this.deps.clearPersistedGoal?.();
1013
- if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
1014
- continue;
1325
+ if (this.goalTracker &&
1326
+ isSameGoalVersion(requestedGoalVersion, this.config.goal) &&
1327
+ confirmedCancel &&
1328
+ !completeGoalRequested) {
1329
+ const persisted = this.deps.clearPersistedGoal?.("cancelled") !== false;
1330
+ if (persisted) {
1331
+ tlog.info("turn.goal_user_cancelled", { cat: "goal" });
1332
+ this.stopBlockCount = 0;
1333
+ this.config.onStream?.({
1334
+ type: "goal_cleared",
1335
+ goalId: this.config.goal?.goalId,
1336
+ revision: this.config.goal?.revision,
1337
+ });
1338
+ this.finalizeModelTurn();
1339
+ if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
1340
+ continue;
1341
+ }
1342
+ return {
1343
+ text: finalText,
1344
+ reason: "completed",
1345
+ messages,
1346
+ completionKind: "goal_control_stop",
1347
+ };
1015
1348
  }
1016
- return { text: finalText, reason: "completed", messages };
1349
+ tlog.warn("turn.goal_user_cancelled_persist_failed", { cat: "goal" });
1350
+ messages.push({
1351
+ role: "user",
1352
+ content: "<system-reminder>cancel_goal 未能持久化删除状态;目标仍处于活动状态。请在会话状态恢复可写后重试。</system-reminder>",
1353
+ });
1017
1354
  }
1018
1355
  // Token budget check
1019
1356
  const totalOutputTokens = this.deps.model.getOutputTokens?.() ?? 0;
@@ -1030,11 +1367,17 @@ export class TurnLoop {
1030
1367
  message: { role: "assistant", content: finalText },
1031
1368
  });
1032
1369
  messages.push({ role: "assistant", content: finalText });
1370
+ this.finalizeModelTurn();
1033
1371
  if (await this.consumeQueuedSteer(messages, "finalize_backfill")) {
1034
1372
  continue;
1035
1373
  }
1036
1374
  messages = this.redactConsumedSensitiveToolResults(messages);
1037
- return { text: finalText, reason: "completed", messages };
1375
+ return {
1376
+ text: finalText,
1377
+ reason: "completed",
1378
+ messages,
1379
+ completionKind: "limit_stop",
1380
+ };
1038
1381
  }
1039
1382
  if (budgetDecision === "nudge") {
1040
1383
  messages.push({
@@ -1072,9 +1415,7 @@ export class TurnLoop {
1072
1415
  hasToolUse: true,
1073
1416
  toolCallCount: toolCalls.length,
1074
1417
  });
1075
- // Record turn boundary
1076
- this.deps.transcript.appendTurnBoundary();
1077
- this.config.onTurnBoundary?.(this.turnCount);
1418
+ this.finalizeModelTurn();
1078
1419
  tlog.info("turn.end", {
1079
1420
  cat: "turn",
1080
1421
  duration_ms: Date.now() - turnStartedAt,
@@ -1089,6 +1430,9 @@ export class TurnLoop {
1089
1430
  // engine's post-run saveState records model_error instead of leaving
1090
1431
  // the session stuck at "active".
1091
1432
  this.patchOrphanedToolUses(messages);
1433
+ // If the model already returned, this is still a completed model turn.
1434
+ // The idempotent helper is a no-op when its normal branch finalized first.
1435
+ this.finalizeModelTurn();
1092
1436
  // A user-initiated Stop also bubbles here (an AbortError thrown from the
1093
1437
  // per-turn scaffolding). Treat it as a clean abort, not a model_error —
1094
1438
  // no error event, so the UI shows only the "你停止了本轮" line.
@@ -1106,6 +1450,12 @@ export class TurnLoop {
1106
1450
  messages = this.redactConsumedSensitiveToolResults(messages);
1107
1451
  return { text: finalText, reason: "model_error", messages };
1108
1452
  }
1453
+ // Pause/delete may have landed while the final allowed tool batch was
1454
+ // draining. Do not cross that safe boundary into an extra summary request.
1455
+ if (this.goalControlStopRequested) {
1456
+ messages = this.redactConsumedSensitiveToolResults(messages);
1457
+ return { text: finalText, reason: "completed", messages };
1458
+ }
1109
1459
  // Max turns reached — do one final summarization call (no tools)
1110
1460
  logger.warn("turn.max_turns_reached", {
1111
1461
  cat: "turn",
@@ -1113,6 +1463,11 @@ export class TurnLoop {
1113
1463
  turnCount: this.turnCount,
1114
1464
  });
1115
1465
  await this.consumeQueuedSteer(messages, "finalize_backfill");
1466
+ if (this.goalControlStopRequested) {
1467
+ messages = this.redactConsumedSensitiveToolResults(messages);
1468
+ return { text: finalText, reason: "completed", messages };
1469
+ }
1470
+ this.deps.onAgentControlState?.("model");
1116
1471
  const hasPendingSensitiveToolResults = this.sensitiveToolResultRedactions.size > 0;
1117
1472
  messages = this.prepareMessagesForModel(messages);
1118
1473
  if (hasPendingSensitiveToolResults) {
@@ -1125,20 +1480,44 @@ export class TurnLoop {
1125
1480
  else {
1126
1481
  messages = this.deps.contextManager.manage(messages);
1127
1482
  }
1483
+ if (this.goalControlStopRequested) {
1484
+ messages = this.redactConsumedSensitiveToolResults(messages);
1485
+ return { text: finalText, reason: "completed", messages };
1486
+ }
1128
1487
  messages.push({
1129
1488
  role: "user",
1130
1489
  content: "<system-reminder>Turn limit reached. Provide a final summary of what you accomplished and what remains to be done. Do NOT call any tools.</system-reminder>",
1131
1490
  });
1132
1491
  this.emitCtxFromMessages(messages);
1133
1492
  try {
1493
+ if (this.goalControlStopRequested) {
1494
+ messages = this.redactConsumedSensitiveToolResults(messages);
1495
+ return { text: finalText, reason: "completed", messages };
1496
+ }
1134
1497
  const summaryResponse = await this.deps.model.call(this.deps.systemPrompt, this.prepareMessagesForModel(messages), [], // No tools available for summary turn
1135
1498
  this.config.onStream, this.config.signal, this.modelCallRecordingOptions());
1499
+ if (summaryResponse.usage?.promptTokens !== undefined) {
1500
+ this.recordResponseUsage(summaryResponse.usage, "primary", false);
1501
+ }
1502
+ if (this.goalTracker && summaryResponse.usage) {
1503
+ recordGoalUsage(this.goalTracker, (summaryResponse.usage.promptTokens ?? 0) + (summaryResponse.usage.completionTokens ?? 0));
1504
+ }
1505
+ if (this.config.signal?.aborted) {
1506
+ this.markStopped();
1507
+ messages = this.redactConsumedSensitiveToolResults(messages);
1508
+ return { text: finalText, reason: "aborted_streaming", messages };
1509
+ }
1136
1510
  messages = this.markPendingImagesConsumed(messages);
1137
1511
  if (summaryResponse.text) {
1138
1512
  finalText = summaryResponse.text;
1139
1513
  }
1140
1514
  }
1141
- catch {
1515
+ catch (err) {
1516
+ if (isAbortError(err) || this.config.signal?.aborted) {
1517
+ this.markStopped();
1518
+ messages = this.redactConsumedSensitiveToolResults(messages);
1519
+ return { text: finalText, reason: "aborted_streaming", messages };
1520
+ }
1142
1521
  // If even summary fails, just return what we have
1143
1522
  this.currentTurnLog.warn("turn.summary_failed", { cat: "turn" });
1144
1523
  }
@@ -1155,6 +1534,7 @@ export class TurnLoop {
1155
1534
  reason: "max_turns",
1156
1535
  messages,
1157
1536
  ...(this.config.goal ? { goalTermination: "max_turns_exhausted" } : {}),
1537
+ ...(this.config.goal ? { goalTerminationRound: this.stopBlockCount } : {}),
1158
1538
  };
1159
1539
  }
1160
1540
  /**
@@ -1213,6 +1593,14 @@ export class TurnLoop {
1213
1593
  // really just a cancel. The signal is the authoritative cancel source.
1214
1594
  if (isAbortError(err) || this.config.signal?.aborted)
1215
1595
  throw err;
1596
+ // Pause/delete arrived while the streaming attempt was in flight. A
1597
+ // non-streaming fallback would be a brand-new provider request after the
1598
+ // promised safe boundary, so revoke any partial stream and synthesize an
1599
+ // empty settled step instead.
1600
+ if (this.goalControlStopRequested) {
1601
+ this.config.onStream?.({ type: "tombstone", messageId: assistantMessageId });
1602
+ return { text: "", toolCalls: [], stopReason: "stop" };
1603
+ }
1216
1604
  // Sensitive tool results are model-facing exactly once. A streaming
1217
1605
  // fallback would re-send the same pending plaintext in a second request,
1218
1606
  // so fail the turn and let the unified exit redact returned history.
@@ -1238,8 +1626,25 @@ export class TurnLoop {
1238
1626
  return this.turnCount;
1239
1627
  }
1240
1628
  async consumeQueuedSteer(messages, source) {
1241
- const steered = this.deps.consumeSteer?.(source) ?? [];
1242
1629
  let consumed = false;
1630
+ const goalControl = this.pendingGoalControlMessage;
1631
+ this.pendingGoalControlMessage = null;
1632
+ if (goalControl &&
1633
+ this.config.goal?.paused !== true &&
1634
+ this.config.goal?.goalId === goalControl.goalId &&
1635
+ (this.config.goal?.revision ?? 1) === (goalControl.revision ?? 1)) {
1636
+ const content = `<system-reminder>\n${goalControl.text}\n</system-reminder>`;
1637
+ const message = { role: "user", content };
1638
+ messages.push(message);
1639
+ this.deps.transcript.appendMessage("user", content, {
1640
+ injected: true,
1641
+ source: "goal-control",
1642
+ });
1643
+ consumed = true;
1644
+ }
1645
+ const directionConsumed = await this.consumeQueuedAgentDirections(messages, source);
1646
+ const steered = this.deps.consumeSteer?.(source) ?? [];
1647
+ consumed ||= directionConsumed;
1243
1648
  for (let index = 0; index < steered.length; index++) {
1244
1649
  const item = steered[index];
1245
1650
  const { id, text, clientMessageId } = item;
@@ -1285,6 +1690,42 @@ export class TurnLoop {
1285
1690
  }
1286
1691
  return consumed;
1287
1692
  }
1693
+ async consumeQueuedAgentDirections(messages, source) {
1694
+ if (!this.deps.consumeAgentDirections)
1695
+ return false;
1696
+ this.deps.onAgentControlState?.("safe-point");
1697
+ const directions = this.deps.consumeAgentDirections();
1698
+ if (directions.length === 0)
1699
+ return false;
1700
+ directions.sort((left, right) => left.sequence - right.sequence);
1701
+ const content = buildAgentDirectionMessage(directions);
1702
+ const envelopeIds = directions.map((item) => item.id);
1703
+ const correlationIds = directions
1704
+ .map((item) => item.correlationId)
1705
+ .filter((value) => value !== undefined);
1706
+ const hook = await this.emitHook("agent_direction_submit", {
1707
+ source: "agent-direction",
1708
+ authority: "agent",
1709
+ prompt: directions.map((item) => item.payload.prompt).join("\n"),
1710
+ envelopeIds,
1711
+ correlationIds,
1712
+ safePoint: source,
1713
+ });
1714
+ const message = { role: "user", content };
1715
+ messages.push(message);
1716
+ this.deps.transcript.appendMessage("user", content, {
1717
+ injected: true,
1718
+ authority: "agent",
1719
+ source: "agent-direction",
1720
+ envelopeIds,
1721
+ correlationIds,
1722
+ });
1723
+ this.deps.onAgentDirectionsDelivered?.(envelopeIds);
1724
+ const hookInjection = wrapHookMessages(hook.messages);
1725
+ if (hookInjection)
1726
+ messages.push(hookInjection);
1727
+ return true;
1728
+ }
1288
1729
  /**
1289
1730
  * Generate synthetic error tool_results for any dangling tool_use blocks
1290
1731
  * that never received results (e.g. because the API call failed).