minimal-agent 0.12.0 → 0.14.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 (472) hide show
  1. package/README.md +6 -5
  2. package/package.json +62 -5
  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 +204 -185
  16. package/plugins/workflow-runner/src/index.ts +249 -224
  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 +404 -467
  28. package/plugins/workflow-runner/src/runner.ts +491 -535
  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 +172 -152
  54. package/plugins/workflow-runner/src/taskIdentity.ts +233 -200
  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 -105
  58. package/plugins/workflow-runner/src/types.ts +155 -520
  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 +542 -469
  68. package/plugins/workflow-runner/src/workflowState.ts +639 -589
  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/skill-creator/scripts/run_eval.py +3 -2
  92. package/skills/ui-design/SKILL.md +5 -2
  93. package/skills/ui-design/references/layout-composition.md +1 -1
  94. package/skills/workflow-creator/SKILL.md +344 -0
  95. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  96. package/skills/workflow-creator/references/api.md +216 -0
  97. package/skills/workflow-creator/references/migration.md +103 -0
  98. package/skills/workflow-creator/references/patterns.md +779 -0
  99. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  100. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  101. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  102. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  103. package/src/advisor/advisorCoordinator.js +503 -0
  104. package/src/advisor/advisorCoordinator.ts +657 -0
  105. package/src/cli/configMenu.tsx +0 -1
  106. package/src/cli/print.js +44 -6
  107. package/src/cli/print.ts +73 -4
  108. package/src/cli/streamJson.js +5 -6
  109. package/src/cli/streamJson.ts +8 -4
  110. package/src/cli/wizards/advisorWizard.js +4 -2
  111. package/src/cli/wizards/advisorWizard.tsx +8 -2
  112. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  113. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  114. package/src/collaboration/taskFlow.js +115 -0
  115. package/src/collaboration/taskFlow.ts +169 -0
  116. package/src/config/configDefaults.js +41 -1
  117. package/src/config/configDefaults.ts +43 -1
  118. package/src/config/configDiagnostics.ts +2 -2
  119. package/src/config/configFile.js +40 -9
  120. package/src/config/configFile.ts +60 -11
  121. package/src/config/configService.js +443 -74
  122. package/src/config/configService.ts +723 -142
  123. package/src/config/numericLimits.js +22 -0
  124. package/src/config/numericLimits.ts +24 -0
  125. package/src/config.js +1 -0
  126. package/src/config.ts +3 -2
  127. package/src/context/budgetPlan.js +1 -1
  128. package/src/context/budgetPlan.ts +1 -1
  129. package/src/context/compact.js +7 -2
  130. package/src/context/compact.ts +9 -2
  131. package/src/context/microCompactLite.js +212 -13
  132. package/src/context/microCompactLite.ts +261 -12
  133. package/src/context/sessionRegistry.js +8 -1
  134. package/src/context/sessionRegistry.ts +18 -5
  135. package/src/context/toolResultArchive.js +72 -0
  136. package/src/context/toolResultArchive.ts +95 -0
  137. package/src/goal/errors.js +8 -0
  138. package/src/goal/errors.ts +18 -0
  139. package/src/goal/goalRuntime.js +344 -0
  140. package/src/goal/goalRuntime.ts +466 -0
  141. package/src/goal/goalStore.js +200 -0
  142. package/src/goal/goalStore.ts +219 -0
  143. package/src/goal/index.js +4 -0
  144. package/src/goal/index.ts +4 -0
  145. package/src/goal/statusMapping.js +71 -0
  146. package/src/goal/statusMapping.ts +95 -0
  147. package/src/goal/types.js +7 -0
  148. package/src/goal/types.ts +179 -0
  149. package/src/goal/workflowController.js +306 -0
  150. package/src/goal/workflowController.ts +360 -0
  151. package/src/harness/evidenceLedger.js +2 -0
  152. package/src/harness/evidenceLedger.ts +1 -0
  153. package/src/harness/stallGuard.js +9 -3
  154. package/src/harness/stallGuard.ts +7 -3
  155. package/src/images/imageResolver.js +161 -0
  156. package/src/images/imageResolver.ts +189 -0
  157. package/src/infra/verification.js +56 -20
  158. package/src/infra/verification.ts +87 -20
  159. package/src/llm/client.js +79 -21
  160. package/src/llm/client.ts +182 -35
  161. package/src/llm/gatewayClient.js +201 -0
  162. package/src/llm/gatewayClient.ts +299 -0
  163. package/src/llm/messageProvenance.js +46 -0
  164. package/src/llm/messageProvenance.ts +58 -0
  165. package/src/llm/modelProvider.js +1 -0
  166. package/src/llm/modelProvider.ts +22 -0
  167. package/src/llm/providers/anthropicFormat.js +108 -1
  168. package/src/llm/providers/anthropicFormat.ts +115 -4
  169. package/src/llm/providers/openaiFormat.js +9 -0
  170. package/src/llm/providers/openaiFormat.ts +12 -3
  171. package/src/loop.js +319 -54
  172. package/src/loop.ts +373 -57
  173. package/src/main.js +4 -1
  174. package/src/main.tsx +4 -1
  175. package/src/managedExecution/index.js +2 -0
  176. package/src/managedExecution/index.ts +2 -0
  177. package/src/managedExecution/registry.js +15 -0
  178. package/src/managedExecution/registry.ts +20 -0
  179. package/src/managedExecution/types.js +1 -0
  180. package/src/managedExecution/types.ts +86 -0
  181. package/src/modelRef.js +9 -0
  182. package/src/modelRef.ts +31 -0
  183. package/src/multimodal.js +14 -28
  184. package/src/multimodal.ts +17 -33
  185. package/src/net/lanDiscovery.js +65 -0
  186. package/src/net/lanDiscovery.ts +82 -0
  187. package/src/plugin-sdk.js +28 -3
  188. package/src/plugin-sdk.ts +39 -6
  189. package/src/plugins/pluginApi.ts +16 -1
  190. package/src/plugins/pluginLoader.js +4 -0
  191. package/src/plugins/pluginLoader.ts +6 -0
  192. package/src/plugins/pluginRunner.js +152 -9
  193. package/src/plugins/pluginRunner.ts +186 -8
  194. package/src/plugins/slashFallback.js +113 -0
  195. package/src/plugins/slashFallback.ts +137 -0
  196. package/src/prompts/policyRegistry.js +15 -8
  197. package/src/prompts/policyRegistry.ts +18 -8
  198. package/src/prompts/promptAssembly.js +103 -12
  199. package/src/prompts/promptAssembly.ts +114 -12
  200. package/src/prompts/skillList.js +21 -3
  201. package/src/prompts/skillList.ts +27 -2
  202. package/src/prompts/stableSystemPrompt.js +18 -0
  203. package/src/prompts/stableSystemPrompt.ts +33 -0
  204. package/src/runtime/agentRuntimeContext.js +8 -2
  205. package/src/runtime/agentRuntimeContext.ts +17 -4
  206. package/src/session/runAgentTurn.js +78 -12
  207. package/src/session/runAgentTurn.ts +105 -23
  208. package/src/session/runTurn.js +10 -9
  209. package/src/session/runTurn.ts +17 -8
  210. package/src/session/sessionState.ts +2 -0
  211. package/src/team/auth.js +186 -0
  212. package/src/team/auth.ts +298 -0
  213. package/src/team/cli.js +105 -0
  214. package/src/team/cli.ts +122 -0
  215. package/src/team/onlineToolContract.js +64 -0
  216. package/src/team/onlineToolContract.ts +109 -0
  217. package/src/team/toolPolicy.js +36 -0
  218. package/src/team/toolPolicy.ts +75 -0
  219. package/src/tools/advisor/advisor.js +148 -37
  220. package/src/tools/advisor/advisor.ts +161 -38
  221. package/src/tools/advisor/advisorDecision.js +71 -0
  222. package/src/tools/advisor/advisorDecision.ts +85 -0
  223. package/src/tools/agent/agent.js +18 -5
  224. package/src/tools/agent/agent.ts +21 -7
  225. package/src/tools/agent/agentControl.js +246 -0
  226. package/src/tools/agent/agentControl.ts +284 -0
  227. package/src/tools/agent/agentTaskService.js +40 -1
  228. package/src/tools/agent/agentTaskService.ts +44 -1
  229. package/src/tools/agent/agentTypes.js +14 -2
  230. package/src/tools/agent/agentTypes.ts +15 -2
  231. package/src/tools/applypatch/applyPatch.js +362 -0
  232. package/src/tools/applypatch/applyPatch.ts +428 -0
  233. package/src/tools/bash/bash.js +70 -18
  234. package/src/tools/bash/bash.ts +75 -20
  235. package/src/tools/capabilities.js +10 -5
  236. package/src/tools/capabilities.ts +10 -5
  237. package/src/tools/computeruse/computerIntent.js +32 -0
  238. package/src/tools/computeruse/computerIntent.ts +46 -0
  239. package/src/tools/computeruse/telemetryLog.js +12 -0
  240. package/src/tools/computeruse/telemetryLog.ts +16 -0
  241. package/src/tools/computeruse/vision/telemetry.js +1 -0
  242. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  243. package/src/tools/effectiveToolSet.js +4 -0
  244. package/src/tools/effectiveToolSet.ts +4 -0
  245. package/src/tools/index.js +21 -2
  246. package/src/tools/index.ts +33 -2
  247. package/src/tools/powershell/powershell.js +18 -1
  248. package/src/tools/powershell/powershell.ts +18 -2
  249. package/src/tools/presentresult/presentresult.js +6 -15
  250. package/src/tools/presentresult/presentresult.ts +6 -15
  251. package/src/tools/read/read.js +69 -11
  252. package/src/tools/read/read.ts +70 -11
  253. package/src/tools/shared/fileUtils.js +1 -2
  254. package/src/tools/shared/fileUtils.ts +1 -2
  255. package/src/tools/shared/permanentDeleteGuard.js +34 -3
  256. package/src/tools/shared/permanentDeleteGuard.ts +43 -6
  257. package/src/tools/shared/toolOutputStore.js +23 -1
  258. package/src/tools/shared/toolOutputStore.ts +22 -1
  259. package/src/tools/skill/skill.js +9 -3
  260. package/src/tools/skill/skill.ts +8 -4
  261. package/src/tools/toolPromptGuides.js +117 -0
  262. package/src/tools/toolPromptGuides.ts +137 -0
  263. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  264. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  265. package/src/tools/webbrowser/browser.js +27 -11
  266. package/src/tools/webbrowser/browser.ts +68 -24
  267. package/src/tools/webbrowser/browserAutomation.js +329 -0
  268. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  269. package/src/tools/webbrowser/browserAxState.js +97 -0
  270. package/src/tools/webbrowser/browserAxState.ts +142 -0
  271. package/src/tools/webbrowser/browserBatch.js +1578 -0
  272. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  273. package/src/tools/webbrowser/browserCdp.js +208 -0
  274. package/src/tools/webbrowser/browserCdp.ts +255 -0
  275. package/src/tools/webbrowser/browserConditions.js +24 -0
  276. package/src/tools/webbrowser/browserConditions.ts +45 -0
  277. package/src/tools/webbrowser/browserContent.js +281 -0
  278. package/src/tools/webbrowser/browserContent.ts +438 -0
  279. package/src/tools/webbrowser/browserControl.js +131 -30
  280. package/src/tools/webbrowser/browserControl.ts +210 -50
  281. package/src/tools/webbrowser/browserDaemon.js +22 -14
  282. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  283. package/src/tools/webbrowser/browserFrames.js +47 -0
  284. package/src/tools/webbrowser/browserFrames.ts +54 -0
  285. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  286. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  287. package/src/tools/webbrowser/browserLocator.js +140 -0
  288. package/src/tools/webbrowser/browserLocator.ts +194 -0
  289. package/src/tools/webbrowser/browserOutcome.js +10 -17
  290. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  291. package/src/tools/webbrowser/browserRecovery.js +58 -0
  292. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  293. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  294. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  295. package/src/tools/webbrowser/browserService.js +28 -2
  296. package/src/tools/webbrowser/browserService.ts +49 -9
  297. package/src/tools/webbrowser/browserStream.js +132 -33
  298. package/src/tools/webbrowser/browserStream.ts +224 -50
  299. package/src/tools/webbrowser/browserTabs.js +48 -15
  300. package/src/tools/webbrowser/browserTabs.ts +178 -96
  301. package/src/tools/webbrowser/webbrowser.js +683 -144
  302. package/src/tools/webbrowser/webbrowser.ts +979 -236
  303. package/src/tools/webfetch/preapproved.js +0 -2
  304. package/src/tools/webfetch/preapproved.ts +0 -2
  305. package/src/tools/websearch/websearch.js +129 -8
  306. package/src/tools/websearch/websearch.ts +138 -6
  307. package/src/types.ts +450 -42
  308. package/src/ui/App.js +377 -58
  309. package/src/ui/App.tsx +576 -111
  310. package/src/ui/LiveArea.js +14 -8
  311. package/src/ui/LiveArea.tsx +14 -10
  312. package/src/ui/MessageList.js +63 -20
  313. package/src/ui/MessageList.tsx +101 -16
  314. package/src/ui/ModelPicker.js +7 -4
  315. package/src/ui/ModelPicker.tsx +55 -8
  316. package/src/ui/hooks/useChat.js +152 -163
  317. package/src/ui/hooks/useChat.ts +396 -334
  318. package/src/ui/loginCommand.js +56 -0
  319. package/src/ui/loginCommand.ts +63 -0
  320. package/src/ui/toolProgress.js +41 -0
  321. package/src/ui/toolProgress.ts +44 -0
  322. package/src/web/browserBridge.js +20 -2
  323. package/src/web/browserBridge.ts +20 -2
  324. package/src/web/protocol.ts +1897 -194
  325. package/workflows/adaptive-research/workflow.mjs +86 -0
  326. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  327. package/workflows/e2e-llm/workflow.mjs +52 -0
  328. package/workflows/e2e-smoke/workflow.mjs +59 -0
  329. package/workflows/e2e-vision-schema/README.md +48 -0
  330. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  331. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  332. package/workflows/file-census/README.md +14 -0
  333. package/workflows/file-census/scripts/.gitkeep +2 -0
  334. package/workflows/file-census/workflow.mjs +110 -0
  335. package/workflows/goal/README.md +166 -0
  336. package/workflows/goal/prompts/audit.md +57 -0
  337. package/workflows/goal/prompts/contract.md +56 -0
  338. package/workflows/goal/prompts/iterate.md +37 -0
  339. package/workflows/goal/workflow.mjs +501 -0
  340. package/workflows/illustrate/README.md +90 -0
  341. package/workflows/illustrate/assets/engines.json +20 -0
  342. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  343. package/workflows/illustrate/assets/extract-bing.js +33 -0
  344. package/workflows/illustrate/assets/extract-google.js +31 -0
  345. package/workflows/illustrate/workflow.mjs +301 -0
  346. package/workflows/plan/workflow.mjs +389 -0
  347. package/workflows/question-crops/README.md +35 -0
  348. package/workflows/question-crops/prompts/inventory.md +23 -0
  349. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  350. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  351. package/workflows/question-crops/workflow.mjs +172 -0
  352. package/workflows/ralph-loop/workflow.mjs +162 -0
  353. package/workflows/research/DESIGN.md +449 -0
  354. package/workflows/research/README.md +74 -0
  355. package/workflows/research/prompts/s0-goal.md +43 -0
  356. package/workflows/research/prompts/s1-early-exit.md +22 -0
  357. package/workflows/research/prompts/s1-map.md +58 -0
  358. package/workflows/research/prompts/s2-options.md +53 -0
  359. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  360. package/workflows/research/prompts/s4-verify.md +74 -0
  361. package/workflows/research/prompts/s5-synthesis.md +81 -0
  362. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  363. package/workflows/research/prompts/s6-package.md +32 -0
  364. package/workflows/research/scripts/build-report.mjs +98 -0
  365. package/workflows/research/workflow.mjs +288 -0
  366. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  367. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  368. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  369. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  370. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  371. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  372. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  373. package/workflows/wrong-question-docx/README.md +55 -0
  374. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  375. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  376. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  377. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  378. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  379. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  380. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  381. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  382. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  383. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  384. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  385. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  386. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  387. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  388. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  389. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  390. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  391. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  392. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  393. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  394. package/plugins/workflow-runner/src/expressions.js +0 -618
  395. package/plugins/workflow-runner/src/expressions.ts +0 -622
  396. package/plugins/workflow-runner/src/loader.js +0 -573
  397. package/plugins/workflow-runner/src/loader.ts +0 -721
  398. package/plugins/workflow-runner/src/multimodal.js +0 -38
  399. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  400. package/plugins/workflow-runner/src/recovery.js +0 -119
  401. package/plugins/workflow-runner/src/recovery.ts +0 -149
  402. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  403. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  404. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -41
  405. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -50
  406. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  407. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  408. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  409. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  410. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  411. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  412. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  413. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  414. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -208
  415. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -256
  416. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -58
  417. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -68
  418. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  419. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  420. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  421. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  422. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  423. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  424. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -147
  425. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  426. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  427. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -226
  428. package/plugins/workflow-runner/test/expressions.test.ts +0 -397
  429. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  430. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  431. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  432. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  433. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  434. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  435. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  436. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  437. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  438. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  439. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -87
  440. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  441. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  442. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -220
  443. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  444. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  445. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -379
  446. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  447. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -150
  448. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  449. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -338
  450. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -270
  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 -199
  468. package/workflows/schema.json +0 -176
  469. package/workflows/webppt.yaml +0 -496
  470. package/workflows/wechat-reading-shots.yaml +0 -315
  471. package/workflows/word.yaml +0 -720
  472. package/workflows/youtube-shorts.yaml +0 -202
package/src/loop.js 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
  * "工作过程显示"事件:
@@ -28,9 +28,10 @@
28
28
  */
29
29
  import crypto from 'node:crypto';
30
30
  import { autoCompactIfNeeded, getCompactThreshold } from './context/compact.js';
31
- import { microCompact, incrementTurn, expireOldEntries } from './context/microCompactLite.js';
31
+ import { microCompact, incrementTurn, expireOldEntries, reclaimOldToolResults, resetCacheEdits, getToolResultPolicy, } from './context/microCompactLite.js';
32
32
  import { isPromptTooLongError, reactiveCompactIfApplicable, } from './context/reactiveCompact.js';
33
33
  import { snipCompactIfNeeded } from './context/snipCompact.js';
34
+ import { archiveToolResult } from './context/toolResultArchive.js';
34
35
  import { countMessagesTokens, countTextTokens, estimateToolsTokens, } from './context/tokenCounter.js';
35
36
  import { buildContextBudgetPlan, fitToolsForEmergency } from './context/budgetPlan.js';
36
37
  import { resizeContextBundle } from './context/contextBundle.js';
@@ -40,20 +41,26 @@ import { ALL_TOOLS, executeTool as defaultExecuteTool, getToolByName, getToolGat
40
41
  import { configService } from './config/configService.js';
41
42
  import { capabilityForToolName } from './tools/capabilities.js';
42
43
  import { createEffectiveToolSet } from './tools/effectiveToolSet.js';
44
+ import { classifyComputerAutomationIntent } from './tools/computeruse/computerIntent.js';
45
+ import { recordComputerUseExposure } from './tools/computeruse/telemetryLog.js';
46
+ import { filterToolsForProvider } from './team/toolPolicy.js';
43
47
  import { hydrateSessionWorktree } from './tools/worktree/sessionWorktree.js';
44
48
  import { capabilitiesForTools, enabledPolicyIds, } from './prompts/policyRegistry.js';
45
49
  import { EvidenceLedger } from './harness/evidenceLedger.js';
46
50
  import { StallGuard } from './harness/stallGuard.js';
47
51
  import { validateFinalClaims } from './harness/claimValidation.js';
52
+ import { AdvisorCoordinator, isWorkspaceMutationTool, } from './advisor/advisorCoordinator.js';
53
+ import { classifyCommand } from './irreversible/rules.js';
48
54
  import { createAgentRuntimeContext, } from './runtime/agentRuntimeContext.js';
49
- const DELIVERY_ARTIFACT = '(?:文件|合同|文档|表格|报告|图片|图像|海报|幻灯片|PPT|PDF|代码|脚本|压缩包|网页|页面|HTML|DOCX|XLSX|CSV|ZIP)';
50
- const DELIVERY_ACTION = '(?:发|发送|交付|提交|给|传|制作|生成|创建|导出|输出|修改|更新|完成)';
51
- const ENGLISH_DELIVERY_ARTIFACT = '(?:file|document|report|spreadsheet|workbook|slides?|presentation|pdf|image|code|script|archive|zip|html|webpage|website)';
52
- const ENGLISH_DELIVERY_ACTION = '(?:send|deliver|submit|provide|create|generate|export|update|modify|finish|produce)';
55
+ import { stripThinkBlocks } from './utils/stripThink.js';
56
+ const DELIVERY_ARTIFACT = '(?:文件|合同|协议|方案|文档|表格|报告|图片|图像|海报|幻灯片|PPT|PDF|代码|脚本|压缩包|网页|页面|HTML|DOCX|XLSX|CSV|ZIP)';
57
+ const DELIVERY_ACTION = '(?:发送|(?<![开研])发|交付|提交|(?<!宣)传(?!统)|制作|生成|创建|导出|输出|修改|更新|完成|写|起草|做|设计|整理)';
58
+ const ENGLISH_DELIVERY_ARTIFACT = '(?:file|contract|document|report|spreadsheet|workbook|slides?|presentation|pdf|image|code|script|archive|zip|html|webpage|website)';
59
+ const ENGLISH_DELIVERY_ACTION = '(?:send|deliver|submit|provide|create|generate|export|update|modify|finish|produce|draft|write|make|build)';
53
60
  /** Whether this turn explicitly asks for a concrete artifact that should be formally delivered. */
54
61
  export function expectsFormalDelivery(input) {
55
62
  const compact = input.replace(/\s+/g, '');
56
- const chinese = new RegExp(`${DELIVERY_ACTION}(?:给)?我?.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}${DELIVERY_ACTION}(?:给)?我?`, 'i');
63
+ const chinese = new RegExp(`${DELIVERY_ACTION}(?:给)?我?.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}${DELIVERY_ACTION}(?:给)?我?|给我.{0,24}${DELIVERY_ARTIFACT}|${DELIVERY_ARTIFACT}.{0,24}给我`, 'i');
57
64
  const english = new RegExp(`\\b${ENGLISH_DELIVERY_ACTION}\\b.{0,80}\\b${ENGLISH_DELIVERY_ARTIFACT}\\b|\\b${ENGLISH_DELIVERY_ARTIFACT}\\b.{0,80}\\b${ENGLISH_DELIVERY_ACTION}\\b`, 'i');
58
65
  return chinese.test(compact) || english.test(input);
59
66
  }
@@ -92,7 +99,15 @@ function getToolsSchemaTokens(tools, signature) {
92
99
  return cached;
93
100
  }
94
101
  /**
95
- * 顾问咨询配额:基线 1 次,之后每累计 N 次工具调用解锁 1 次,封顶 3 次。
102
+ * 主工具循环的默认轮数上限 —— **唯一真值**。
103
+ *
104
+ * 2026-08 从 50 提到 200(4 倍)。它此前是 `options.maxTurns ?? 200` 里的一个裸字面量,
105
+ * 没有名字也就没有引用点,于是「跟着它一起动的东西」(顾问配额封顶)改不动 ——
106
+ * 见下面的 `ADVISOR_QUOTA_MAX`。
107
+ */
108
+ export const DEFAULT_MAX_TURNS = 200;
109
+ /**
110
+ * 顾问咨询配额:基线 1 次,之后每累计 N 次工具调用解锁 1 次,封顶见 ADVISOR_QUOTA_MAX。
96
111
  *
97
112
  * 曾经是硬编码 1 次/回合。那个数字隐含「一个用户回合 = 一个决策点」的假设,
98
113
  * 在短问答里成立,在「继续,完成后续所有工作」这类一句话跑几十步的长回合里
@@ -105,11 +120,48 @@ function getToolsSchemaTokens(tools, signature) {
105
120
  * 刷工具调用换配额的成本远高于收益。
106
121
  */
107
122
  const ADVISOR_QUOTA_BASE = 1;
108
- const ADVISOR_QUOTA_MAX = 3;
109
123
  const ADVISOR_QUOTA_UNLOCK_EVERY_TOOL_CALLS = 15;
124
+ /**
125
+ * 配额封顶**由 `DEFAULT_MAX_TURNS` 推导,不是独立的魔术数字**。
126
+ *
127
+ * 上面那段注释的论据是「一个长回合里真正值钱的咨询点至少三个」——那是按当时
128
+ * `maxTurns = 50` 算的。轮数上限后来提到 200(4 倍工作量塞进同一个回合),
129
+ * 「高风险批次做完」这类节点随之变多,封顶却没跟着动,于是配额重新变成瓶颈:
130
+ * 这正是它当年从「硬编码 1 次」被改掉的同一个毛病,只是换了个刻度。
131
+ *
132
+ * 用公式而不是再写一个字面量,两者才会一起动:
133
+ * 50 轮 → 3(复现历史值) · 200 轮 → 5 · 下限恒为 3
134
+ */
135
+ const ADVISOR_QUOTA_MAX = Math.max(3, Math.round(DEFAULT_MAX_TURNS / 40));
110
136
  export function advisorQuotaFor(executedToolCalls) {
111
137
  return Math.min(ADVISOR_QUOTA_MAX, ADVISOR_QUOTA_BASE + Math.floor(executedToolCalls / ADVISOR_QUOTA_UNLOCK_EVERY_TOOL_CALLS));
112
138
  }
139
+ function advisorSemanticsFor(call) {
140
+ let parsed = {};
141
+ try {
142
+ parsed = JSON.parse(call.function.arguments);
143
+ }
144
+ catch { /* zod layer reports malformed input */ }
145
+ const mutatesWorkspace = isWorkspaceMutationTool(call.function.name);
146
+ if (call.function.name === 'Bash' || call.function.name === 'PowerShell') {
147
+ const command = typeof parsed.command === 'string' ? parsed.command : '';
148
+ const risk = command ? classifyCommand(command) : null;
149
+ return { mutatesWorkspace, highRisk: risk?.riskLevel === 'critical', riskReason: risk?.why };
150
+ }
151
+ if (call.function.name === 'ComputerUse') {
152
+ const highRisk = parsed.intent === 'submit'
153
+ || (parsed.action === 'window' && parsed.windowOperation === 'close');
154
+ return { mutatesWorkspace, highRisk, ...(highRisk ? { riskReason: '原生界面操作不可逆或可能提交外部动作' } : {}) };
155
+ }
156
+ if (call.function.name === 'WebBrowser' || call.function.name === 'BrowserBatch') {
157
+ const highRisk = parsed.intent === 'submit'
158
+ || (call.function.name === 'BrowserBatch'
159
+ && Array.isArray(parsed.steps)
160
+ && parsed.steps.some((step) => typeof step === 'object' && step !== null && step.intent === 'submit'));
161
+ return { mutatesWorkspace, highRisk, ...(highRisk ? { riskReason: '浏览器动作将进行发布、购买、删除或最终提交' } : {}) };
162
+ }
163
+ return { mutatesWorkspace, highRisk: false };
164
+ }
113
165
  /**
114
166
  * 执行一次"用户输入 → 模型回答完成"的完整流程。
115
167
  *
@@ -120,8 +172,8 @@ export async function* runQuery(userInput, options) {
120
172
  // Tool screenshots are transient evidence for the tool continuation that produced them.
121
173
  // Never carry a prior turn's base64 image into a new user request.
122
174
  evictPriorVisionShots(history);
123
- const maxTurns = options.maxTurns ?? 50;
124
- const runtimeContext = options.runtimeContext ?? createAgentRuntimeContext({
175
+ const maxTurns = options.maxTurns ?? DEFAULT_MAX_TURNS;
176
+ const baseRuntimeContext = options.runtimeContext ?? createAgentRuntimeContext({
125
177
  surface: 'headless',
126
178
  profile: 'main',
127
179
  cwd: process.cwd(),
@@ -135,10 +187,22 @@ export async function* runQuery(userInput, options) {
135
187
  computerIntervention: options.computerIntervention,
136
188
  toolGate: options.toolGate,
137
189
  });
190
+ const computerIntent = baseRuntimeContext.computerIntent
191
+ ?? classifyComputerAutomationIntent(userInput, sessionState?.computerAutomation);
192
+ const runtimeContext = createAgentRuntimeContext({
193
+ ...baseRuntimeContext,
194
+ computerIntent,
195
+ });
196
+ if (runtimeContext.computer) {
197
+ void recordComputerUseExposure(computerIntent.needed, computerIntent.source);
198
+ }
199
+ if (!computerIntent.needed && sessionState)
200
+ delete sessionState.computerAutomation;
138
201
  // DI fallback:测试可传 stub,生产走真实模块单例
139
202
  const chatFn = options.chat ?? defaultChat;
140
203
  const executeToolFn = options.executeTool ?? defaultExecuteTool;
141
204
  const explicitFileDeliveryRequested = expectsFormalDelivery(userInput);
205
+ const currentUserMessageId = options.userMessageId ?? crypto.randomUUID();
142
206
  const transientFor = (tools) => {
143
207
  const opaque = options.transientContext?.trim() ?? '';
144
208
  const bundle = options.transientBundle;
@@ -185,6 +249,8 @@ export async function* runQuery(userInput, options) {
185
249
  currentUserInput: userInput,
186
250
  userMessageId: options.userMessageId,
187
251
  memoryAccess: runtimeContext.memoryAccess,
252
+ conversationMode: runtimeContext.conversationMode ?? 'private',
253
+ surface: runtimeContext.surface,
188
254
  workspaceId: runtimeContext.workspaceId,
189
255
  sessionId: runtimeContext.sessionId,
190
256
  cwd: runtimeContext.cwd,
@@ -195,9 +261,10 @@ export async function* runQuery(userInput, options) {
195
261
  // 实际下发给模型的工具集:allowedTools 未传时给全量;
196
262
  // 传入数组时严格按数组过滤,[] 表示本轮不下发任何工具。
197
263
  // 仅影响呈现给 LLM 的工具清单,不影响 executeTool 的内部调度能力。
198
- const hostTools = options.availableTools ?? ALL_TOOLS;
264
+ const hostTools = filterToolsForProvider(options.availableTools ?? ALL_TOOLS, provider);
199
265
  const availableTools = hostTools.filter((tool) => (tool.name !== 'Agent' || subagents.enabled)
200
- && (tool.name !== 'ComputerUse' || runtimeContext.computer !== undefined));
266
+ && (tool.name !== 'ComputerUse'
267
+ || (runtimeContext.computer !== undefined && runtimeContext.computerIntent?.needed === true)));
201
268
  const effectiveToolSet = createEffectiveToolSet({
202
269
  availableTools,
203
270
  runtime: runtimeContext,
@@ -210,6 +277,30 @@ export async function* runQuery(userInput, options) {
210
277
  && runtimeContext.surface !== 'workflow'
211
278
  && effectiveToolSet.names.has('PresentResult');
212
279
  let currentEffectiveToolSet = effectiveToolSet;
280
+ const advisorCoordinator = new AdvisorCoordinator({
281
+ history,
282
+ currentRequest: userInput,
283
+ currentRequestMessageId: currentUserMessageId,
284
+ advisorAvailable: effectiveToolSet.names.has('Advisor')
285
+ && effectiveToolSet.names.has('AdvisorDecision'),
286
+ });
287
+ toolCallContext.advisorRuntime = advisorCoordinator;
288
+ // 组装每轮请求的易变上下文。三个构建点(首轮 / autoCompact 后 / snip 后)共用,
289
+ // 避免 Advisor 相关提醒在复制粘贴中三处漂移。提醒均为 transient:只进当轮请求副本,
290
+ // 不进会话历史(与 DELIVERY_CORRECTION_REMINDER 同构,见 buildModelMessages)。
291
+ const buildRequestTransientContext = () => {
292
+ const advisorAvailable = currentEffectiveToolSet.names.has('Advisor')
293
+ && currentEffectiveToolSet.names.has('AdvisorDecision');
294
+ const parts = [
295
+ transientFor(activeTools),
296
+ deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
297
+ advisorAvailable ? advisorCoordinator.transientReminder() : '',
298
+ advisorAvailable
299
+ ? advisorCoordinator.usageLine(toolCallContext.advisorCalls.used, toolCallContext.advisorCalls.max)
300
+ : '',
301
+ ];
302
+ return parts.filter(Boolean).join('\n\n');
303
+ };
213
304
  let lastRequestTransientContext = [options.transientBundle?.text, options.transientContext]
214
305
  .filter(Boolean).join('\n\n');
215
306
  const evidenceLedger = new EvidenceLedger();
@@ -269,6 +360,16 @@ export async function* runQuery(userInput, options) {
269
360
  message: `未作为图片附加:${skipped.source}(${skipped.reason}),已按普通文本处理。`,
270
361
  };
271
362
  }
363
+ let visionRequiredThisTurn = Array.isArray(userContent)
364
+ && userContent.some((part) => part.type === 'image_url' || part.type === 'video_url');
365
+ if (visionRequiredThisTurn && provider.supportsVision === false) {
366
+ yield {
367
+ type: 'error',
368
+ error: '当前模型已配置为不支持视觉输入,图片未发送。请切换到支持图片的模型。',
369
+ code: 'llm_error',
370
+ };
371
+ return;
372
+ }
272
373
  // 1. 用户消息入栈(v2:填 timestamp,让 MessageList 的 turn 边界横线
273
374
  // 能拿到首条 user 的真实时间,否则 fallback Date.now() 会丢失"提交时刻"语义)
274
375
  const userMessage = {
@@ -277,12 +378,16 @@ export async function* runQuery(userInput, options) {
277
378
  ...(options.userAttachments?.length ? { attachments: options.userAttachments } : {}),
278
379
  ...(options.userBrowserReferences?.length ? { browserReferences: options.userBrowserReferences } : {}),
279
380
  timestamp: Date.now(),
280
- id: options.userMessageId ?? crypto.randomUUID(),
381
+ id: currentUserMessageId,
382
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
281
383
  };
282
- history.push(userMessage);
283
- // 立即通知 UI:用户消息已入栈,触发 bump 让 <Static> 马上把它 commit 进 scrollback,
284
- // 不必等整轮 assistant 落定(保证用户输入第一时间置顶,符合 T-A-O-R 展示顺序)。
285
- yield { type: 'user_message_committed', message: userMessage };
384
+ const alreadyCommitted = history.some((message) => message.role === 'user' && message.id === userMessage.id);
385
+ if (!alreadyCommitted) {
386
+ history.push(userMessage);
387
+ // 立即通知 UI:用户消息已入栈,触发 bump 让 <Static> 马上把它 commit 进 scrollback,
388
+ // 不必等整轮 assistant 落定(保证用户输入第一时间置顶,符合 T-A-O-R 展示顺序)。
389
+ yield { type: 'user_message_committed', message: userMessage };
390
+ }
286
391
  // 反应式压缩自救:本 runQuery 内最多触发 MAX_REACTIVE_PER_QUERY 次(防本轮死循环);
287
392
  // 跨 runQuery 的连续失败由 reactiveCompact.ts 的 circuit breaker(consecutiveFailures)兜底。
288
393
  let reactiveCount = 0;
@@ -290,6 +395,13 @@ export async function* runQuery(userInput, options) {
290
395
  // 上一轮 LLM 返回的真实 prompt token 数(done.promptTokens;provider 开 include_usage 才有)。
291
396
  // 作为 autoCompact 的权威判据,校正本地估算的系统性低估;压缩后清空(历史已变,旧值失真)。
292
397
  let lastPromptTokens;
398
+ // ── 回合级资源账本 ────────────────────────────────────────────────
399
+ // ⚠️ 与 lastPromptTokens 生命周期**故意不同**:那个是「当前 history 有多大」的快照,
400
+ // 压缩后必须清零;这个是「本回合总共花了多少」的累计,压缩不影响已经花掉的钱。
401
+ // ⚠️ 它只记账,**不参与任何控制流** —— loop 绝不因预算中断进行中的工具循环。
402
+ const turnUsage = { inputTokens: 0, outputTokens: 0, totalCalls: 0, reportedCalls: 0 };
403
+ // 归档退化只报一次:每条超长结果都报会把上下文刷满,反而盖掉别的信号。
404
+ let archiveDegradedWarned = false;
293
405
  // 提示词缓存观测位(纯展示,不参与任何控制流)。与 lastPromptTokens 同生命周期:
294
406
  // 压缩后一并清空,避免把压缩前的命中数据挂到压缩后的新历史上误导排障。
295
407
  let lastCachedPromptTokens;
@@ -302,6 +414,15 @@ export async function* runQuery(userInput, options) {
302
414
  turn++;
303
415
  incrementTurn(sessionState?.microCompact);
304
416
  expireOldEntries(sessionState?.microCompact);
417
+ // Path C 批量回收旧工具结果:可重放的观察类工具结果(Read/Grep/Bash…)
418
+ // 超过阈值时只保留热尾部。必须在 buildContextBudgetPlan 之前跑——预算估算和
419
+ // autoCompact 判据直接看到瘦身后的历史,避免无谓的全量压缩。
420
+ // cacheEdits 模式(Anthropic 格式 + provider.cacheEdits):不改 history,删除目标
421
+ // 记入状态、由请求层组装 cache_edits 发送--服务端 KV 原地删除,前缀缓存完整保留。
422
+ // 本地估算的虚高部分(服务端已删内容)由 autoCompactIfNeeded 的 serverDeletedTokens
423
+ // 校正,判据不会因此过早触发压缩。
424
+ const cacheEditsMode = provider.apiFormat === 'anthropic' && provider.cacheEdits === true;
425
+ reclaimOldToolResults(history, sessionState?.microCompact, cacheEditsMode ? 'cacheEdits' : 'inline');
305
426
  if (signal?.aborted) {
306
427
  pushManualInterruptMarker(history);
307
428
  if (!workStarted) {
@@ -324,10 +445,7 @@ export async function* runQuery(userInput, options) {
324
445
  }
325
446
  // 2. 自动压缩(判据 = max(上一轮真实 usage, 历史估算 + 工具 schema),
326
447
  // 阈值 provider 无关、按 contextWindow × compactRatio 比例触发)
327
- let requestTransientContext = [
328
- transientFor(activeTools),
329
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
330
- ].filter(Boolean).join('\n\n');
448
+ let requestTransientContext = buildRequestTransientContext();
331
449
  lastRequestTransientContext = requestTransientContext;
332
450
  const preCompactBudget = buildContextBudgetPlan({
333
451
  provider,
@@ -341,6 +459,11 @@ export async function* runQuery(userInput, options) {
341
459
  actualPromptTokens: lastPromptTokens,
342
460
  toolsTokens: getToolsSchemaTokens(activeTools, effectiveToolSet.signature),
343
461
  transientTokens: countTextTokens(requestTransientContext),
462
+ // cacheEdits 判据校正:本地 history 未瘦身,扣掉服务端已删部分的估算,
463
+ // 防止虚高估算劫持 Math.max 导致压缩过早触发(压缩会重建整个前缀缓存)。
464
+ ...(cacheEditsMode && sessionState?.microCompact
465
+ ? { serverDeletedTokens: sessionState.microCompact.cacheEdits.activeDeletedTokens }
466
+ : {}),
344
467
  signal,
345
468
  promptCache: options.promptCache,
346
469
  chat: chatFn,
@@ -350,6 +473,10 @@ export async function* runQuery(userInput, options) {
350
473
  yield { type: 'stage_change', stage: 'compacting' };
351
474
  history.length = 0;
352
475
  history.push(...compact.messages);
476
+ // cacheEdits 状态随压缩作废:pinned 的翻译后下标指向已不存在的消息序列,
477
+ // 服务端缓存前缀也已失效——不重置会导致删除指令插错位置甚至重复删除。
478
+ if (sessionState?.microCompact)
479
+ resetCacheEdits(sessionState.microCompact);
353
480
  lastPromptTokens = undefined;
354
481
  lastCachedPromptTokens = undefined;
355
482
  lastCacheMissTokens = undefined;
@@ -380,6 +507,9 @@ export async function* runQuery(userInput, options) {
380
507
  if (snipped.messagesRemoved > 0) {
381
508
  history.length = 0;
382
509
  history.push(...snipped.messages);
510
+ // 同 compact:snip 砍头后 pinned 下标失位,cacheEdits 必须重置
511
+ if (sessionState?.microCompact)
512
+ resetCacheEdits(sessionState.microCompact);
383
513
  lastPromptTokens = undefined;
384
514
  lastCachedPromptTokens = undefined;
385
515
  lastCacheMissTokens = undefined;
@@ -415,10 +545,7 @@ export async function* runQuery(userInput, options) {
415
545
  }
416
546
  // Re-open transient capacity after compaction. If the compacted tail is still
417
547
  // too large, trim old history before sacrificing any model-visible ability.
418
- requestTransientContext = [
419
- transientFor(activeTools),
420
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
421
- ].filter(Boolean).join('\n\n');
548
+ requestTransientContext = buildRequestTransientContext();
422
549
  lastRequestTransientContext = requestTransientContext;
423
550
  let postCompactBudget = buildContextBudgetPlan({
424
551
  provider,
@@ -443,10 +570,7 @@ export async function* runQuery(userInput, options) {
443
570
  messagesRemoved: snipped.messagesRemoved,
444
571
  tokensFreed: snipped.tokensFreed,
445
572
  };
446
- requestTransientContext = [
447
- transientFor(activeTools),
448
- deliveryCorrectionAttempted && !resultPresented ? DELIVERY_CORRECTION_REMINDER : '',
449
- ].filter(Boolean).join('\n\n');
573
+ requestTransientContext = buildRequestTransientContext();
450
574
  lastRequestTransientContext = requestTransientContext;
451
575
  postCompactBudget = buildContextBudgetPlan({
452
576
  provider,
@@ -470,6 +594,8 @@ export async function* runQuery(userInput, options) {
470
594
  runtime: runtimeContext,
471
595
  requiredNames: [...effectiveToolSet.requiredNames],
472
596
  });
597
+ advisorCoordinator.setAvailable(currentEffectiveToolSet.names.has('Advisor')
598
+ && currentEffectiveToolSet.names.has('AdvisorDecision'));
473
599
  if (emergencyFit.removedNames.length > 0) {
474
600
  yield {
475
601
  type: 'warning',
@@ -509,12 +635,22 @@ export async function* runQuery(userInput, options) {
509
635
  workStarted = true;
510
636
  try {
511
637
  const modelMessages = buildModelMessages(history, requestTransientContext, userMessage.id);
638
+ advisorCoordinator.markTransientDelivered();
512
639
  for await (const ev of chatFn({
513
640
  provider,
514
641
  messages: modelMessages,
515
642
  tools: currentEffectiveToolSet.tools,
516
643
  signal,
644
+ conversationMode: runtimeContext.conversationMode ?? 'private',
517
645
  promptCache: options.promptCache,
646
+ visionPolicy: visionRequiredThisTurn ? 'required' : 'optional-history',
647
+ // cacheEdits 回收状态(同一会话级对象跨轮复用;compact 后由 resetCacheEdits 清空)
648
+ ...(cacheEditsMode && sessionState?.microCompact
649
+ ? { cacheEdits: sessionState.microCompact.cacheEdits }
650
+ : {}),
651
+ ...(options.responseFormat
652
+ ? { responseFormat: options.responseFormat }
653
+ : {}),
518
654
  })) {
519
655
  if (ev.type === 'text_delta') {
520
656
  if (!stageStreamingYielded) {
@@ -541,6 +677,10 @@ export async function* runQuery(userInput, options) {
541
677
  reasoningDetails.push(...ev.items);
542
678
  }
543
679
  }
680
+ else if (ev.type === 'usage') {
681
+ if (typeof ev.promptTokens === 'number')
682
+ lastPromptTokens = ev.promptTokens;
683
+ }
544
684
  else if (ev.type === 'done') {
545
685
  // R2:记下流的终止原因。'length'(max_tokens 截断)会在本轮收尾时触发 warning。
546
686
  lastFinishReason = ev.stopReason;
@@ -556,6 +696,17 @@ export async function* runQuery(userInput, options) {
556
696
  lastCacheWriteTokens = ev.cacheWriteTokens;
557
697
  if (ev.promptCacheUsage)
558
698
  lastPromptCacheUsage = ev.promptCacheUsage;
699
+ // 回合级记账(与上面的 lastPromptTokens 不同:那个压缩后要清零,这个不清)。
700
+ // ⚠️ 只有 provider 真报了才计入 reportedCalls —— 「没人告诉我」不能被当成
701
+ // 「真的是 0」,否则预算闸永远不触发。覆盖率由 trustworthy 如实暴露。
702
+ turnUsage.totalCalls += 1;
703
+ const reportedIn = typeof ev.promptTokens === 'number' ? ev.promptTokens : undefined;
704
+ const reportedOut = typeof ev.outputTokens === 'number' ? ev.outputTokens : undefined;
705
+ if (reportedIn !== undefined || reportedOut !== undefined) {
706
+ turnUsage.reportedCalls += 1;
707
+ turnUsage.inputTokens += reportedIn ?? 0;
708
+ turnUsage.outputTokens += reportedOut ?? 0;
709
+ }
559
710
  }
560
711
  }
561
712
  }
@@ -644,21 +795,53 @@ export async function* runQuery(userInput, options) {
644
795
  ...(reasoningDetails.length > 0 ? { reasoning_details: reasoningDetails } : {}),
645
796
  timestamp: Date.now(),
646
797
  id: crypto.randomUUID(),
798
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
647
799
  };
648
- history.push(assistantMsg);
649
- yield { type: 'assistant_message', message: assistantMsg };
650
800
  // 4. 没有工具调用 → 整轮交互结束
651
801
  if (toolCallsByIndex.length === 0) {
802
+ const emptyResponse = stripThinkBlocks(assistantText).length === 0;
652
803
  // A model occasionally writes a path/link in prose and stops without formally
653
804
  // presenting it. Give it one model-only corrective pass; the reminder is
654
805
  // transient and therefore never pollutes persisted conversation history.
655
- if (formalDeliveryRequired
806
+ if (!emptyResponse && formalDeliveryRequired
656
807
  && !resultPresented
657
808
  && !deliveryCorrectionAttempted
658
809
  && turn < maxTurns) {
659
810
  deliveryCorrectionAttempted = true;
811
+ yield {
812
+ type: 'assistant_draft_discarded',
813
+ reason: 'formal_delivery',
814
+ textLength: assistantText.length,
815
+ };
660
816
  continue;
661
817
  }
818
+ const deliveryCheckpoint = emptyResponse
819
+ ? { allow: true }
820
+ : advisorCoordinator.beforeDelivery();
821
+ if (!deliveryCheckpoint.allow && turn < maxTurns) {
822
+ yield {
823
+ type: 'assistant_draft_discarded',
824
+ reason: 'advisor_checkpoint',
825
+ textLength: assistantText.length,
826
+ };
827
+ continue;
828
+ }
829
+ if (emptyResponse) {
830
+ // A no-tool response is already the model's terminal signal. Do not turn it
831
+ // into another model round, and do not persist/broadcast an empty bubble.
832
+ yield {
833
+ type: 'assistant_draft_discarded',
834
+ reason: 'empty_response',
835
+ textLength: assistantText.length,
836
+ };
837
+ }
838
+ else {
839
+ history.push(assistantMsg);
840
+ yield {
841
+ type: 'assistant_message',
842
+ message: assistantMsg,
843
+ };
844
+ }
662
845
  // R2:最终回答被 provider 因 max_tokens 截断(finish_reason='length')时,
663
846
  // 不能静默当正常 end_turn 收尾 —— 否则 -p 宿主会拿到半截答案却以为成功。
664
847
  // 走通用 warning(text/json 都落 stderr,不污染 json 那一行 stdout 契约)。
@@ -698,28 +881,42 @@ export async function* runQuery(userInput, options) {
698
881
  : {}),
699
882
  promptCacheUsage: lastPromptCacheUsage,
700
883
  };
884
+ // 资源账本挂到**每一条** turn_done 出口上。⚠️ 别只挂在 'completed' 那条:
885
+ // 失败、交付不全、声明未核实的回合照样烧了 token,漏掉它们会让预算系统性少算,
886
+ // 而少算的方向恰好是「让循环继续跑」—— 这正是本仓库记过账的
887
+ // 「turn_done.completionStatus 被 print.ts 整条丢弃」同一类错误的反面。
888
+ const usage = {
889
+ ...turnUsage,
890
+ trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls,
891
+ };
701
892
  if (formalDeliveryRequired && !resultPresented) {
702
893
  const warning = '交付未完成:用户明确要求提供文件,但本轮没有成功提交可打开的文件结果。';
703
894
  yield { type: 'warning', message: warning };
704
- yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning };
895
+ yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning, usage };
705
896
  }
706
897
  else if (resultPresented && presentedResultUnverified) {
707
898
  const warning = '结果已提交,但至少一个文件尚未通过结构化验证。';
708
899
  yield { type: 'warning', message: warning };
709
- yield { type: 'turn_done', completionStatus: 'delivery_unverified', warning };
900
+ yield { type: 'turn_done', completionStatus: 'delivery_unverified', warning, usage };
710
901
  }
711
902
  else if (!claimValidation.verified) {
712
903
  yield {
713
904
  type: 'turn_done',
714
905
  completionStatus: 'claims_unverified',
715
906
  warning: claimValidation.warnings.join('\n'),
907
+ usage,
716
908
  };
717
909
  }
718
910
  else {
719
- yield { type: 'turn_done', completionStatus: 'completed' };
911
+ yield { type: 'turn_done', completionStatus: 'completed', usage };
720
912
  }
721
913
  return;
722
914
  }
915
+ history.push(assistantMsg);
916
+ yield {
917
+ type: 'assistant_message',
918
+ message: assistantMsg,
919
+ };
723
920
  // 5. 执行工具调用
724
921
  //
725
922
  // R1:按 isConcurrencySafe 决定并发策略 —— 工具早就声明了这个字段,此前却没有
@@ -762,6 +959,10 @@ export async function* runQuery(userInput, options) {
762
959
  },
763
960
  };
764
961
  }
962
+ const checkpoint = advisorCoordinator.beforeTool(tc, advisorSemanticsFor(tc));
963
+ if (!checkpoint.allow) {
964
+ return { tc, result: checkpoint.deferred };
965
+ }
765
966
  // 工具执行闸门:宿主注入后覆盖所有工具,包括只读工具。deny → 把 reason 当
766
967
  // tool_result 回填,模型据此改道/如实报告,不抛异常不中断整轮。宿主异常一律 deny。
767
968
  const isReadOnly = getToolByName(tc.function.name)?.isReadOnly === true;
@@ -788,16 +989,29 @@ export async function* runQuery(userInput, options) {
788
989
  options.onToolStart?.(tc.function.name, tc.id);
789
990
  // 浅拷贝填入本次调用的 toolCallId:进度按调用实例路由(并行同名工具不串扰)。
790
991
  // advisorCalls/agentCalls 是共享对象引用,计数仍全轮共享,不受拷贝影响。
791
- let result = await executeToolFn(tc.function.name, tc.function.arguments, signal, { ...toolCallContext, cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd, toolCallId: tc.id, reportProgress });
792
- if (tc.function.name === 'PresentResult'
793
- && result.ok
794
- && result.metadata?.kind === 'result_manifest'
795
- && options.commitFormalDelivery) {
992
+ let result = await executeToolFn(tc.function.name, tc.function.arguments, signal, {
993
+ ...toolCallContext,
994
+ cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd,
995
+ toolCallId: tc.id,
996
+ reportProgress,
997
+ });
998
+ if (tc.function.name === 'ComputerUse' && sessionState) {
999
+ sessionState.computerAutomation = { lastUsedAt: Date.now() };
1000
+ }
1001
+ const downloadableArtifact = result.ok && result.metadata?.kind === 'browser_download'
1002
+ ? result.metadata.artifact
1003
+ : undefined;
1004
+ const formalManifest = result.ok && result.metadata?.kind === 'result_manifest'
1005
+ ? result.metadata.manifest
1006
+ : downloadableArtifact;
1007
+ if (formalManifest &&
1008
+ (tc.function.name === 'PresentResult' || downloadableArtifact) &&
1009
+ options.commitFormalDelivery) {
796
1010
  try {
797
1011
  await options.commitFormalDelivery({
798
1012
  toolCallId: tc.id,
799
1013
  turnId: userMessage.id ?? options.userMessageId ?? 'turn:unknown',
800
- manifest: result.metadata.manifest,
1014
+ manifest: formalManifest,
801
1015
  });
802
1016
  }
803
1017
  catch (error) {
@@ -809,6 +1023,10 @@ export async function* runQuery(userInput, options) {
809
1023
  };
810
1024
  }
811
1025
  }
1026
+ if (tc.function.name === 'Advisor' || tc.function.name === 'AdvisorDecision') {
1027
+ // 串行批次内下一项可能就是 AdvisorDecision/受保护写入,必须立即推进协调器状态。
1028
+ advisorCoordinator.observeToolResult(tc, result);
1029
+ }
812
1030
  options.onToolEnd?.(tc.id, false);
813
1031
  return { tc, result };
814
1032
  }
@@ -920,36 +1138,79 @@ export async function* runQuery(userInput, options) {
920
1138
  }
921
1139
  }
922
1140
  // 本回合工作量累计 → 上调顾问配额(只增不减;已用掉的次数不会因此回退)。
923
- executedToolCalls += settled.length;
1141
+ executedToolCalls += settled.filter(({ result }) => result.ok || result.disposition !== 'deferred').length;
924
1142
  toolCallContext.advisorCalls.max = advisorQuotaFor(executedToolCalls);
925
1143
  // 严格按 toolCallsByIndex 顺序把 tool 消息回填 history(settled 已保持该顺序)。
926
1144
  let anyAborted = false;
927
1145
  for (const { tc, result } of settled) {
928
- evidenceLedger.record(tc.function.name, tc.function.arguments, result);
1146
+ if (tc.function.name !== 'Advisor' && tc.function.name !== 'AdvisorDecision') {
1147
+ advisorCoordinator.observeToolResult(tc, result);
1148
+ }
1149
+ if (result.ok || result.disposition !== 'deferred') {
1150
+ evidenceLedger.record(tc.function.name, tc.function.arguments, result);
1151
+ }
929
1152
  if (!result.ok && result.error === 'aborted')
930
1153
  anyAborted = true;
931
- if (tc.function.name === 'PresentResult' && result.ok && result.metadata?.kind === 'result_manifest') {
1154
+ const browserArtifact = result.ok && result.metadata?.kind === 'browser_download'
1155
+ ? result.metadata.artifact
1156
+ : undefined;
1157
+ if (result.ok &&
1158
+ ((tc.function.name === 'PresentResult' &&
1159
+ result.metadata?.kind === 'result_manifest') ||
1160
+ browserArtifact)) {
932
1161
  resultPresented = true;
933
- presentedResultUnverified ||= result.metadata.manifest.files.some((file) => file.verification !== 'passed');
1162
+ // `not_run` only says that no optional, format-specific validator was
1163
+ // invoked. The PresentResult boundary has still checked the file and
1164
+ // persisted its fingerprint, so it must not downgrade a normal delivery.
1165
+ const manifest = result.metadata?.kind === 'result_manifest'
1166
+ ? result.metadata.manifest
1167
+ : browserArtifact;
1168
+ presentedResultUnverified ||=
1169
+ manifest?.files.some((file) => file.verification === 'failed') ??
1170
+ false;
934
1171
  }
935
1172
  const rawContent = result.ok ? result.content : `Error: ${result.error}`;
936
- // 微压缩仅对成功结果走(失败结果通常很短,不需要压缩)
1173
+ const basePolicy = result.policy ?? getToolResultPolicy(tc.function.name);
1174
+ // 超大结果先归档再压缩:归档失败时保留原文,绝不因为磁盘问题破坏上下文。
1175
+ const archived = result.ok && sessionState
1176
+ ? await archiveToolResult(rawContent, sessionState.microCompact.archiveKey, tc.function.name)
1177
+ : { content: rawContent, originalChars: rawContent.length };
1178
+ // 归档退化必须发声:撞会话上限后每条超长结果都退回原文塞进上下文,
1179
+ // 也就是这个机制本来要解决的问题原样回来了 —— 静默的话没人会发现。
1180
+ if (archived.degraded && !archiveDegradedWarned) {
1181
+ archiveDegradedWarned = true;
1182
+ yield {
1183
+ type: 'warning',
1184
+ message: archived.degraded === 'session-limit'
1185
+ ? '工具结果归档已达本会话上限,后续超长结果将直接进入上下文(归档不再生效)。建议 /new 开新会话。'
1186
+ : '工具结果归档写盘失败,后续超长结果将直接进入上下文。请检查磁盘空间与权限。',
1187
+ };
1188
+ }
1189
+ const policy = archived.archiveRef
1190
+ ? { ...basePolicy, archiveRef: archived.archiveRef, originalChars: archived.originalChars }
1191
+ : basePolicy;
1192
+ // 微压缩仅对成功结果走;policy 会保护高价值结果,避免不可逆中段丢失。
937
1193
  const content = result.ok
938
- ? microCompact(tc.function.name, rawContent, sessionState?.microCompact)
1194
+ ? microCompact(tc.function.name, archived.content, sessionState?.microCompact, policy)
939
1195
  : rawContent;
940
1196
  history.push({
941
1197
  role: 'tool',
942
1198
  content,
943
1199
  tool_call_id: tc.id,
1200
+ tool_result_policy: policy,
944
1201
  ...(result.metadata ? { display_metadata: result.metadata } : {}),
945
1202
  timestamp: Date.now(),
946
1203
  id: crypto.randomUUID(),
1204
+ ...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
947
1205
  });
948
1206
  }
949
1207
  // If the third identical observation is also the configured final turn, retain the
950
1208
  // established max_turns termination contract. The stall guard is valuable only when
951
1209
  // it can stop a failing path before the caller's explicit turn budget does.
952
1210
  const preemptedRepeatedFailure = settled.some(({ result }) => !result.ok && result.error.includes('第三次调用已在执行前拒绝'));
1211
+ const preemptedSignature = preemptedRepeatedFailure
1212
+ ? settled.find(({ result }) => !result.ok && result.error.includes('第三次调用已在执行前拒绝'))?.tc
1213
+ : undefined;
953
1214
  const stall = preemptedRepeatedFailure
954
1215
  ? ({
955
1216
  stalled: true,
@@ -960,12 +1221,13 @@ export async function* runQuery(userInput, options) {
960
1221
  : { stalled: false };
961
1222
  if (stall.stalled) {
962
1223
  const message = stall.reason === 'repeated_failure'
963
- ? '检测到同一工具以相同参数连续返回相同失败,已停止重复重试。'
964
- : '检测到连续工具轮次没有产生新文本或新结果,已停止无进展循环。';
1224
+ ? '检测到同一工具以相同参数连续返回相同失败;请停止原样重试,改做只读诊断、切换路径或咨询 Advisor。'
1225
+ : '检测到连续工具轮次没有产生新文本或新结果;请切换路径或咨询 Advisor。';
1226
+ advisorCoordinator.notifyStalled(stall.signature
1227
+ ?? (preemptedSignature
1228
+ ? `${preemptedSignature.function.name}\0${preemptedSignature.function.arguments}`
1229
+ : stall.reason));
965
1230
  yield { type: 'warning', message };
966
- yield { type: 'harness_diagnostics', diagnostics: harnessDiagnostics('stalled') };
967
- yield { type: 'error', error: message, code: 'stalled' };
968
- return;
969
1231
  }
970
1232
  // 关键节点截图回填:把工具附带的实况截图作为一条带图 user 消息喂给(视觉)模型自我核验,
971
1233
  // 并 yield injected_message 让宿主在对话里即时展示这张实况截图。
@@ -978,6 +1240,7 @@ export async function* runQuery(userInput, options) {
978
1240
  // The history copy is intentionally downgraded after the turn. Emit a detached snapshot
979
1241
  // so TUI/Desktop stores do not see their already-rendered image mutate underneath them.
980
1242
  if (injectedShot) {
1243
+ visionRequiredThisTurn = true;
981
1244
  yield { type: 'injected_message', message: structuredClone(injectedShot) };
982
1245
  }
983
1246
  if (anyAborted || signal?.aborted) {
@@ -1070,7 +1333,7 @@ function isVisionShotText(text) {
1070
1333
  * 把失败伪装成成功——见 types.ts 里 ToolResult 那段注释。
1071
1334
  */
1072
1335
  async function injectVisionFollowups(history, settled, provider) {
1073
- if (provider.supportsVision !== true)
1336
+ if (provider.supportsVision === false)
1074
1337
  return null;
1075
1338
  const images = settled.flatMap((s) => s.result.visionImages ?? []);
1076
1339
  if (images.length === 0)
@@ -1196,6 +1459,8 @@ function friendlyToolDescription(toolName, rawArgsJson) {
1196
1459
  return `Fetching ...`;
1197
1460
  }
1198
1461
  }
1462
+ case 'BrowserBatch': return `Browser batch (${Array.isArray(args.steps) ? args.steps.length : 0} steps)`;
1463
+ case 'BrowserCDP': return `Browser CDP ${truncate(String(args.method ?? args.operation ?? ''), 45)}`;
1199
1464
  case 'WebSearch': return `Searching \`${truncate(args.query, 50)}\``;
1200
1465
  case 'Agent': {
1201
1466
  const t = typeof args.agent_type === 'string' ? args.agent_type : 'explore';