minimal-agent 0.13.0 → 0.15.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 (474) hide show
  1. package/README.md +6 -5
  2. package/package.json +60 -4
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +197 -206
  16. package/plugins/workflow-runner/src/index.ts +248 -249
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +400 -489
  28. package/plugins/workflow-runner/src/runner.ts +488 -560
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +151 -307
  54. package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -211
  58. package/plugins/workflow-runner/src/types.ts +154 -623
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +474 -802
  68. package/plugins/workflow-runner/src/workflowState.ts +575 -970
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/ui-design/SKILL.md +5 -2
  92. package/skills/ui-design/references/layout-composition.md +1 -1
  93. package/skills/workflow-creator/SKILL.md +344 -0
  94. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  95. package/skills/workflow-creator/references/api.md +216 -0
  96. package/skills/workflow-creator/references/migration.md +103 -0
  97. package/skills/workflow-creator/references/patterns.md +779 -0
  98. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  99. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  100. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  101. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  102. package/src/advisor/advisorCoordinator.js +536 -0
  103. package/src/advisor/advisorCoordinator.ts +693 -0
  104. package/src/cli/configMenu.tsx +0 -1
  105. package/src/cli/print.js +27 -6
  106. package/src/cli/print.ts +48 -4
  107. package/src/cli/quickModelSetup.js +12 -1
  108. package/src/cli/quickModelSetup.tsx +15 -3
  109. package/src/cli/streamJson.js +5 -6
  110. package/src/cli/streamJson.ts +8 -4
  111. package/src/cli/wizards/advisorWizard.js +4 -2
  112. package/src/cli/wizards/advisorWizard.tsx +8 -2
  113. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  114. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  115. package/src/cli/wizards/providerPresets.js +18 -8
  116. package/src/cli/wizards/providerPresets.ts +18 -8
  117. package/src/collaboration/taskFlow.js +115 -0
  118. package/src/collaboration/taskFlow.ts +169 -0
  119. package/src/config/configDefaults.js +41 -1
  120. package/src/config/configDefaults.ts +43 -1
  121. package/src/config/configDiagnostics.ts +2 -2
  122. package/src/config/configFile.js +40 -9
  123. package/src/config/configFile.ts +60 -11
  124. package/src/config/configService.js +419 -74
  125. package/src/config/configService.ts +703 -145
  126. package/src/config/numericLimits.js +22 -0
  127. package/src/config/numericLimits.ts +24 -0
  128. package/src/config.js +1 -0
  129. package/src/config.ts +3 -2
  130. package/src/context/budgetPlan.js +1 -1
  131. package/src/context/budgetPlan.ts +1 -1
  132. package/src/context/compact.js +7 -2
  133. package/src/context/compact.ts +9 -2
  134. package/src/context/microCompactLite.js +212 -13
  135. package/src/context/microCompactLite.ts +261 -12
  136. package/src/context/sessionRegistry.js +8 -1
  137. package/src/context/sessionRegistry.ts +18 -5
  138. package/src/context/toolResultArchive.js +72 -0
  139. package/src/context/toolResultArchive.ts +95 -0
  140. package/src/goal/errors.js +8 -0
  141. package/src/goal/errors.ts +18 -0
  142. package/src/goal/goalRuntime.js +344 -0
  143. package/src/goal/goalRuntime.ts +466 -0
  144. package/src/goal/goalStore.js +200 -0
  145. package/src/goal/goalStore.ts +219 -0
  146. package/src/goal/index.js +4 -0
  147. package/src/goal/index.ts +4 -0
  148. package/src/goal/statusMapping.js +71 -0
  149. package/src/goal/statusMapping.ts +95 -0
  150. package/src/goal/types.js +7 -0
  151. package/src/goal/types.ts +179 -0
  152. package/src/goal/workflowController.js +306 -0
  153. package/src/goal/workflowController.ts +360 -0
  154. package/src/harness/evidenceLedger.js +2 -0
  155. package/src/harness/evidenceLedger.ts +1 -0
  156. package/src/harness/stallGuard.js +9 -3
  157. package/src/harness/stallGuard.ts +7 -3
  158. package/src/images/imageResolver.js +161 -0
  159. package/src/images/imageResolver.ts +189 -0
  160. package/src/infra/verification.js +34 -5
  161. package/src/infra/verification.ts +39 -4
  162. package/src/llm/client.js +79 -21
  163. package/src/llm/client.ts +182 -35
  164. package/src/llm/gatewayClient.js +201 -0
  165. package/src/llm/gatewayClient.ts +299 -0
  166. package/src/llm/messageProvenance.js +46 -0
  167. package/src/llm/messageProvenance.ts +58 -0
  168. package/src/llm/modelProvider.js +1 -0
  169. package/src/llm/modelProvider.ts +22 -0
  170. package/src/llm/providers/anthropicFormat.js +108 -1
  171. package/src/llm/providers/anthropicFormat.ts +115 -4
  172. package/src/llm/providers/openaiFormat.js +9 -0
  173. package/src/llm/providers/openaiFormat.ts +12 -3
  174. package/src/loop.js +328 -55
  175. package/src/loop.ts +382 -58
  176. package/src/main.js +4 -1
  177. package/src/main.tsx +4 -1
  178. package/src/managedExecution/index.js +2 -0
  179. package/src/managedExecution/index.ts +2 -0
  180. package/src/managedExecution/registry.js +15 -0
  181. package/src/managedExecution/registry.ts +20 -0
  182. package/src/managedExecution/types.js +1 -0
  183. package/src/managedExecution/types.ts +86 -0
  184. package/src/modelRef.js +9 -0
  185. package/src/modelRef.ts +31 -0
  186. package/src/multimodal.js +14 -28
  187. package/src/multimodal.ts +17 -33
  188. package/src/net/lanDiscovery.js +65 -0
  189. package/src/net/lanDiscovery.ts +82 -0
  190. package/src/plugin-sdk.js +28 -3
  191. package/src/plugin-sdk.ts +39 -6
  192. package/src/plugins/pluginApi.ts +16 -1
  193. package/src/plugins/pluginLoader.js +4 -0
  194. package/src/plugins/pluginLoader.ts +6 -0
  195. package/src/plugins/pluginRunner.js +139 -7
  196. package/src/plugins/pluginRunner.ts +173 -9
  197. package/src/plugins/slashFallback.js +113 -0
  198. package/src/plugins/slashFallback.ts +137 -0
  199. package/src/prompts/policyRegistry.js +12 -32
  200. package/src/prompts/policyRegistry.ts +15 -32
  201. package/src/prompts/promptAssembly.js +103 -12
  202. package/src/prompts/promptAssembly.ts +114 -12
  203. package/src/prompts/skillList.js +21 -3
  204. package/src/prompts/skillList.ts +27 -2
  205. package/src/prompts/stableSystemPrompt.js +18 -0
  206. package/src/prompts/stableSystemPrompt.ts +33 -0
  207. package/src/runtime/agentRuntimeContext.js +8 -2
  208. package/src/runtime/agentRuntimeContext.ts +17 -4
  209. package/src/session/runAgentTurn.js +78 -12
  210. package/src/session/runAgentTurn.ts +105 -23
  211. package/src/session/runTurn.js +10 -9
  212. package/src/session/runTurn.ts +17 -8
  213. package/src/session/sessionState.ts +2 -0
  214. package/src/team/auth.js +186 -0
  215. package/src/team/auth.ts +298 -0
  216. package/src/team/cli.js +105 -0
  217. package/src/team/cli.ts +122 -0
  218. package/src/team/onlineToolContract.js +64 -0
  219. package/src/team/onlineToolContract.ts +109 -0
  220. package/src/team/toolPolicy.js +36 -0
  221. package/src/team/toolPolicy.ts +75 -0
  222. package/src/tools/advisor/advisor.js +115 -50
  223. package/src/tools/advisor/advisor.ts +124 -51
  224. package/src/tools/advisor/advisorDecision.js +71 -0
  225. package/src/tools/advisor/advisorDecision.ts +85 -0
  226. package/src/tools/agent/agent.js +18 -5
  227. package/src/tools/agent/agent.ts +21 -7
  228. package/src/tools/agent/agentControl.js +246 -0
  229. package/src/tools/agent/agentControl.ts +284 -0
  230. package/src/tools/agent/agentTaskService.js +40 -1
  231. package/src/tools/agent/agentTaskService.ts +44 -1
  232. package/src/tools/agent/agentTypes.js +14 -2
  233. package/src/tools/agent/agentTypes.ts +15 -2
  234. package/src/tools/applypatch/applyPatch.js +362 -0
  235. package/src/tools/applypatch/applyPatch.ts +428 -0
  236. package/src/tools/bash/bash.js +22 -15
  237. package/src/tools/bash/bash.ts +23 -17
  238. package/src/tools/capabilities.js +10 -5
  239. package/src/tools/capabilities.ts +10 -5
  240. package/src/tools/computeruse/computerIntent.js +32 -0
  241. package/src/tools/computeruse/computerIntent.ts +46 -0
  242. package/src/tools/computeruse/telemetryLog.js +12 -0
  243. package/src/tools/computeruse/telemetryLog.ts +16 -0
  244. package/src/tools/computeruse/vision/telemetry.js +1 -0
  245. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  246. package/src/tools/effectiveToolSet.js +4 -0
  247. package/src/tools/effectiveToolSet.ts +4 -0
  248. package/src/tools/index.js +21 -2
  249. package/src/tools/index.ts +33 -2
  250. package/src/tools/presentresult/presentresult.js +6 -15
  251. package/src/tools/presentresult/presentresult.ts +6 -15
  252. package/src/tools/read/read.js +62 -9
  253. package/src/tools/read/read.ts +63 -9
  254. package/src/tools/shared/fileUtils.js +1 -2
  255. package/src/tools/shared/fileUtils.ts +1 -2
  256. package/src/tools/shared/toolOutputStore.js +23 -1
  257. package/src/tools/shared/toolOutputStore.ts +22 -1
  258. package/src/tools/skill/skill.js +9 -3
  259. package/src/tools/skill/skill.ts +8 -4
  260. package/src/tools/toolPromptGuides.js +125 -0
  261. package/src/tools/toolPromptGuides.ts +146 -0
  262. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  263. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  264. package/src/tools/webbrowser/browser.js +27 -11
  265. package/src/tools/webbrowser/browser.ts +68 -24
  266. package/src/tools/webbrowser/browserAutomation.js +329 -0
  267. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  268. package/src/tools/webbrowser/browserAxState.js +97 -0
  269. package/src/tools/webbrowser/browserAxState.ts +142 -0
  270. package/src/tools/webbrowser/browserBatch.js +1578 -0
  271. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  272. package/src/tools/webbrowser/browserCdp.js +208 -0
  273. package/src/tools/webbrowser/browserCdp.ts +255 -0
  274. package/src/tools/webbrowser/browserConditions.js +24 -0
  275. package/src/tools/webbrowser/browserConditions.ts +45 -0
  276. package/src/tools/webbrowser/browserContent.js +281 -0
  277. package/src/tools/webbrowser/browserContent.ts +438 -0
  278. package/src/tools/webbrowser/browserControl.js +131 -34
  279. package/src/tools/webbrowser/browserControl.ts +210 -56
  280. package/src/tools/webbrowser/browserDaemon.js +22 -14
  281. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  282. package/src/tools/webbrowser/browserFrames.js +47 -0
  283. package/src/tools/webbrowser/browserFrames.ts +54 -0
  284. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  285. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  286. package/src/tools/webbrowser/browserLocator.js +140 -0
  287. package/src/tools/webbrowser/browserLocator.ts +194 -0
  288. package/src/tools/webbrowser/browserOutcome.js +10 -17
  289. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  290. package/src/tools/webbrowser/browserRecovery.js +58 -0
  291. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  292. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  293. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  294. package/src/tools/webbrowser/browserService.js +28 -2
  295. package/src/tools/webbrowser/browserService.ts +49 -9
  296. package/src/tools/webbrowser/browserStream.js +132 -33
  297. package/src/tools/webbrowser/browserStream.ts +224 -50
  298. package/src/tools/webbrowser/browserTabs.js +48 -15
  299. package/src/tools/webbrowser/browserTabs.ts +178 -96
  300. package/src/tools/webbrowser/webbrowser.js +679 -144
  301. package/src/tools/webbrowser/webbrowser.ts +975 -236
  302. package/src/tools/webfetch/preapproved.js +0 -2
  303. package/src/tools/webfetch/preapproved.ts +0 -2
  304. package/src/tools/websearch/websearch.js +129 -8
  305. package/src/tools/websearch/websearch.ts +138 -6
  306. package/src/types.ts +459 -40
  307. package/src/ui/App.js +377 -58
  308. package/src/ui/App.tsx +576 -111
  309. package/src/ui/LiveArea.js +14 -8
  310. package/src/ui/LiveArea.tsx +14 -10
  311. package/src/ui/MessageList.js +63 -20
  312. package/src/ui/MessageList.tsx +101 -16
  313. package/src/ui/ModelPicker.js +7 -4
  314. package/src/ui/ModelPicker.tsx +55 -8
  315. package/src/ui/hooks/useChat.js +152 -163
  316. package/src/ui/hooks/useChat.ts +396 -334
  317. package/src/ui/loginCommand.js +56 -0
  318. package/src/ui/loginCommand.ts +63 -0
  319. package/src/ui/toolProgress.js +41 -0
  320. package/src/ui/toolProgress.ts +44 -0
  321. package/src/web/browserBridge.js +20 -2
  322. package/src/web/browserBridge.ts +20 -2
  323. package/src/web/protocol.ts +1897 -194
  324. package/workflows/adaptive-research/workflow.mjs +86 -0
  325. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  326. package/workflows/e2e-llm/workflow.mjs +52 -0
  327. package/workflows/e2e-smoke/workflow.mjs +59 -0
  328. package/workflows/e2e-vision-schema/README.md +48 -0
  329. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  330. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  331. package/workflows/file-census/README.md +14 -0
  332. package/workflows/file-census/scripts/.gitkeep +2 -0
  333. package/workflows/file-census/workflow.mjs +110 -0
  334. package/workflows/goal/README.md +166 -0
  335. package/workflows/goal/prompts/audit.md +57 -0
  336. package/workflows/goal/prompts/contract.md +56 -0
  337. package/workflows/goal/prompts/iterate.md +37 -0
  338. package/workflows/goal/workflow.mjs +501 -0
  339. package/workflows/illustrate/README.md +90 -0
  340. package/workflows/illustrate/assets/engines.json +20 -0
  341. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  342. package/workflows/illustrate/assets/extract-bing.js +33 -0
  343. package/workflows/illustrate/assets/extract-google.js +31 -0
  344. package/workflows/illustrate/workflow.mjs +301 -0
  345. package/workflows/plan/workflow.mjs +389 -0
  346. package/workflows/question-crops/README.md +35 -0
  347. package/workflows/question-crops/prompts/inventory.md +23 -0
  348. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  349. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  350. package/workflows/question-crops/workflow.mjs +172 -0
  351. package/workflows/ralph-loop/workflow.mjs +162 -0
  352. package/workflows/research/README.md +74 -0
  353. package/workflows/research/prompts/s0-goal.md +43 -0
  354. package/workflows/research/prompts/s1-early-exit.md +22 -0
  355. package/workflows/research/prompts/s1-map.md +58 -0
  356. package/workflows/research/prompts/s2-options.md +53 -0
  357. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  358. package/workflows/research/prompts/s4-verify.md +74 -0
  359. package/workflows/research/prompts/s5-synthesis.md +81 -0
  360. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  361. package/workflows/research/prompts/s6-package.md +32 -0
  362. package/workflows/research/scripts/build-report.mjs +98 -0
  363. package/workflows/research/workflow.mjs +288 -0
  364. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  365. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  366. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  367. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  368. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  369. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  370. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  371. package/workflows/wrong-question-docx/README.md +55 -0
  372. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  373. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  374. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  375. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  376. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  377. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  378. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  379. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  380. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  381. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  382. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  383. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  384. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  385. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  386. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  387. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  388. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  389. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  390. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  391. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  392. package/plugins/workflow-runner/src/expressions.js +0 -639
  393. package/plugins/workflow-runner/src/expressions.ts +0 -640
  394. package/plugins/workflow-runner/src/loader.js +0 -573
  395. package/plugins/workflow-runner/src/loader.ts +0 -721
  396. package/plugins/workflow-runner/src/multimodal.js +0 -38
  397. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  398. package/plugins/workflow-runner/src/recovery.js +0 -126
  399. package/plugins/workflow-runner/src/recovery.ts +0 -156
  400. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  401. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  402. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -42
  403. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
  404. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  405. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  406. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  407. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  408. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  409. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  410. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  411. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  412. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -227
  413. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
  414. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
  415. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
  416. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  417. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  418. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  419. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  420. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  421. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  422. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -167
  423. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  424. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  425. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +0 -314
  426. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
  427. package/plugins/workflow-runner/test/expressions.test.ts +0 -439
  428. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  429. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  430. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  431. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  432. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  433. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  434. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  435. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  436. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  437. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  438. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -106
  439. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  440. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  441. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -221
  442. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  443. package/plugins/workflow-runner/test/research.load.test.ts +0 -328
  444. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  445. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
  446. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  447. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
  448. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  449. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
  450. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
  451. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  452. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  453. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  454. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  455. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  456. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  457. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  458. package/src/advisorState.js +0 -22
  459. package/src/cli/configWizard.js +0 -293
  460. package/src/tools/toolProgressBridge.js +0 -48
  461. package/workflows/adaptive-research.yaml +0 -50
  462. package/workflows/book-review-short.yaml +0 -125
  463. package/workflows/e2e-write-greet.yaml +0 -31
  464. package/workflows/illustrate.yaml +0 -227
  465. package/workflows/plan.yaml +0 -470
  466. package/workflows/ppt.yaml +0 -1670
  467. package/workflows/ralph-loop.yaml +0 -201
  468. package/workflows/research.yaml +0 -873
  469. package/workflows/schema.json +0 -176
  470. package/workflows/webppt.yaml +0 -496
  471. package/workflows/wechat-reading-shots.yaml +0 -315
  472. package/workflows/word.yaml +0 -720
  473. package/workflows/youtube-shorts.yaml +0 -202
  474. /package/workflows/{research.md → research/DESIGN.md} +0 -0
package/src/loop.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * d. 否则并行执行所有工具调用(OpenAI 协议要求 tool 消息按 tool_calls 顺序回填)
19
19
  * e. 进入下一轮(让模型看到 tool_result 后继续推理)
20
20
  *
21
- * 失控保护:maxTurns(默认 50)。
21
+ * 失控保护:maxTurns(默认 200)。
22
22
  * 中断支持:AbortSignal 透传到 chat() 和 tool.call()。
23
23
  *
24
24
  * "工作过程显示"事件:
@@ -29,12 +29,20 @@
29
29
 
30
30
  import crypto from 'node:crypto';
31
31
  import { autoCompactIfNeeded, getCompactThreshold } from './context/compact.js';
32
- import { microCompact, incrementTurn, expireOldEntries } from './context/microCompactLite.js';
32
+ import {
33
+ microCompact,
34
+ incrementTurn,
35
+ expireOldEntries,
36
+ reclaimOldToolResults,
37
+ resetCacheEdits,
38
+ getToolResultPolicy,
39
+ } from './context/microCompactLite.js';
33
40
  import {
34
41
  isPromptTooLongError,
35
42
  reactiveCompactIfApplicable,
36
43
  } from './context/reactiveCompact.js';
37
44
  import { snipCompactIfNeeded } from './context/snipCompact.js';
45
+ import { archiveToolResult } from './context/toolResultArchive.js';
38
46
  import {
39
47
  countMessagesTokens,
40
48
  countTextTokens,
@@ -63,10 +71,14 @@ import type {
63
71
  ToolCall,
64
72
  ToolCallContext,
65
73
  ToolResult,
74
+ TurnResourceUsage,
66
75
  } from './types.js';
67
76
  import { configService } from './config/configService.js';
68
77
  import { capabilityForToolName } from './tools/capabilities.js';
69
78
  import { createEffectiveToolSet } from './tools/effectiveToolSet.js';
79
+ import { classifyComputerAutomationIntent } from './tools/computeruse/computerIntent.js';
80
+ import { recordComputerUseExposure } from './tools/computeruse/telemetryLog.js';
81
+ import { filterToolsForProvider } from './team/toolPolicy.js';
70
82
  import { hydrateSessionWorktree } from './tools/worktree/sessionWorktree.js';
71
83
  import {
72
84
  capabilitiesForTools,
@@ -75,21 +87,28 @@ import {
75
87
  import { EvidenceLedger } from './harness/evidenceLedger.js';
76
88
  import { StallGuard } from './harness/stallGuard.js';
77
89
  import { validateFinalClaims } from './harness/claimValidation.js';
90
+ import {
91
+ AdvisorCoordinator,
92
+ isWorkspaceMutationTool,
93
+ type AdvisorToolSemantics,
94
+ } from './advisor/advisorCoordinator.js';
95
+ import { classifyCommand } from './irreversible/rules.js';
78
96
  import {
79
97
  createAgentRuntimeContext,
80
98
  type AgentRuntimeContext,
81
99
  } from './runtime/agentRuntimeContext.js';
100
+ import { stripThinkBlocks } from './utils/stripThink.js';
82
101
 
83
- const DELIVERY_ARTIFACT = '(?:文件|合同|文档|表格|报告|图片|图像|海报|幻灯片|PPT|PDF|代码|脚本|压缩包|网页|页面|HTML|DOCX|XLSX|CSV|ZIP)';
84
- const DELIVERY_ACTION = '(?:发|发送|交付|提交|给|传|制作|生成|创建|导出|输出|修改|更新|完成)';
85
- const ENGLISH_DELIVERY_ARTIFACT = '(?:file|document|report|spreadsheet|workbook|slides?|presentation|pdf|image|code|script|archive|zip|html|webpage|website)';
86
- const ENGLISH_DELIVERY_ACTION = '(?:send|deliver|submit|provide|create|generate|export|update|modify|finish|produce)';
102
+ const DELIVERY_ARTIFACT = '(?:文件|合同|协议|方案|文档|表格|报告|图片|图像|海报|幻灯片|PPT|PDF|代码|脚本|压缩包|网页|页面|HTML|DOCX|XLSX|CSV|ZIP)';
103
+ const DELIVERY_ACTION = '(?:发送|(?<![开研])发|交付|提交|(?<!宣)传(?!统)|制作|生成|创建|导出|输出|修改|更新|完成|写|起草|做|设计|整理)';
104
+ const ENGLISH_DELIVERY_ARTIFACT = '(?:file|contract|document|report|spreadsheet|workbook|slides?|presentation|pdf|image|code|script|archive|zip|html|webpage|website)';
105
+ const ENGLISH_DELIVERY_ACTION = '(?:send|deliver|submit|provide|create|generate|export|update|modify|finish|produce|draft|write|make|build)';
87
106
 
88
107
  /** Whether this turn explicitly asks for a concrete artifact that should be formally delivered. */
89
108
  export function expectsFormalDelivery(input: string): boolean {
90
109
  const compact = input.replace(/\s+/g, '');
91
110
  const chinese = new RegExp(
92
- `${DELIVERY_ACTION}(?:给)?我?.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}${DELIVERY_ACTION}(?:给)?我?`,
111
+ `${DELIVERY_ACTION}(?:给)?我?.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}${DELIVERY_ACTION}(?:给)?我?|给我.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}给我`,
93
112
  'i',
94
113
  );
95
114
  const english = new RegExp(
@@ -136,7 +155,16 @@ function getToolsSchemaTokens(tools: readonly Tool[], signature?: string): numbe
136
155
  }
137
156
 
138
157
  /**
139
- * 顾问咨询配额:基线 1 次,之后每累计 N 次工具调用解锁 1 次,封顶 3 次。
158
+ * 主工具循环的默认轮数上限 —— **唯一真值**。
159
+ *
160
+ * 2026-08 从 50 提到 200(4 倍)。它此前是 `options.maxTurns ?? 200` 里的一个裸字面量,
161
+ * 没有名字也就没有引用点,于是「跟着它一起动的东西」(顾问配额封顶)改不动 ——
162
+ * 见下面的 `ADVISOR_QUOTA_MAX`。
163
+ */
164
+ export const DEFAULT_MAX_TURNS = 200;
165
+
166
+ /**
167
+ * 顾问咨询配额:基线 1 次,之后每累计 N 次工具调用解锁 1 次,封顶见 ADVISOR_QUOTA_MAX。
140
168
  *
141
169
  * 曾经是硬编码 1 次/回合。那个数字隐含「一个用户回合 = 一个决策点」的假设,
142
170
  * 在短问答里成立,在「继续,完成后续所有工作」这类一句话跑几十步的长回合里
@@ -149,9 +177,21 @@ function getToolsSchemaTokens(tools: readonly Tool[], signature?: string): numbe
149
177
  * 刷工具调用换配额的成本远高于收益。
150
178
  */
151
179
  const ADVISOR_QUOTA_BASE = 1;
152
- const ADVISOR_QUOTA_MAX = 3;
153
180
  const ADVISOR_QUOTA_UNLOCK_EVERY_TOOL_CALLS = 15;
154
181
 
182
+ /**
183
+ * 配额封顶**由 `DEFAULT_MAX_TURNS` 推导,不是独立的魔术数字**。
184
+ *
185
+ * 上面那段注释的论据是「一个长回合里真正值钱的咨询点至少三个」——那是按当时
186
+ * `maxTurns = 50` 算的。轮数上限后来提到 200(4 倍工作量塞进同一个回合),
187
+ * 「高风险批次做完」这类节点随之变多,封顶却没跟着动,于是配额重新变成瓶颈:
188
+ * 这正是它当年从「硬编码 1 次」被改掉的同一个毛病,只是换了个刻度。
189
+ *
190
+ * 用公式而不是再写一个字面量,两者才会一起动:
191
+ * 50 轮 → 3(复现历史值) · 200 轮 → 5 · 下限恒为 3
192
+ */
193
+ const ADVISOR_QUOTA_MAX = Math.max(3, Math.round(DEFAULT_MAX_TURNS / 40));
194
+
155
195
  export function advisorQuotaFor(executedToolCalls: number): number {
156
196
  return Math.min(
157
197
  ADVISOR_QUOTA_MAX,
@@ -159,6 +199,30 @@ export function advisorQuotaFor(executedToolCalls: number): number {
159
199
  );
160
200
  }
161
201
 
202
+ function advisorSemanticsFor(call: ToolCall): AdvisorToolSemantics {
203
+ let parsed: Record<string, unknown> = {};
204
+ try { parsed = JSON.parse(call.function.arguments) as Record<string, unknown>; } catch { /* zod layer reports malformed input */ }
205
+ const mutatesWorkspace = isWorkspaceMutationTool(call.function.name);
206
+ if (call.function.name === 'Bash' || call.function.name === 'PowerShell') {
207
+ const command = typeof parsed.command === 'string' ? parsed.command : '';
208
+ const risk = command ? classifyCommand(command) : null;
209
+ return { mutatesWorkspace, highRisk: risk?.riskLevel === 'critical', riskReason: risk?.why };
210
+ }
211
+ if (call.function.name === 'ComputerUse') {
212
+ const highRisk = parsed.intent === 'submit'
213
+ || (parsed.action === 'window' && parsed.windowOperation === 'close');
214
+ return { mutatesWorkspace, highRisk, ...(highRisk ? { riskReason: '原生界面操作不可逆或可能提交外部动作' } : {}) };
215
+ }
216
+ if (call.function.name === 'WebBrowser' || call.function.name === 'BrowserBatch') {
217
+ const highRisk = parsed.intent === 'submit'
218
+ || (call.function.name === 'BrowserBatch'
219
+ && Array.isArray(parsed.steps)
220
+ && parsed.steps.some((step) => typeof step === 'object' && step !== null && (step as Record<string, unknown>).intent === 'submit'));
221
+ return { mutatesWorkspace, highRisk, ...(highRisk ? { riskReason: '浏览器动作将进行发布、购买、删除或最终提交' } : {}) };
222
+ }
223
+ return { mutatesWorkspace, highRisk: false };
224
+ }
225
+
162
226
  export interface RunQueryOptions {
163
227
  provider: Provider;
164
228
  /** Canonical host-issued trust context. Legacy fields below remain adapters. */
@@ -174,6 +238,25 @@ export interface RunQueryOptions {
174
238
  allowedTools?: string[];
175
239
  /** Tools the host/workflow explicitly requires; emergency schema reduction preserves them. */
176
240
  requiredTools?: string[];
241
+ /**
242
+ * provider 原生结构化输出(`response_format.json_schema`),原样透传给 `chat()`。
243
+ *
244
+ * ⚠️ **只在本轮没有工具时才该传**:response_format 会强制**每一条** assistant
245
+ * 消息都是 JSON,而工具调用轮的 assistant 消息带的是 tool_calls —— 两者互斥,
246
+ * 同时给会让带工具的循环直接崩。工作流引擎因此只在 `llm()`(零工具)和
247
+ * schema 修复轮(零工具)上传它;`agent()` 的结构化输出走「跑完再修复」那条路。
248
+ *
249
+ * Anthropic 格式的 provider 不支持,`chat()` 会在发请求前抛错 —— 所以它是
250
+ * **加成**而不是地基,地基是引擎侧的「校验 + 有界修复」。
251
+ */
252
+ responseFormat?: {
253
+ type: 'json_schema';
254
+ json_schema: {
255
+ name: string;
256
+ schema: Record<string, unknown>;
257
+ strict?: boolean;
258
+ };
259
+ };
177
260
  /**
178
261
  * Host-provided tool catalog. Production hosts normally omit this and use ALL_TOOLS;
179
262
  * tests and embedders with custom executors must provide matching schemas so the
@@ -244,8 +327,12 @@ export interface RunQueryOptions {
244
327
  promptCache?: import('./types.js').PromptCacheContext;
245
328
  /** 仅供宿主 UI 恢复附件卡片;不会发送给 LLM。 */
246
329
  userAttachments?: import('./types.js').MessageAttachmentRef[];
330
+ /** Host-authorized files for typed workflow inputs; runtime-only paths. */
331
+ inputResources?: readonly import('./types.js').InvocationResource[];
247
332
  /** Stable host-provided identity for the user message of this turn. */
248
333
  userMessageId?: string;
334
+ /** Trusted local origin for every message created by this run. */
335
+ messageProvenance?: import('./types.js').ConversationProvenance;
249
336
  /** Browser element chips are persisted for UI but stripped before provider calls. */
250
337
  userBrowserReferences?: import('./types.js').MessageBrowserReference[];
251
338
  /** 显式传入的图片附件(-p --image / stream-json images)。 */
@@ -282,8 +369,8 @@ export async function* runQuery(
282
369
  // Tool screenshots are transient evidence for the tool continuation that produced them.
283
370
  // Never carry a prior turn's base64 image into a new user request.
284
371
  evictPriorVisionShots(history);
285
- const maxTurns = options.maxTurns ?? 50;
286
- const runtimeContext = options.runtimeContext ?? createAgentRuntimeContext({
372
+ const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
373
+ const baseRuntimeContext = options.runtimeContext ?? createAgentRuntimeContext({
287
374
  surface: 'headless',
288
375
  profile: 'main',
289
376
  cwd: process.cwd(),
@@ -297,10 +384,24 @@ export async function* runQuery(
297
384
  computerIntervention: options.computerIntervention,
298
385
  toolGate: options.toolGate,
299
386
  });
387
+ const computerIntent = baseRuntimeContext.computerIntent
388
+ ?? classifyComputerAutomationIntent(userInput, sessionState?.computerAutomation);
389
+ const runtimeContext = createAgentRuntimeContext({
390
+ ...baseRuntimeContext,
391
+ computerIntent,
392
+ });
393
+ if (runtimeContext.computer) {
394
+ void recordComputerUseExposure(
395
+ computerIntent.needed,
396
+ computerIntent.source,
397
+ );
398
+ }
399
+ if (!computerIntent.needed && sessionState) delete sessionState.computerAutomation;
300
400
  // DI fallback:测试可传 stub,生产走真实模块单例
301
401
  const chatFn = options.chat ?? defaultChat;
302
402
  const executeToolFn = options.executeTool ?? defaultExecuteTool;
303
403
  const explicitFileDeliveryRequested = expectsFormalDelivery(userInput);
404
+ const currentUserMessageId = options.userMessageId ?? crypto.randomUUID();
304
405
 
305
406
  const transientFor = (tools: readonly Tool[]): string => {
306
407
  const opaque = options.transientContext?.trim() ?? '';
@@ -335,7 +436,15 @@ export async function* runQuery(
335
436
  history,
336
437
  sessionState,
337
438
  // 配额随本回合工作量增长(见 advisorQuotaFor);下面每轮工具执行完后上调。
338
- advisorCalls: { used: 0, max: advisorQuotaFor(0) },
439
+ // mandatoryUsed/mandatoryMax 是独立的"强制检查点"配额池,不随工具调用数
440
+ // 增长——它是安全闸门的固定配额,不是"干得越多奖励越多"的自愿咨询配额;
441
+ // 复用 ADVISOR_QUOTA_MAX 而不新增字面量,避免和自愿池的封顶各写各的漂移。
442
+ advisorCalls: {
443
+ used: 0,
444
+ max: advisorQuotaFor(0),
445
+ mandatoryUsed: 0,
446
+ mandatoryMax: ADVISOR_QUOTA_MAX,
447
+ },
339
448
  advisorInvocation: 'agent',
340
449
  // Agent 等派生工具起嵌套子查询所需的运行态:与主循环同一 provider、
341
450
  // 同一审批闸门(子 agent 不绕过宿主策略)。agentCalls 与 advisorCalls 同构,
@@ -350,6 +459,8 @@ export async function* runQuery(
350
459
  currentUserInput: userInput,
351
460
  userMessageId: options.userMessageId,
352
461
  memoryAccess: runtimeContext.memoryAccess,
462
+ conversationMode: runtimeContext.conversationMode ?? 'private',
463
+ surface: runtimeContext.surface,
353
464
  workspaceId: runtimeContext.workspaceId,
354
465
  sessionId: runtimeContext.sessionId,
355
466
  cwd: runtimeContext.cwd,
@@ -362,10 +473,11 @@ export async function* runQuery(
362
473
  // 实际下发给模型的工具集:allowedTools 未传时给全量;
363
474
  // 传入数组时严格按数组过滤,[] 表示本轮不下发任何工具。
364
475
  // 仅影响呈现给 LLM 的工具清单,不影响 executeTool 的内部调度能力。
365
- const hostTools = options.availableTools ?? ALL_TOOLS;
476
+ const hostTools = filterToolsForProvider(options.availableTools ?? ALL_TOOLS, provider);
366
477
  const availableTools = hostTools.filter((tool) =>
367
478
  (tool.name !== 'Agent' || subagents.enabled)
368
- && (tool.name !== 'ComputerUse' || runtimeContext.computer !== undefined));
479
+ && (tool.name !== 'ComputerUse'
480
+ || (runtimeContext.computer !== undefined && runtimeContext.computerIntent?.needed === true)));
369
481
  const effectiveToolSet = createEffectiveToolSet({
370
482
  availableTools,
371
483
  runtime: runtimeContext,
@@ -378,6 +490,30 @@ export async function* runQuery(
378
490
  && runtimeContext.surface !== 'workflow'
379
491
  && effectiveToolSet.names.has('PresentResult');
380
492
  let currentEffectiveToolSet = effectiveToolSet;
493
+ const advisorCoordinator = new AdvisorCoordinator({
494
+ history,
495
+ currentRequest: userInput,
496
+ currentRequestMessageId: currentUserMessageId,
497
+ advisorAvailable: effectiveToolSet.names.has('Advisor')
498
+ && effectiveToolSet.names.has('AdvisorDecision'),
499
+ });
500
+ toolCallContext.advisorRuntime = advisorCoordinator;
501
+ // 组装每轮请求的易变上下文。三个构建点(首轮 / autoCompact 后 / snip 后)共用,
502
+ // 避免 Advisor 相关提醒在复制粘贴中三处漂移。提醒均为 transient:只进当轮请求副本,
503
+ // 不进会话历史(与 DELIVERY_CORRECTION_REMINDER 同构,见 buildModelMessages)。
504
+ const buildRequestTransientContext = (): string => {
505
+ const advisorAvailable = currentEffectiveToolSet.names.has('Advisor')
506
+ && currentEffectiveToolSet.names.has('AdvisorDecision');
507
+ const parts = [
508
+ transientFor(activeTools),
509
+ deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
510
+ advisorAvailable ? advisorCoordinator.transientReminder() : '',
511
+ advisorAvailable
512
+ ? advisorCoordinator.usageLine(toolCallContext.advisorCalls!.used, toolCallContext.advisorCalls!.max)
513
+ : '',
514
+ ];
515
+ return parts.filter(Boolean).join('\n\n');
516
+ };
381
517
  let lastRequestTransientContext = [options.transientBundle?.text, options.transientContext]
382
518
  .filter(Boolean).join('\n\n');
383
519
  const evidenceLedger = new EvidenceLedger();
@@ -439,6 +575,16 @@ export async function* runQuery(
439
575
  message: `未作为图片附加:${skipped.source}(${skipped.reason}),已按普通文本处理。`,
440
576
  };
441
577
  }
578
+ let visionRequiredThisTurn = Array.isArray(userContent)
579
+ && userContent.some((part) => part.type === 'image_url' || part.type === 'video_url');
580
+ if (visionRequiredThisTurn && provider.supportsVision === false) {
581
+ yield {
582
+ type: 'error',
583
+ error: '当前模型已配置为不支持视觉输入,图片未发送。请切换到支持图片的模型。',
584
+ code: 'llm_error',
585
+ };
586
+ return;
587
+ }
442
588
 
443
589
  // 1. 用户消息入栈(v2:填 timestamp,让 MessageList 的 turn 边界横线
444
590
  // 能拿到首条 user 的真实时间,否则 fallback Date.now() 会丢失"提交时刻"语义)
@@ -448,12 +594,16 @@ export async function* runQuery(
448
594
  ...(options.userAttachments?.length ? { attachments: options.userAttachments } : {}),
449
595
  ...(options.userBrowserReferences?.length ? { browserReferences: options.userBrowserReferences } : {}),
450
596
  timestamp: Date.now(),
451
- id: options.userMessageId ?? crypto.randomUUID(),
597
+ id: currentUserMessageId,
598
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
452
599
  };
453
- history.push(userMessage);
454
- // 立即通知 UI:用户消息已入栈,触发 bump 让 <Static> 马上把它 commit 进 scrollback,
455
- // 不必等整轮 assistant 落定(保证用户输入第一时间置顶,符合 T-A-O-R 展示顺序)。
456
- yield { type: 'user_message_committed', message: userMessage };
600
+ const alreadyCommitted = history.some((message) => message.role === 'user' && message.id === userMessage.id);
601
+ if (!alreadyCommitted) {
602
+ history.push(userMessage);
603
+ // 立即通知 UI:用户消息已入栈,触发 bump 让 <Static> 马上把它 commit 进 scrollback,
604
+ // 不必等整轮 assistant 落定(保证用户输入第一时间置顶,符合 T-A-O-R 展示顺序)。
605
+ yield { type: 'user_message_committed', message: userMessage };
606
+ }
457
607
 
458
608
  // 反应式压缩自救:本 runQuery 内最多触发 MAX_REACTIVE_PER_QUERY 次(防本轮死循环);
459
609
  // 跨 runQuery 的连续失败由 reactiveCompact.ts 的 circuit breaker(consecutiveFailures)兜底。
@@ -463,6 +613,15 @@ export async function* runQuery(
463
613
  // 上一轮 LLM 返回的真实 prompt token 数(done.promptTokens;provider 开 include_usage 才有)。
464
614
  // 作为 autoCompact 的权威判据,校正本地估算的系统性低估;压缩后清空(历史已变,旧值失真)。
465
615
  let lastPromptTokens: number | undefined;
616
+
617
+ // ── 回合级资源账本 ────────────────────────────────────────────────
618
+ // ⚠️ 与 lastPromptTokens 生命周期**故意不同**:那个是「当前 history 有多大」的快照,
619
+ // 压缩后必须清零;这个是「本回合总共花了多少」的累计,压缩不影响已经花掉的钱。
620
+ // ⚠️ 它只记账,**不参与任何控制流** —— loop 绝不因预算中断进行中的工具循环。
621
+ const turnUsage = { inputTokens: 0, outputTokens: 0, totalCalls: 0, reportedCalls: 0 };
622
+
623
+ // 归档退化只报一次:每条超长结果都报会把上下文刷满,反而盖掉别的信号。
624
+ let archiveDegradedWarned = false;
466
625
  // 提示词缓存观测位(纯展示,不参与任何控制流)。与 lastPromptTokens 同生命周期:
467
626
  // 压缩后一并清空,避免把压缩前的命中数据挂到压缩后的新历史上误导排障。
468
627
  let lastCachedPromptTokens: number | undefined;
@@ -472,10 +631,24 @@ export async function* runQuery(
472
631
 
473
632
  let turn = 0;
474
633
  let workStarted = false;
634
+
475
635
  while (turn < maxTurns) {
476
636
  turn++;
477
637
  incrementTurn(sessionState?.microCompact);
478
638
  expireOldEntries(sessionState?.microCompact);
639
+ // Path C 批量回收旧工具结果:可重放的观察类工具结果(Read/Grep/Bash…)
640
+ // 超过阈值时只保留热尾部。必须在 buildContextBudgetPlan 之前跑——预算估算和
641
+ // autoCompact 判据直接看到瘦身后的历史,避免无谓的全量压缩。
642
+ // cacheEdits 模式(Anthropic 格式 + provider.cacheEdits):不改 history,删除目标
643
+ // 记入状态、由请求层组装 cache_edits 发送--服务端 KV 原地删除,前缀缓存完整保留。
644
+ // 本地估算的虚高部分(服务端已删内容)由 autoCompactIfNeeded 的 serverDeletedTokens
645
+ // 校正,判据不会因此过早触发压缩。
646
+ const cacheEditsMode = provider.apiFormat === 'anthropic' && provider.cacheEdits === true;
647
+ reclaimOldToolResults(
648
+ history,
649
+ sessionState?.microCompact,
650
+ cacheEditsMode ? 'cacheEdits' : 'inline',
651
+ );
479
652
 
480
653
  if (signal?.aborted) {
481
654
  pushManualInterruptMarker(history);
@@ -499,10 +672,7 @@ export async function* runQuery(
499
672
 
500
673
  // 2. 自动压缩(判据 = max(上一轮真实 usage, 历史估算 + 工具 schema),
501
674
  // 阈值 provider 无关、按 contextWindow × compactRatio 比例触发)
502
- let requestTransientContext = [
503
- transientFor(activeTools),
504
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
505
- ].filter(Boolean).join('\n\n');
675
+ let requestTransientContext = buildRequestTransientContext();
506
676
  lastRequestTransientContext = requestTransientContext;
507
677
  const preCompactBudget = buildContextBudgetPlan({
508
678
  provider,
@@ -516,6 +686,11 @@ export async function* runQuery(
516
686
  actualPromptTokens: lastPromptTokens,
517
687
  toolsTokens: getToolsSchemaTokens(activeTools, effectiveToolSet.signature),
518
688
  transientTokens: countTextTokens(requestTransientContext),
689
+ // cacheEdits 判据校正:本地 history 未瘦身,扣掉服务端已删部分的估算,
690
+ // 防止虚高估算劫持 Math.max 导致压缩过早触发(压缩会重建整个前缀缓存)。
691
+ ...(cacheEditsMode && sessionState?.microCompact
692
+ ? { serverDeletedTokens: sessionState.microCompact.cacheEdits.activeDeletedTokens }
693
+ : {}),
519
694
  signal,
520
695
  promptCache: options.promptCache,
521
696
  chat: chatFn,
@@ -525,6 +700,9 @@ export async function* runQuery(
525
700
  yield { type: 'stage_change', stage: 'compacting' };
526
701
  history.length = 0;
527
702
  history.push(...compact.messages);
703
+ // cacheEdits 状态随压缩作废:pinned 的翻译后下标指向已不存在的消息序列,
704
+ // 服务端缓存前缀也已失效——不重置会导致删除指令插错位置甚至重复删除。
705
+ if (sessionState?.microCompact) resetCacheEdits(sessionState.microCompact);
528
706
  lastPromptTokens = undefined;
529
707
  lastCachedPromptTokens = undefined;
530
708
  lastCacheMissTokens = undefined;
@@ -553,6 +731,8 @@ export async function* runQuery(
553
731
  if (snipped.messagesRemoved > 0) {
554
732
  history.length = 0;
555
733
  history.push(...snipped.messages);
734
+ // 同 compact:snip 砍头后 pinned 下标失位,cacheEdits 必须重置
735
+ if (sessionState?.microCompact) resetCacheEdits(sessionState.microCompact);
556
736
  lastPromptTokens = undefined;
557
737
  lastCachedPromptTokens = undefined;
558
738
  lastCacheMissTokens = undefined;
@@ -588,10 +768,7 @@ export async function* runQuery(
588
768
 
589
769
  // Re-open transient capacity after compaction. If the compacted tail is still
590
770
  // too large, trim old history before sacrificing any model-visible ability.
591
- requestTransientContext = [
592
- transientFor(activeTools),
593
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
594
- ].filter(Boolean).join('\n\n');
771
+ requestTransientContext = buildRequestTransientContext();
595
772
  lastRequestTransientContext = requestTransientContext;
596
773
  let postCompactBudget = buildContextBudgetPlan({
597
774
  provider,
@@ -616,10 +793,7 @@ export async function* runQuery(
616
793
  messagesRemoved: snipped.messagesRemoved,
617
794
  tokensFreed: snipped.tokensFreed,
618
795
  };
619
- requestTransientContext = [
620
- transientFor(activeTools),
621
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
622
- ].filter(Boolean).join('\n\n');
796
+ requestTransientContext = buildRequestTransientContext();
623
797
  lastRequestTransientContext = requestTransientContext;
624
798
  postCompactBudget = buildContextBudgetPlan({
625
799
  provider,
@@ -644,6 +818,10 @@ export async function* runQuery(
644
818
  runtime: runtimeContext,
645
819
  requiredNames: [...effectiveToolSet.requiredNames],
646
820
  });
821
+ advisorCoordinator.setAvailable(
822
+ currentEffectiveToolSet.names.has('Advisor')
823
+ && currentEffectiveToolSet.names.has('AdvisorDecision'),
824
+ );
647
825
  if (emergencyFit.removedNames.length > 0) {
648
826
  yield {
649
827
  type: 'warning',
@@ -689,12 +867,22 @@ export async function* runQuery(
689
867
 
690
868
  try {
691
869
  const modelMessages = buildModelMessages(history, requestTransientContext, userMessage.id);
870
+ advisorCoordinator.markTransientDelivered();
692
871
  for await (const ev of chatFn({
693
872
  provider,
694
873
  messages: modelMessages,
695
874
  tools: currentEffectiveToolSet.tools,
696
875
  signal,
876
+ conversationMode: runtimeContext.conversationMode ?? 'private',
697
877
  promptCache: options.promptCache,
878
+ visionPolicy: visionRequiredThisTurn ? 'required' : 'optional-history',
879
+ // cacheEdits 回收状态(同一会话级对象跨轮复用;compact 后由 resetCacheEdits 清空)
880
+ ...(cacheEditsMode && sessionState?.microCompact
881
+ ? { cacheEdits: sessionState.microCompact.cacheEdits }
882
+ : {}),
883
+ ...(options.responseFormat
884
+ ? { responseFormat: options.responseFormat }
885
+ : {}),
698
886
  })) {
699
887
  if (ev.type === 'text_delta') {
700
888
  if (!stageStreamingYielded) {
@@ -716,6 +904,8 @@ export async function* runQuery(
716
904
  // reasoning_details 是结构化数组,没有 delta 字符串,跳过 yield
717
905
  reasoningDetails.push(...ev.items);
718
906
  }
907
+ } else if (ev.type === 'usage') {
908
+ if (typeof ev.promptTokens === 'number') lastPromptTokens = ev.promptTokens;
719
909
  } else if (ev.type === 'done') {
720
910
  // R2:记下流的终止原因。'length'(max_tokens 截断)会在本轮收尾时触发 warning。
721
911
  lastFinishReason = ev.stopReason;
@@ -726,6 +916,18 @@ export async function* runQuery(
726
916
  if (typeof ev.cacheMissTokens === 'number') lastCacheMissTokens = ev.cacheMissTokens;
727
917
  if (typeof ev.cacheWriteTokens === 'number') lastCacheWriteTokens = ev.cacheWriteTokens;
728
918
  if (ev.promptCacheUsage) lastPromptCacheUsage = ev.promptCacheUsage;
919
+
920
+ // 回合级记账(与上面的 lastPromptTokens 不同:那个压缩后要清零,这个不清)。
921
+ // ⚠️ 只有 provider 真报了才计入 reportedCalls —— 「没人告诉我」不能被当成
922
+ // 「真的是 0」,否则预算闸永远不触发。覆盖率由 trustworthy 如实暴露。
923
+ turnUsage.totalCalls += 1;
924
+ const reportedIn = typeof ev.promptTokens === 'number' ? ev.promptTokens : undefined;
925
+ const reportedOut = typeof ev.outputTokens === 'number' ? ev.outputTokens : undefined;
926
+ if (reportedIn !== undefined || reportedOut !== undefined) {
927
+ turnUsage.reportedCalls += 1;
928
+ turnUsage.inputTokens += reportedIn ?? 0;
929
+ turnUsage.outputTokens += reportedOut ?? 0;
930
+ }
729
931
  }
730
932
  }
731
933
  } catch (e) {
@@ -824,24 +1026,54 @@ export async function* runQuery(
824
1026
  ...(reasoningDetails.length > 0 ? { reasoning_details: reasoningDetails } : {}),
825
1027
  timestamp: Date.now(),
826
1028
  id: crypto.randomUUID(),
1029
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
827
1030
  };
828
- history.push(assistantMsg);
829
- yield { type: 'assistant_message', message: assistantMsg as Extract<Message, { role: 'assistant' }> };
830
-
831
1031
  // 4. 没有工具调用 → 整轮交互结束
832
1032
  if (toolCallsByIndex.length === 0) {
1033
+ const emptyResponse = stripThinkBlocks(assistantText).length === 0;
833
1034
  // A model occasionally writes a path/link in prose and stops without formally
834
1035
  // presenting it. Give it one model-only corrective pass; the reminder is
835
1036
  // transient and therefore never pollutes persisted conversation history.
836
1037
  if (
837
- formalDeliveryRequired
1038
+ !emptyResponse && formalDeliveryRequired
838
1039
  && !resultPresented
839
1040
  && !deliveryCorrectionAttempted
840
1041
  && turn < maxTurns
841
1042
  ) {
842
1043
  deliveryCorrectionAttempted = true;
1044
+ yield {
1045
+ type: 'assistant_draft_discarded',
1046
+ reason: 'formal_delivery',
1047
+ textLength: assistantText.length,
1048
+ };
1049
+ continue;
1050
+ }
1051
+ const deliveryCheckpoint = emptyResponse
1052
+ ? { allow: true as const }
1053
+ : advisorCoordinator.beforeDelivery();
1054
+ if (!deliveryCheckpoint.allow && turn < maxTurns) {
1055
+ yield {
1056
+ type: 'assistant_draft_discarded',
1057
+ reason: 'advisor_checkpoint',
1058
+ textLength: assistantText.length,
1059
+ };
843
1060
  continue;
844
1061
  }
1062
+ if (emptyResponse) {
1063
+ // A no-tool response is already the model's terminal signal. Do not turn it
1064
+ // into another model round, and do not persist/broadcast an empty bubble.
1065
+ yield {
1066
+ type: 'assistant_draft_discarded',
1067
+ reason: 'empty_response',
1068
+ textLength: assistantText.length,
1069
+ };
1070
+ } else {
1071
+ history.push(assistantMsg);
1072
+ yield {
1073
+ type: 'assistant_message',
1074
+ message: assistantMsg as Extract<Message, { role: 'assistant' }>,
1075
+ };
1076
+ }
845
1077
  // R2:最终回答被 provider 因 max_tokens 截断(finish_reason='length')时,
846
1078
  // 不能静默当正常 end_turn 收尾 —— 否则 -p 宿主会拿到半截答案却以为成功。
847
1079
  // 走通用 warning(text/json 都落 stderr,不污染 json 那一行 stdout 契约)。
@@ -884,26 +1116,41 @@ export async function* runQuery(
884
1116
  : {}),
885
1117
  promptCacheUsage: lastPromptCacheUsage,
886
1118
  };
1119
+ // 资源账本挂到**每一条** turn_done 出口上。⚠️ 别只挂在 'completed' 那条:
1120
+ // 失败、交付不全、声明未核实的回合照样烧了 token,漏掉它们会让预算系统性少算,
1121
+ // 而少算的方向恰好是「让循环继续跑」—— 这正是本仓库记过账的
1122
+ // 「turn_done.completionStatus 被 print.ts 整条丢弃」同一类错误的反面。
1123
+ const usage: TurnResourceUsage = {
1124
+ ...turnUsage,
1125
+ trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls,
1126
+ };
887
1127
  if (formalDeliveryRequired && !resultPresented) {
888
1128
  const warning = '交付未完成:用户明确要求提供文件,但本轮没有成功提交可打开的文件结果。';
889
1129
  yield { type: 'warning', message: warning };
890
- yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning };
1130
+ yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning, usage };
891
1131
  } else if (resultPresented && presentedResultUnverified) {
892
1132
  const warning = '结果已提交,但至少一个文件尚未通过结构化验证。';
893
1133
  yield { type: 'warning', message: warning };
894
- yield { type: 'turn_done', completionStatus: 'delivery_unverified', warning };
1134
+ yield { type: 'turn_done', completionStatus: 'delivery_unverified', warning, usage };
895
1135
  } else if (!claimValidation.verified) {
896
1136
  yield {
897
1137
  type: 'turn_done',
898
1138
  completionStatus: 'claims_unverified',
899
1139
  warning: claimValidation.warnings.join('\n'),
1140
+ usage,
900
1141
  };
901
1142
  } else {
902
- yield { type: 'turn_done', completionStatus: 'completed' };
1143
+ yield { type: 'turn_done', completionStatus: 'completed', usage };
903
1144
  }
904
1145
  return;
905
1146
  }
906
1147
 
1148
+ history.push(assistantMsg);
1149
+ yield {
1150
+ type: 'assistant_message',
1151
+ message: assistantMsg as Extract<Message, { role: 'assistant' }>,
1152
+ };
1153
+
907
1154
  // 5. 执行工具调用
908
1155
  //
909
1156
  // R1:按 isConcurrencySafe 决定并发策略 —— 工具早就声明了这个字段,此前却没有
@@ -956,6 +1203,10 @@ export async function* runQuery(
956
1203
  },
957
1204
  };
958
1205
  }
1206
+ const checkpoint = advisorCoordinator.beforeTool(tc, advisorSemanticsFor(tc));
1207
+ if (!checkpoint.allow) {
1208
+ return { tc, result: checkpoint.deferred! };
1209
+ }
959
1210
  // 工具执行闸门:宿主注入后覆盖所有工具,包括只读工具。deny → 把 reason 当
960
1211
  // tool_result 回填,模型据此改道/如实报告,不抛异常不中断整轮。宿主异常一律 deny。
961
1212
  const isReadOnly = getToolByName(tc.function.name)?.isReadOnly === true;
@@ -992,19 +1243,35 @@ export async function* runQuery(
992
1243
  tc.function.name,
993
1244
  tc.function.arguments,
994
1245
  signal,
995
- { ...toolCallContext, cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd, toolCallId: tc.id, reportProgress },
1246
+ {
1247
+ ...toolCallContext,
1248
+ cwd:
1249
+ sessionState?.activeWorktree?.path ?? toolCallContext.cwd,
1250
+ toolCallId: tc.id,
1251
+ reportProgress,
1252
+ },
996
1253
  );
1254
+ if (tc.function.name === 'ComputerUse' && sessionState) {
1255
+ sessionState.computerAutomation = { lastUsedAt: Date.now() };
1256
+ }
1257
+ const downloadableArtifact =
1258
+ result.ok && result.metadata?.kind === 'browser_download'
1259
+ ? result.metadata.artifact
1260
+ : undefined;
1261
+ const formalManifest =
1262
+ result.ok && result.metadata?.kind === 'result_manifest'
1263
+ ? result.metadata.manifest
1264
+ : downloadableArtifact;
997
1265
  if (
998
- tc.function.name === 'PresentResult'
999
- && result.ok
1000
- && result.metadata?.kind === 'result_manifest'
1001
- && options.commitFormalDelivery
1266
+ formalManifest &&
1267
+ (tc.function.name === 'PresentResult' || downloadableArtifact) &&
1268
+ options.commitFormalDelivery
1002
1269
  ) {
1003
1270
  try {
1004
1271
  await options.commitFormalDelivery({
1005
1272
  toolCallId: tc.id,
1006
1273
  turnId: userMessage.id ?? options.userMessageId ?? 'turn:unknown',
1007
- manifest: result.metadata.manifest,
1274
+ manifest: formalManifest,
1008
1275
  });
1009
1276
  } catch (error) {
1010
1277
  result = {
@@ -1015,6 +1282,10 @@ export async function* runQuery(
1015
1282
  };
1016
1283
  }
1017
1284
  }
1285
+ if (tc.function.name === 'Advisor' || tc.function.name === 'AdvisorDecision') {
1286
+ // 串行批次内下一项可能就是 AdvisorDecision/受保护写入,必须立即推进协调器状态。
1287
+ advisorCoordinator.observeToolResult(tc, result);
1288
+ }
1018
1289
  options.onToolEnd?.(tc.id, false);
1019
1290
  return { tc, result };
1020
1291
  } catch (e) {
@@ -1133,32 +1404,75 @@ export async function* runQuery(
1133
1404
  }
1134
1405
 
1135
1406
  // 本回合工作量累计 → 上调顾问配额(只增不减;已用掉的次数不会因此回退)。
1136
- executedToolCalls += settled.length;
1407
+ executedToolCalls += settled.filter(({ result }) =>
1408
+ result.ok || result.disposition !== 'deferred').length;
1137
1409
  toolCallContext.advisorCalls!.max = advisorQuotaFor(executedToolCalls);
1138
1410
 
1139
1411
  // 严格按 toolCallsByIndex 顺序把 tool 消息回填 history(settled 已保持该顺序)。
1140
1412
  let anyAborted = false;
1141
1413
  for (const { tc, result } of settled) {
1142
- evidenceLedger.record(tc.function.name, tc.function.arguments, result);
1414
+ if (tc.function.name !== 'Advisor' && tc.function.name !== 'AdvisorDecision') {
1415
+ advisorCoordinator.observeToolResult(tc, result);
1416
+ }
1417
+ if (result.ok || result.disposition !== 'deferred') {
1418
+ evidenceLedger.record(tc.function.name, tc.function.arguments, result);
1419
+ }
1143
1420
  if (!result.ok && result.error === 'aborted') anyAborted = true;
1144
- if (tc.function.name === 'PresentResult' && result.ok && result.metadata?.kind === 'result_manifest') {
1421
+ const browserArtifact =
1422
+ result.ok && result.metadata?.kind === 'browser_download'
1423
+ ? result.metadata.artifact
1424
+ : undefined;
1425
+ if (
1426
+ result.ok &&
1427
+ ((tc.function.name === 'PresentResult' &&
1428
+ result.metadata?.kind === 'result_manifest') ||
1429
+ browserArtifact)
1430
+ ) {
1145
1431
  resultPresented = true;
1146
- presentedResultUnverified ||= result.metadata.manifest.files.some(
1147
- (file) => file.verification !== 'passed',
1148
- );
1432
+ // `not_run` only says that no optional, format-specific validator was
1433
+ // invoked. The PresentResult boundary has still checked the file and
1434
+ // persisted its fingerprint, so it must not downgrade a normal delivery.
1435
+ const manifest =
1436
+ result.metadata?.kind === 'result_manifest'
1437
+ ? result.metadata.manifest
1438
+ : browserArtifact;
1439
+ presentedResultUnverified ||=
1440
+ manifest?.files.some((file) => file.verification === 'failed') ??
1441
+ false;
1149
1442
  }
1150
1443
  const rawContent = result.ok ? result.content : `Error: ${result.error}`;
1151
- // 微压缩仅对成功结果走(失败结果通常很短,不需要压缩)
1444
+ const basePolicy = result.policy ?? getToolResultPolicy(tc.function.name);
1445
+ // 超大结果先归档再压缩:归档失败时保留原文,绝不因为磁盘问题破坏上下文。
1446
+ const archived = result.ok && sessionState
1447
+ ? await archiveToolResult(rawContent, sessionState.microCompact.archiveKey, tc.function.name)
1448
+ : { content: rawContent, originalChars: rawContent.length };
1449
+ // 归档退化必须发声:撞会话上限后每条超长结果都退回原文塞进上下文,
1450
+ // 也就是这个机制本来要解决的问题原样回来了 —— 静默的话没人会发现。
1451
+ if (archived.degraded && !archiveDegradedWarned) {
1452
+ archiveDegradedWarned = true;
1453
+ yield {
1454
+ type: 'warning',
1455
+ message: archived.degraded === 'session-limit'
1456
+ ? '工具结果归档已达本会话上限,后续超长结果将直接进入上下文(归档不再生效)。建议 /new 开新会话。'
1457
+ : '工具结果归档写盘失败,后续超长结果将直接进入上下文。请检查磁盘空间与权限。',
1458
+ };
1459
+ }
1460
+ const policy = archived.archiveRef
1461
+ ? { ...basePolicy, archiveRef: archived.archiveRef, originalChars: archived.originalChars }
1462
+ : basePolicy;
1463
+ // 微压缩仅对成功结果走;policy 会保护高价值结果,避免不可逆中段丢失。
1152
1464
  const content = result.ok
1153
- ? microCompact(tc.function.name, rawContent, sessionState?.microCompact)
1465
+ ? microCompact(tc.function.name, archived.content, sessionState?.microCompact, policy)
1154
1466
  : rawContent;
1155
1467
  history.push({
1156
1468
  role: 'tool',
1157
1469
  content,
1158
1470
  tool_call_id: tc.id,
1471
+ tool_result_policy: policy,
1159
1472
  ...(result.metadata ? { display_metadata: result.metadata } : {}),
1160
1473
  timestamp: Date.now(),
1161
1474
  id: crypto.randomUUID(),
1475
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
1162
1476
  });
1163
1477
  }
1164
1478
 
@@ -1169,6 +1483,10 @@ export async function* runQuery(
1169
1483
  ({ result }) =>
1170
1484
  !result.ok && result.error.includes('第三次调用已在执行前拒绝'),
1171
1485
  );
1486
+ const preemptedSignature = preemptedRepeatedFailure
1487
+ ? settled.find(({ result }) =>
1488
+ !result.ok && result.error.includes('第三次调用已在执行前拒绝'))?.tc
1489
+ : undefined;
1172
1490
  const stall = preemptedRepeatedFailure
1173
1491
  ? ({
1174
1492
  stalled: true as const,
@@ -1183,12 +1501,15 @@ export async function* runQuery(
1183
1501
  : { stalled: false as const };
1184
1502
  if (stall.stalled) {
1185
1503
  const message = stall.reason === 'repeated_failure'
1186
- ? '检测到同一工具以相同参数连续返回相同失败,已停止重复重试。'
1187
- : '检测到连续工具轮次没有产生新文本或新结果,已停止无进展循环。';
1504
+ ? '检测到同一工具以相同参数连续返回相同失败;请停止原样重试,改做只读诊断、切换路径或咨询 Advisor。'
1505
+ : '检测到连续工具轮次没有产生新文本或新结果;请切换路径或咨询 Advisor。';
1506
+ advisorCoordinator.notifyStalled(
1507
+ stall.signature
1508
+ ?? (preemptedSignature
1509
+ ? `${preemptedSignature.function.name}\0${preemptedSignature.function.arguments}`
1510
+ : stall.reason),
1511
+ );
1188
1512
  yield { type: 'warning', message };
1189
- yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('stalled') };
1190
- yield { type: 'error', error: message, code: 'stalled' };
1191
- return;
1192
1513
  }
1193
1514
 
1194
1515
  // 关键节点截图回填:把工具附带的实况截图作为一条带图 user 消息喂给(视觉)模型自我核验,
@@ -1201,6 +1522,7 @@ export async function* runQuery(
1201
1522
  // The history copy is intentionally downgraded after the turn. Emit a detached snapshot
1202
1523
  // so TUI/Desktop stores do not see their already-rendered image mutate underneath them.
1203
1524
  if (injectedShot) {
1525
+ visionRequiredThisTurn = true;
1204
1526
  yield { type: 'injected_message', message: structuredClone(injectedShot) };
1205
1527
  }
1206
1528
 
@@ -1311,7 +1633,7 @@ async function injectVisionFollowups(
1311
1633
  settled: { result: ToolResult }[],
1312
1634
  provider: Provider,
1313
1635
  ): Promise<Message | null> {
1314
- if (provider.supportsVision !== true) return null;
1636
+ if (provider.supportsVision === false) return null;
1315
1637
  const images = settled.flatMap((s) => s.result.visionImages ?? []);
1316
1638
  if (images.length === 0) return null;
1317
1639
  evictPriorVisionShots(history);
@@ -1441,6 +1763,8 @@ function friendlyToolDescription(toolName: string, rawArgsJson: string): string
1441
1763
  return `Fetching ...`;
1442
1764
  }
1443
1765
  }
1766
+ case 'BrowserBatch': return `Browser batch (${Array.isArray(args.steps) ? args.steps.length : 0} steps)`;
1767
+ case 'BrowserCDP': return `Browser CDP ${truncate(String(args.method ?? args.operation ?? ''), 45)}`;
1444
1768
  case 'WebSearch': return `Searching \`${truncate(args.query, 50)}\``;
1445
1769
  case 'Agent': {
1446
1770
  const t = typeof args.agent_type === 'string' ? args.agent_type : 'explore';