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
@@ -15,50 +15,79 @@
15
15
  * ============================================================
16
16
  */
17
17
 
18
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
19
18
  import { stat } from 'node:fs/promises';
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
20
20
 
21
+ import {
22
+ type AskAnswer,
23
+ type AskQuestionSpec,
24
+ type AskSource,
25
+ createAskCoordinator,
26
+ setAskHandler,
27
+ } from '../../askBridge.js';
21
28
  import { getWorkingDir, setWorkingDir } from '../../bootstrap/workingDir.js';
29
+ import type { ComputerUseCliMode } from '../../cli/computerUse.js';
30
+ import {
31
+ createCliComputerRun,
32
+ createComputerUseHostGate,
33
+ } from '../../cli/computerUse.js';
22
34
  import { forceCompact } from '../../context/compact.js';
35
+ import { acquireContextLock } from '../../context/contextLock.js';
23
36
  import {
24
37
  clearContext,
25
38
  loadContext,
26
39
  saveContext,
27
40
  } from '../../context/persistContext.js';
28
41
  import { resetReactiveCompactState } from '../../context/reactiveCompact.js';
29
- import { acquireContextLock } from '../../context/contextLock.js';
30
- import { workspaceIndexService } from '../../context/workspaceIndex.js';
42
+ import {
43
+ resolveSessionFile,
44
+ sessionFileFor,
45
+ } from '../../context/sessionPath.js';
31
46
  import {
32
47
  getActiveSession,
48
+ listSessionSummaries,
33
49
  setActiveSession,
34
50
  updateSessionMetadata,
35
51
  } from '../../context/sessionRegistry.js';
52
+ import { workspaceIndexService } from '../../context/workspaceIndex.js';
36
53
  import {
37
- resolveSessionFile,
38
- sessionFileFor,
39
- } from '../../context/sessionPath.js';
40
- import { runQuery } from '../../loop.js';
41
- import { buildBaseSystemPrompt, buildTransientPromptBundle } from '../../prompts/system.js';
54
+ HumanInterventionCoordinator,
55
+ type PendingHumanIntervention,
56
+ } from '../../humanIntervention.js';
42
57
  import { createPromptCacheContext } from '../../llm/promptCache.js';
58
+ import { runQuery } from '../../loop.js';
59
+ import {
60
+ type TurnJournal,
61
+ startTurnJournal,
62
+ } from '../../memory/turnJournal.js';
63
+ import { createInputQueue } from '../../messageQueue.js';
64
+ import type { PluginEvent } from '../../plugins/pluginApi.js';
65
+ import { runWithPlugins } from '../../plugins/pluginRunner.js';
66
+ import {
67
+ buildBaseSystemPrompt,
68
+ buildTransientPromptBundle,
69
+ } from '../../prompts/system.js';
70
+ import {
71
+ refreshStableSystemPrompt,
72
+ upsertStableSystemMessage,
73
+ } from '../../prompts/stableSystemPrompt.js';
43
74
  import {
44
75
  createAgentRuntimeContext,
45
76
  filterToolsForRuntime,
46
77
  memoryContextFromRuntime,
47
78
  } from '../../runtime/agentRuntimeContext.js';
48
- import { startTurnJournal, type TurnJournal } from '../../memory/turnJournal.js';
49
- import { ALL_TOOLS, getToolByName } from '../../tools/index.js';
50
- import { createInputQueue } from '../../messageQueue.js';
51
79
  import {
52
- createAskCoordinator,
53
- setAskHandler,
54
- type AskAnswer,
55
- type AskQuestionSpec,
56
- type AskSource,
57
- } from '../../askBridge.js';
58
- import { createAskSlot } from './askSlot.js';
59
- import { figures } from '../figures.js';
80
+ getOrCreateSessionState,
81
+ sessionStateKey,
82
+ } from '../../session/sessionState.js';
83
+ import { filterToolsForCategories } from '../../team/toolPolicy.js';
84
+ import { runAdvisor } from '../../tools/advisor/advisor.js';
85
+ import { classifyComputerAutomationIntent } from '../../tools/computeruse/computerIntent.js';
86
+ import { computerUseService } from '../../tools/computeruse/computerUseService.js';
87
+ import { createComputerInterventionAdapter } from '../../tools/computeruse/intervention.js';
88
+ import type { ComputerUseRunContext } from '../../tools/computeruse/protocol.js';
89
+ import { ALL_TOOLS, getToolByName } from '../../tools/index.js';
60
90
  import { clearFileState } from '../../tools/shared/fileState.js';
61
- import { getOrCreateSessionState, sessionStateKey } from '../../session/sessionState.js';
62
91
  import { hydrateSessionWorktree } from '../../tools/worktree/sessionWorktree.js';
63
92
  import type {
64
93
  LoopEvent,
@@ -68,23 +97,8 @@ import type {
68
97
  ToolResult,
69
98
  ToolStatusState,
70
99
  } from '../../types.js';
71
- import { runAdvisor } from '../../tools/advisor/advisor.js';
72
- import type { PluginEvent } from '../../plugins/pluginApi.js';
73
- import { runWithPlugins } from '../../plugins/pluginRunner.js';
74
100
  import { dlog } from '../../utils/debugLog.js';
75
- import type { ComputerUseCliMode } from '../../cli/computerUse.js';
76
- import { createCliComputerRun, createComputerUseHostGate } from '../../cli/computerUse.js';
77
- import {
78
- computerUseService,
79
- } from '../../tools/computeruse/computerUseService.js';
80
- import {
81
- HumanInterventionCoordinator,
82
- type PendingHumanIntervention,
83
- } from '../../humanIntervention.js';
84
- import { createComputerInterventionAdapter } from '../../tools/computeruse/intervention.js';
85
- import type {
86
- ComputerUseRunContext,
87
- } from '../../tools/computeruse/protocol.js';
101
+ import { createAskSlot } from './askSlot.js';
88
102
 
89
103
  export interface PluginProgressState {
90
104
  pluginId: string;
@@ -95,7 +109,10 @@ export interface PluginProgressState {
95
109
  }
96
110
 
97
111
  /** loop.ts 每轮 yield 的 context_usage 事件(去掉 type 字段)。 */
98
- export type ContextUsageInfo = Omit<Extract<LoopEvent, { type: 'context_usage' }>, 'type'>;
112
+ export type ContextUsageInfo = Omit<
113
+ Extract<LoopEvent, { type: 'context_usage' }>,
114
+ 'type'
115
+ >;
99
116
 
100
117
  export interface UseChatResult {
101
118
  history: Message[];
@@ -285,7 +302,10 @@ function useThrottledFlush({
285
302
  const append = useCallback(
286
303
  (delta: string) => {
287
304
  bufferRef.current += delta;
288
- if (typeof maxChars === 'number' && bufferRef.current.length >= maxChars) {
305
+ if (
306
+ typeof maxChars === 'number' &&
307
+ bufferRef.current.length >= maxChars
308
+ ) {
289
309
  flush();
290
310
  return;
291
311
  }
@@ -322,21 +342,11 @@ export function useChat(args: {
322
342
  const [transientError, setTransientError] = useState<string | null>(null);
323
343
  const [interrupted, setInterrupted] = useState(false);
324
344
 
325
- /**
326
- * 暂态错误状态机的跨事件可变上下文(用 ref,不需要为它单独重渲染)。
327
- * - activeStepId:当前正在执行的 workflow step id(workflow_step_start 记下,
328
- * 该 step 的 workflow_step_end 清除);null = 不在任何 step 内。
329
- * - pendingLlmError:该 step 执行中捕获到的最近一次 llm_error 文案(暂存,
330
- * 等 step 结束才决定丢弃 or 提升为永久红字)。
331
- * handleEvent 通过 setters.workflowCtx 同步读写这个对象(pure 逻辑,测试可注入 stub)。
332
- */
333
- const workflowCtxRef = useRef<{
334
- activeStepId: string | null;
335
- pendingLlmError: string | null;
336
- }>({ activeStepId: null, pendingLlmError: null });
337
- // 工作流步骤 id → 人话进度名(title)映射:start 记入、end/skipped 回查,
338
- // 让 scrollback 三种进度行(▶/✓/⊘)都显示同一人话名而非 id。跨事件持 ref。
339
- const stepTitlesRef = useRef<Map<string, string>>(new Map());
345
+ // 注:这里曾有 workflowCtxRef(activeStepId / pendingLlmError)与 stepTitlesRef,
346
+ // 服务于 yaml DSL 的 workflow_step_* 事件。那些事件随引擎删除后没有任何发送方,
347
+ // 于是 activeStepId 恒为 null、两条依赖它的暂态错误分支永远走不到 —— 一并删掉。
348
+ // 代码编排引擎把 llm_error / max_turns 在 api.ts 内部消化(不外泄给顶层),
349
+ // 不需要宿主再做这层「暂态还是永久」的裁决。
340
350
  const [pluginProgress, setPluginProgress] =
341
351
  useState<PluginProgressState | null>(null);
342
352
 
@@ -350,22 +360,31 @@ export function useChat(args: {
350
360
  * 比 useTokenUsage 的本地估算更准;未收到过(如刚启动、纯只读问答走了只读快速
351
361
  * 路径)时该值仍是 null,StatusLine 降级回 useTokenUsage 估算。
352
362
  */
353
- const [contextUsage, setContextUsage] = useState<ContextUsageInfo | null>(null);
363
+ const [contextUsage, setContextUsage] = useState<ContextUsageInfo | null>(
364
+ null,
365
+ );
354
366
  const [computerUseMode, setComputerUseMode] =
355
367
  useState<ComputerUseCliMode>('disabled');
356
368
  const promptToolSignatureRef = useRef('');
357
369
  const activeComputerRunRef = useRef<ComputerUseRunContext | null>(null);
358
- const [computerInterventions] = useState(() => new HumanInterventionCoordinator());
370
+ const [computerInterventions] = useState(
371
+ () => new HumanInterventionCoordinator(),
372
+ );
359
373
  const [pendingComputerIntervention, setPendingComputerIntervention] =
360
374
  useState<PendingHumanIntervention | null>(null);
361
- useEffect(() => computerInterventions.subscribe((event) => {
362
- if (event.type === 'started') {
363
- setPendingComputerIntervention(event.intervention);
364
- } else {
365
- setPendingComputerIntervention((current) =>
366
- current?.id === event.resolution.intervention.id ? null : current);
367
- }
368
- }), [computerInterventions]);
375
+ useEffect(
376
+ () =>
377
+ computerInterventions.subscribe((event) => {
378
+ if (event.type === 'started') {
379
+ setPendingComputerIntervention(event.intervention);
380
+ } else {
381
+ setPendingComputerIntervention((current) =>
382
+ current?.id === event.resolution.intervention.id ? null : current,
383
+ );
384
+ }
385
+ }),
386
+ [computerInterventions],
387
+ );
369
388
  /**
370
389
  * 工具并行执行:以 callId 为 key 保存所有运行中工具。
371
390
  * 用 Map 是为了 O(1) 增删;对外 memo 物化成稳定数组(Map 迭代顺序 = 插入顺序,
@@ -382,8 +401,12 @@ export function useChat(args: {
382
401
 
383
402
  const [inputQueue] = useState(() => createInputQueue());
384
403
  const [queuedCount, setQueuedCount] = useState(0);
385
- const [queuedItems, setQueuedItems] = useState<readonly import('../../messageQueue.js').QueuedInput[]>([]);
386
- const runningToolsRef = useRef<Map<string, { toolName: string; isCancelable: boolean }>>(new Map());
404
+ const [queuedItems, setQueuedItems] = useState<
405
+ readonly import('../../messageQueue.js').QueuedInput[]
406
+ >([]);
407
+ const runningToolsRef = useRef<
408
+ Map<string, { toolName: string; isCancelable: boolean }>
409
+ >(new Map());
387
410
 
388
411
  useEffect(() => {
389
412
  return inputQueue.subscribe(() => {
@@ -398,12 +421,14 @@ export function useChat(args: {
398
421
  // askSlot.ts(纯函数可单测);这里只桥接 React 状态。
399
422
  const [pendingAsk, setPendingAsk] = useState<AskQuestionSpec[] | null>(null);
400
423
  // 提问来源:子任务提问时 kind:'subagent' + description → AskQuestion 显示徽标。
401
- const [pendingAskSource, setPendingAskSource] = useState<AskSource | null>(null);
424
+ const [pendingAskSource, setPendingAskSource] = useState<AskSource | null>(
425
+ null,
426
+ );
402
427
  const [pendingAskQueueCount, setPendingAskQueueCount] = useState(0);
403
428
  const [askSlot] = useState(() =>
404
429
  createAskSlot((questions, source) => {
405
430
  setPendingAsk(questions);
406
- setPendingAskSource(questions ? source ?? null : null);
431
+ setPendingAskSource(questions ? (source ?? null) : null);
407
432
  }),
408
433
  );
409
434
 
@@ -456,7 +481,6 @@ export function useChat(args: {
456
481
  setRunningToolsMap(new Map());
457
482
  // 暂态错误是"过程级"提示,turn 结束 / 切换时一并清掉,不残留到下一轮
458
483
  setTransientError(null);
459
- workflowCtxRef.current = { activeStepId: null, pendingLlmError: null };
460
484
  }, []);
461
485
 
462
486
  /** turn 结束 / 中断 / 切换时一并取消所有节流 buffer 的 pending timer。 */
@@ -492,9 +516,11 @@ export function useChat(args: {
492
516
  // 归属校验保证已答完提问的 abort 监听器不会清掉后来提问的槽,否则后者 promise
493
517
  // 永不落定 → 整轮死挂,见 askSlot.ts 头注)。
494
518
  useEffect(() => {
495
- setAskHandler(createAskCoordinator(askSlot.handler, (status) => {
496
- setPendingAskQueueCount(status.queuedCount ?? 0);
497
- }));
519
+ setAskHandler(
520
+ createAskCoordinator(askSlot.handler, (status) => {
521
+ setPendingAskQueueCount(status.queuedCount ?? 0);
522
+ }),
523
+ );
498
524
  return () => setAskHandler(null);
499
525
  }, [askSlot]);
500
526
 
@@ -549,37 +575,46 @@ export function useChat(args: {
549
575
  const runManualAdvisor = useCallback(
550
576
  async (note?: string): Promise<ToolResult> => {
551
577
  if (isLoading) {
552
- return { ok: false, error: '当前有任务在执行;等它结束或按 ESC 中断后再咨询顾问。' };
578
+ return {
579
+ ok: false,
580
+ error: '当前有任务在执行;等它结束或按 ESC 中断后再咨询顾问。',
581
+ };
553
582
  }
554
583
  const callId = 'manual-advisor';
555
584
  const ac = new AbortController();
556
585
  abortRef.current = ac;
557
586
  setIsLoading(true);
558
587
  setProgressStage('tool_executing');
559
- setRunningToolsMap(new Map([[callId, {
560
- toolName: 'Advisor',
561
- argsPreview: note ?? '',
562
- status: 'running' as const,
563
- argsFriendly: '正在咨询顾问模型',
564
- }]]));
588
+ setRunningToolsMap(
589
+ new Map([
590
+ [
591
+ callId,
592
+ {
593
+ toolName: 'Advisor',
594
+ argsPreview: note ?? '',
595
+ status: 'running' as const,
596
+ argsFriendly: '正在咨询顾问模型',
597
+ },
598
+ ],
599
+ ]),
600
+ );
565
601
  try {
566
- return await runAdvisor(
567
- note ? { note } : {},
568
- ac.signal,
569
- {
570
- history: historyRef.current,
571
- advisorCalls: { used: 0, max: 1 },
572
- advisorInvocation: 'manual',
573
- reportProgress: (message) => {
574
- // advisor.ts 侧已有 200ms 节流,这里直接落状态即可。
575
- setRunningToolsMap((prev) => {
576
- const current = prev.get(callId);
577
- if (!current) return prev;
578
- return new Map(prev).set(callId, { ...current, progress: message });
602
+ return await runAdvisor(note ? { note } : {}, ac.signal, {
603
+ history: historyRef.current,
604
+ advisorCalls: { used: 0, max: 1 },
605
+ advisorInvocation: 'manual',
606
+ reportProgress: (message) => {
607
+ // advisor.ts 侧已有 200ms 节流,这里直接落状态即可。
608
+ setRunningToolsMap((prev) => {
609
+ const current = prev.get(callId);
610
+ if (!current) return prev;
611
+ return new Map(prev).set(callId, {
612
+ ...current,
613
+ progress: message,
579
614
  });
580
- },
615
+ });
581
616
  },
582
- );
617
+ });
583
618
  } finally {
584
619
  setIsLoading(false);
585
620
  setProgressStage('idle');
@@ -590,7 +625,10 @@ export function useChat(args: {
590
625
  // (见下方 consumeQueued)—— 手动咨询不走那条路径,排队的话就会静静躺着,
591
626
  // 用户看到「排队 1 条」却永远不执行,直到下次提交才错序跑掉。
592
627
  const queued = inputQueue.dequeue();
593
- if (queued) queueMicrotask(() => { void submitRef.current?.(queued.value); });
628
+ if (queued)
629
+ queueMicrotask(() => {
630
+ void submitRef.current?.(queued.value);
631
+ });
594
632
  }
595
633
  },
596
634
  [isLoading, inputQueue],
@@ -604,7 +642,8 @@ export function useChat(args: {
604
642
  if (isLoading) {
605
643
  inputQueue.enqueue(trimmed, 'next');
606
644
  const running = Array.from(runningToolsRef.current.values());
607
- const allCancelable = running.length > 0 && running.every(t => t.isCancelable);
645
+ const allCancelable =
646
+ running.length > 0 && running.every((t) => t.isCancelable);
608
647
  if (allCancelable) {
609
648
  abortRef.current?.abort('interrupt');
610
649
  }
@@ -623,8 +662,6 @@ export function useChat(args: {
623
662
  setProgressStage('idle');
624
663
  setReasoningPreview('');
625
664
  setRunningToolsMap(new Map());
626
- // 重置暂态错误状态机(防上一轮工作流残留)
627
- workflowCtxRef.current = { activeStepId: null, pendingLlmError: null };
628
665
 
629
666
  const ac = new AbortController();
630
667
  abortRef.current = ac;
@@ -641,20 +678,31 @@ export function useChat(args: {
641
678
  }, 10_000);
642
679
 
643
680
  try {
644
- await updateSessionMetadata(getWorkingDir(), activeSessionIdRef.current, {
645
- lastMessagePreview: trimmed,
646
- }).catch(() => {});
647
- const activeFile = sessionFileFor(getWorkingDir(), activeSessionIdRef.current);
681
+ await updateSessionMetadata(
682
+ getWorkingDir(),
683
+ activeSessionIdRef.current,
684
+ {
685
+ lastMessagePreview: trimmed,
686
+ },
687
+ ).catch(() => {});
688
+ const activeFile = sessionFileFor(
689
+ getWorkingDir(),
690
+ activeSessionIdRef.current,
691
+ );
648
692
  releaseContextLock = await acquireContextLock(activeFile, ac.signal);
649
693
  // Gateway/Cron may have written this cwd since the TUI rendered its last frame.
650
694
  // Reload after acquiring the cross-process lock so this turn extends the newest history.
651
695
  const latest = await loadContext(activeFile);
652
696
  if (latest) {
653
- const ephemeral = historyRef.current.filter((message) => message.role === 'system' && message._ephemeral);
697
+ const ephemeral = historyRef.current.filter(
698
+ (message) => message.role === 'system' && message._ephemeral,
699
+ );
654
700
  historyRef.current.length = 0;
655
701
  historyRef.current.push(...latest, ...ephemeral);
656
702
  }
657
- const workspace = await workspaceIndexService.findByPath(getWorkingDir()).catch(() => null);
703
+ const workspace = await workspaceIndexService
704
+ .findByPath(getWorkingDir())
705
+ .catch(() => null);
658
706
  const computerRun =
659
707
  computerUseMode !== 'disabled'
660
708
  ? createCliComputerRun({
@@ -684,17 +732,25 @@ export function useChat(args: {
684
732
  },
685
733
  })
686
734
  : undefined;
687
- const toolGate = computerRun ? createComputerUseHostGate(computerRun) : undefined;
688
- const turnSessionState = getOrCreateSessionState(sessionStateKey({
689
- surface: 'tui', workspaceId: workspace?.id,
690
- cwd: workspace?.id ? undefined : getWorkingDir(),
691
- sessionId: activeSessionIdRef.current,
692
- }));
735
+ const toolGate = computerRun
736
+ ? createComputerUseHostGate(computerRun)
737
+ : undefined;
738
+ const turnSessionState = getOrCreateSessionState(
739
+ sessionStateKey({
740
+ surface: 'tui',
741
+ workspaceId: workspace?.id,
742
+ cwd: workspace?.id ? undefined : getWorkingDir(),
743
+ sessionId: activeSessionIdRef.current,
744
+ }),
745
+ );
693
746
  await hydrateSessionWorktree({
694
- sessionState: turnSessionState, workspaceId: workspace?.id,
695
- sessionId: activeSessionIdRef.current, cwd: getWorkingDir(),
747
+ sessionState: turnSessionState,
748
+ workspaceId: workspace?.id,
749
+ sessionId: activeSessionIdRef.current,
750
+ cwd: getWorkingDir(),
696
751
  });
697
- const effectiveCwd = turnSessionState.activeWorktree?.path ?? getWorkingDir();
752
+ const effectiveCwd =
753
+ turnSessionState.activeWorktree?.path ?? getWorkingDir();
698
754
  const runtimeContext = createAgentRuntimeContext({
699
755
  surface: 'tui',
700
756
  profile: 'main',
@@ -705,18 +761,54 @@ export function useChat(args: {
705
761
  memoryAccess: 'read-write',
706
762
  interactiveAsk: true,
707
763
  computer: computerRun,
764
+ computerIntent: classifyComputerAutomationIntent(
765
+ trimmed,
766
+ turnSessionState.computerAutomation,
767
+ ),
708
768
  computerIntervention,
709
769
  toolGate,
710
770
  });
711
- const turnTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
771
+ const runtimeTools = filterToolsForRuntime(ALL_TOOLS, runtimeContext);
772
+ const sessionSummary = (
773
+ await listSessionSummaries(getWorkingDir())
774
+ ).find((session) => session.id === activeSessionIdRef.current);
775
+ const currentProvider = providerRef.current;
776
+ const effectiveGatewayCategories =
777
+ currentProvider.source === 'gateway'
778
+ ? [
779
+ ...new Set(
780
+ (
781
+ sessionSummary?.onlineToolCategories ??
782
+ currentProvider.gatewayDefaultToolCategories ??
783
+ currentProvider.gatewayMaxToolCategories ??
784
+ []
785
+ ).filter(
786
+ (category) =>
787
+ currentProvider.gatewayMaxToolCategories?.includes(
788
+ category,
789
+ ) ?? true,
790
+ ),
791
+ ),
792
+ ]
793
+ : undefined;
794
+ const turnProvider =
795
+ currentProvider.source === 'gateway'
796
+ ? {
797
+ ...currentProvider,
798
+ gatewayToolCategories: effectiveGatewayCategories,
799
+ }
800
+ : currentProvider;
801
+ const turnTools =
802
+ currentProvider.source === 'gateway'
803
+ ? filterToolsForCategories(runtimeTools, effectiveGatewayCategories)
804
+ : runtimeTools;
712
805
  const toolSignature = `${effectiveCwd}\0${turnTools.map((tool) => tool.name).join(',')}`;
713
806
  if (promptToolSignatureRef.current !== toolSignature) {
714
- const stablePrompt = await buildBaseSystemPrompt(effectiveCwd, turnTools);
715
- const rest = historyRef.current[0]?.role === 'system'
716
- ? historyRef.current.slice(1)
717
- : [...historyRef.current];
718
- historyRef.current.length = 0;
719
- historyRef.current.push({ role: 'system', content: stablePrompt }, ...rest);
807
+ await refreshStableSystemPrompt(
808
+ historyRef.current,
809
+ effectiveCwd,
810
+ turnTools,
811
+ );
720
812
  promptToolSignatureRef.current = toolSignature;
721
813
  }
722
814
  // 每回合只刷新易变层;稳定层仅在实际能力目录变化时重建。
@@ -742,11 +834,12 @@ export function useChat(args: {
742
834
  userInput: trimmed,
743
835
  }).catch(() => null);
744
836
  for await (const ev of runWithPlugins(trimmed, {
745
- provider: providerRef.current,
837
+ provider: turnProvider,
746
838
  history: historyRef.current,
747
839
  signal: ac.signal,
748
840
  runtimeContext,
749
841
  allowedTools: turnTools.map((tool) => tool.name),
842
+ availableTools: turnTools,
750
843
  sessionState: turnSessionState,
751
844
  parseInlineImages: true,
752
845
  ...(turnTransient ? { transientBundle: turnTransient } : {}),
@@ -781,9 +874,6 @@ export function useChat(args: {
781
874
  setContextUsage,
782
875
  // 暂态错误(工作流步内 llm_error / 重试进度)→ LiveArea 黄字
783
876
  setTransientError,
784
- workflowCtx: workflowCtxRef.current,
785
- // 步骤 id→人话名映射:scrollback 进度行三态(▶/✓/⊘)统一显示 title
786
- stepTitles: stepTitlesRef.current,
787
877
  // 节流相关 —— buffer 累积 + 100ms timer flush
788
878
  appendStreamingDelta: streamingFlush.append,
789
879
  flushStreamingBuffer: streamingFlush.flush,
@@ -816,11 +906,16 @@ export function useChat(args: {
816
906
  activeComputerRunRef.current = null;
817
907
  runningToolsRef.current.clear();
818
908
  // 记账失败不影响落盘与解锁(它是附加能力,不是功能)。
819
- await turnJournal?.finish(historyRef.current, turnJournalStart).catch(() => undefined);
909
+ await turnJournal
910
+ ?.finish(historyRef.current, turnJournalStart)
911
+ .catch(() => undefined);
820
912
  turnJournal = null;
821
913
  // v2 multi-session:持久化到当前 active session 文件。
822
914
  try {
823
- await saveContext(historyRef.current, sessionFileFor(getWorkingDir(), activeSessionIdRef.current));
915
+ await saveContext(
916
+ historyRef.current,
917
+ sessionFileFor(getWorkingDir(), activeSessionIdRef.current),
918
+ );
824
919
  } finally {
825
920
  await releaseContextLock?.();
826
921
  }
@@ -838,7 +933,6 @@ export function useChat(args: {
838
933
  [
839
934
  bump,
840
935
  isLoading,
841
- sessionFile,
842
936
  streamingFlush,
843
937
  cancelAllThrottles,
844
938
  reasoningFlush,
@@ -867,9 +961,12 @@ export function useChat(args: {
867
961
  const stopComputerUse = useCallback(async (): Promise<string> => {
868
962
  setComputerUseMode('disabled');
869
963
  const context = activeComputerRunRef.current;
870
- if (!context) return '已撤销后续回合授权;当前没有正在运行的 Computer Use。';
964
+ if (!context)
965
+ return '已撤销后续回合授权;当前没有正在运行的 Computer Use。';
871
966
  computerInterventions.abort(context);
872
- const response = await computerUseService.request(context, { action: 'stop' });
967
+ const response = await computerUseService.request(context, {
968
+ action: 'stop',
969
+ });
873
970
  return response.ok
874
971
  ? '已停止当前回合的 Computer Use,并撤销后续回合授权。'
875
972
  : `已撤销后续回合授权;原生停止失败:${response.error?.message ?? '未知错误'}`;
@@ -946,7 +1043,10 @@ export function useChat(args: {
946
1043
 
947
1044
  // 生成全新的 system prompt(实时扫 skills + 拼项目指令),
948
1045
  // 与启动时 main.tsx buildInitialHistory 行为完全一致
949
- const newSystemPrompt = await buildBaseSystemPrompt(getWorkingDir(), ALL_TOOLS);
1046
+ const newSystemPrompt = await buildBaseSystemPrompt(
1047
+ getWorkingDir(),
1048
+ ALL_TOOLS,
1049
+ );
950
1050
  historyRef.current.length = 0;
951
1051
  historyRef.current.push({ role: 'system', content: newSystemPrompt });
952
1052
  promptToolSignatureRef.current = '';
@@ -966,13 +1066,19 @@ export function useChat(args: {
966
1066
  // 清空磁盘上的持久化文件(防止下次启动又加载损坏数据)
967
1067
  await clearContext();
968
1068
  // 清空 pre-read guard 状态表(防止跨 session 的 Read 记录污染新会话)
969
- const workspace = await workspaceIndexService.findByPath(getWorkingDir()).catch(() => null);
970
- clearFileState(getOrCreateSessionState(sessionStateKey({
971
- surface: 'tui',
972
- workspaceId: workspace?.id,
973
- cwd: workspace?.id ? undefined : getWorkingDir(),
974
- sessionId: activeSessionIdRef.current,
975
- })).fileReads);
1069
+ const workspace = await workspaceIndexService
1070
+ .findByPath(getWorkingDir())
1071
+ .catch(() => null);
1072
+ clearFileState(
1073
+ getOrCreateSessionState(
1074
+ sessionStateKey({
1075
+ surface: 'tui',
1076
+ workspaceId: workspace?.id,
1077
+ cwd: workspace?.id ? undefined : getWorkingDir(),
1078
+ sessionId: activeSessionIdRef.current,
1079
+ }),
1080
+ ).fileReads,
1081
+ );
976
1082
  // 刚清空,不需要再保存一次"空历史"
977
1083
  }, [bump, isLoading, cancelAllThrottles, resetTurnState]);
978
1084
 
@@ -993,7 +1099,9 @@ export function useChat(args: {
993
1099
  let success = false;
994
1100
  try {
995
1101
  const cwd = getWorkingDir();
996
- const workspace = await workspaceIndexService.findByPath(cwd).catch(() => null);
1102
+ const workspace = await workspaceIndexService
1103
+ .findByPath(cwd)
1104
+ .catch(() => null);
997
1105
  const r = await forceCompact(
998
1106
  historyRef.current,
999
1107
  args.provider,
@@ -1056,7 +1164,10 @@ export function useChat(args: {
1056
1164
 
1057
1165
  // 1. 保住当前 session
1058
1166
  try {
1059
- await saveContext(historyRef.current, sessionFileFor(cwd, activeSessionIdRef.current));
1167
+ await saveContext(
1168
+ historyRef.current,
1169
+ sessionFileFor(cwd, activeSessionIdRef.current),
1170
+ );
1060
1171
  } catch {
1061
1172
  // 保存失败不阻塞切换(用户可能就是想切走止损)
1062
1173
  }
@@ -1102,80 +1213,104 @@ export function useChat(args: {
1102
1213
  [bump, isLoading, cancelAllThrottles, resetTurnState],
1103
1214
  );
1104
1215
 
1105
- const switchWorkspace = useCallback(async (cwd: string, sessionId: string) => {
1106
- if (isLoading) return false;
1107
- const targetCwd = cwd.trim();
1108
- const targetSessionId = sessionId.trim();
1109
- if (!targetCwd || !targetSessionId) return false;
1216
+ const switchWorkspace = useCallback(
1217
+ async (cwd: string, sessionId: string) => {
1218
+ if (isLoading) return false;
1219
+ const targetCwd = cwd.trim();
1220
+ const targetSessionId = sessionId.trim();
1221
+ if (!targetCwd || !targetSessionId) return false;
1110
1222
 
1111
- let loaded: Message[] | null = null;
1112
- let targetExists = false;
1113
- try {
1114
- const targetFile = await resolveSessionFile(targetCwd, targetSessionId);
1115
- try { targetExists = (await stat(targetFile)).isFile(); } catch { targetExists = false; }
1116
- loaded = await loadContext(targetFile);
1117
- if (targetExists && loaded === null) throw new Error('目标会话历史损坏或无法读取');
1118
- } catch (error) {
1119
- setError(error instanceof Error ? error.message : String(error));
1120
- bump();
1121
- return false;
1122
- }
1123
-
1124
- let systemPrompt: string;
1125
- try { systemPrompt = await buildBaseSystemPrompt(targetCwd, ALL_TOOLS); }
1126
- catch (error) {
1127
- setError(`无法打开工作区:${error instanceof Error ? error.message : String(error)}`);
1128
- bump();
1129
- return false;
1130
- }
1223
+ let loaded: Message[] | null = null;
1224
+ let targetExists = false;
1225
+ try {
1226
+ const targetFile = await resolveSessionFile(targetCwd, targetSessionId);
1227
+ try {
1228
+ targetExists = (await stat(targetFile)).isFile();
1229
+ } catch {
1230
+ targetExists = false;
1231
+ }
1232
+ loaded = await loadContext(targetFile);
1233
+ if (targetExists && loaded === null)
1234
+ throw new Error('目标会话历史损坏或无法读取');
1235
+ } catch (error) {
1236
+ setError(error instanceof Error ? error.message : String(error));
1237
+ bump();
1238
+ return false;
1239
+ }
1131
1240
 
1132
- const previousCwd = getWorkingDir();
1133
- try {
1134
- await saveContext(historyRef.current, sessionFileFor(previousCwd, activeSessionIdRef.current));
1135
- } catch {
1136
- // A damaged old workspace must not trap the user in it.
1137
- }
1241
+ let systemPrompt: string;
1242
+ try {
1243
+ systemPrompt = await buildBaseSystemPrompt(targetCwd, ALL_TOOLS);
1244
+ } catch (error) {
1245
+ setError(
1246
+ `无法打开工作区:${error instanceof Error ? error.message : String(error)}`,
1247
+ );
1248
+ bump();
1249
+ return false;
1250
+ }
1138
1251
 
1139
- try { await setActiveSession(targetCwd, targetSessionId); }
1140
- catch (error) {
1141
- setError(`无法切换 Session:${error instanceof Error ? error.message : String(error)}`);
1142
- bump();
1143
- return false;
1144
- }
1145
- setWorkingDir(targetCwd);
1146
- activeSessionIdRef.current = targetSessionId;
1147
- setActiveSessionId(targetSessionId);
1252
+ const previousCwd = getWorkingDir();
1253
+ try {
1254
+ await saveContext(
1255
+ historyRef.current,
1256
+ sessionFileFor(previousCwd, activeSessionIdRef.current),
1257
+ );
1258
+ } catch {
1259
+ // A damaged old workspace must not trap the user in it.
1260
+ }
1148
1261
 
1149
- historyRef.current.length = 0;
1150
- historyRef.current.push({ role: 'system', content: systemPrompt });
1151
- promptToolSignatureRef.current = '';
1152
- if (loaded?.length) {
1153
- historyRef.current.push(...(loaded[0]?.role === 'system' ? loaded.slice(1) : loaded));
1154
- }
1262
+ try {
1263
+ await setActiveSession(targetCwd, targetSessionId);
1264
+ } catch (error) {
1265
+ setError(
1266
+ `无法切换 Session:${error instanceof Error ? error.message : String(error)}`,
1267
+ );
1268
+ bump();
1269
+ return false;
1270
+ }
1271
+ setWorkingDir(targetCwd);
1272
+ activeSessionIdRef.current = targetSessionId;
1273
+ setActiveSessionId(targetSessionId);
1155
1274
 
1156
- const targetWorkspace = await workspaceIndexService.findByPath(targetCwd).catch(() => null);
1157
- clearFileState(getOrCreateSessionState(sessionStateKey({
1158
- surface: 'tui',
1159
- workspaceId: targetWorkspace?.id,
1160
- cwd: targetWorkspace?.id ? undefined : targetCwd,
1161
- sessionId: targetSessionId,
1162
- })).fileReads);
1163
- resetReactiveCompactState();
1164
- cancelAllThrottles();
1165
- setContextUsage(null);
1166
- setError(null);
1167
- setInterrupted(false);
1168
- resetTurnState();
1169
- setCompactGeneration((generation) => generation + 1);
1170
- bump();
1171
- return true;
1172
- }, [bump, isLoading, cancelAllThrottles, resetTurnState]);
1275
+ historyRef.current.length = 0;
1276
+ historyRef.current.push(...(loaded ?? []));
1277
+ upsertStableSystemMessage(historyRef.current, systemPrompt);
1278
+ promptToolSignatureRef.current = '';
1279
+
1280
+ const targetWorkspace = await workspaceIndexService
1281
+ .findByPath(targetCwd)
1282
+ .catch(() => null);
1283
+ clearFileState(
1284
+ getOrCreateSessionState(
1285
+ sessionStateKey({
1286
+ surface: 'tui',
1287
+ workspaceId: targetWorkspace?.id,
1288
+ cwd: targetWorkspace?.id ? undefined : targetCwd,
1289
+ sessionId: targetSessionId,
1290
+ }),
1291
+ ).fileReads,
1292
+ );
1293
+ resetReactiveCompactState();
1294
+ cancelAllThrottles();
1295
+ setContextUsage(null);
1296
+ setError(null);
1297
+ setInterrupted(false);
1298
+ resetTurnState();
1299
+ setCompactGeneration((generation) => generation + 1);
1300
+ bump();
1301
+ return true;
1302
+ },
1303
+ [bump, isLoading, cancelAllThrottles, resetTurnState],
1304
+ );
1173
1305
 
1174
1306
  // history 用 useMemo 托管:version 变化时返回 historyRef.current 的浅拷贝。
1175
1307
  // 为什么必须 .slice():runQuery 内部 mutate 同一个数组(push 新消息),如果这里
1176
1308
  // 返回同一引用,Ink <Static items={history}> 浅比较看不到 prop 变化 → 不 commit
1177
1309
  // 新增 message → turn 结束 LiveArea 消失后屏幕空白,看起来像"输出被吃掉"。
1178
1310
  // slice 后元素引用不变,只是数组容器是新的,足够让 Static 检测到 length 增长并 commit。
1311
+ // version is an explicit invalidation counter; the callback intentionally
1312
+ // reads the ref-backed history rather than the counter itself.
1313
+ // biome-ignore lint/correctness/useExhaustiveDependencies: version intentionally invalidates the ref-backed history snapshot
1179
1314
  const history = useMemo(() => historyRef.current.slice(), [version]);
1180
1315
 
1181
1316
  return {
@@ -1235,7 +1370,9 @@ export interface HandleEventSetters {
1235
1370
  setProgressStage: (s: ProgressStage) => void;
1236
1371
  setReasoningPreview: (updater: (prev: string) => string) => void;
1237
1372
  setRunningToolsMap: (
1238
- updater: (prev: Map<string, ToolStatusState>) => Map<string, ToolStatusState>,
1373
+ updater: (
1374
+ prev: Map<string, ToolStatusState>,
1375
+ ) => Map<string, ToolStatusState>,
1239
1376
  ) => void;
1240
1377
  /**
1241
1378
  * 内核 context_usage 事件的落地 setter(可选——不传时该事件被静默忽略,
@@ -1255,122 +1392,31 @@ export interface HandleEventSetters {
1255
1392
  cancelToolBump: () => void;
1256
1393
  bump: () => void;
1257
1394
  /**
1258
- * 把一行工作流进度作为持久暗色行落进 scrollback(复用 ephemeralLine)。
1259
- * 让 /workflow 执行过程在 TUI 里逐步可见、且出错后仍停留在屏幕上——用户一眼看出停在哪步。
1260
- * 可选:非工作流场景(测试 / -p)不传也不影响。
1395
+ * 把一行插件进度作为持久暗色行落进 scrollback(复用 ephemeralLine)。
1396
+ *
1397
+ * 状态行是**覆盖刷新**的,只留得住最后一条;而工作流排障总发生在跑完之后
1398
+ * (「它停在哪一步了?」)。这条通道把每条进度也留在 scrollback 里,
1399
+ * 失败后仍看得见。走 ephemeral 行:只进展示,不进 history、不被持久化。
1400
+ * 可选:不传即关闭该行为(测试 / -p 不需要)。
1261
1401
  */
1262
1402
  emitProgressLine?: (text: string) => void;
1263
1403
  /**
1264
1404
  * 暂态错误 setter(喂 LiveArea 黄字,可被覆盖刷新)。
1265
- * 可选:不传时退化为"工作流步内 llm_error 不再永久红字残留 + 重试聚合"特性关闭,
1266
- * 非工作流路径行为完全不变(既有单测不传也不受影响)。
1405
+ * 当前用于压缩失败提示与插件重试进度;可选,不传即关闭。
1267
1406
  */
1268
1407
  setTransientError?: (e: string | null) => void;
1269
- /**
1270
- * 暂态错误状态机的跨事件可变上下文(由 useChat 持 ref 注入)。
1271
- * - activeStepId:当前正在执行的 workflow step id(null = 不在任何 step 内)
1272
- * - pendingLlmError:该 step 内捕获的最近一次 llm_error 文案(暂存待裁决)
1273
- * handleEvent 同步读写它;可选——不传时降级(同 setTransientError 缺省)。
1274
- */
1275
- workflowCtx?: { activeStepId: string | null; pendingLlmError: string | null };
1276
- /**
1277
- * 工作流步骤 id → 已插值人话进度名(title)的映射(由 useChat 持 ref 注入)。
1278
- * workflow_step_start 带 title 时记入,step_end / step_skipped 据 id 回查,
1279
- * 让 scrollback 的 ▶/✓/✗/⊘ 三种进度行都显示同一个人话名而非 id + 块内序号。
1280
- * 可选——不传时退化为「end/skipped 显示 id」(既有单测不传也不受影响)。
1281
- */
1282
- stepTitles?: Map<string, string>;
1283
1408
  }
1284
1409
 
1285
1410
  export function handleEvent(
1286
1411
  ev: LoopEvent | PluginEvent,
1287
1412
  setters: HandleEventSetters,
1288
1413
  ): void {
1289
- // ---- 工作流进度事件(WorkflowEvent,不在 LoopEvent union 里)----
1290
- // 渲染成持久的暗色行落进 scrollback:跑到哪一步、成功/失败/跳过都看得见,
1291
- // 且出错后仍停留在屏幕上 —— 解决"工作流执行过程不可见、不知道停在哪"。
1292
- const wf = ev as {
1293
- type: string;
1294
- id?: string;
1295
- kind?: string;
1296
- index?: number;
1297
- total?: number;
1298
- ok?: boolean;
1299
- error?: string;
1300
- reason?: string;
1301
- name?: string;
1302
- totalSteps?: number;
1303
- title?: string;
1304
- };
1305
- // 据 id 回查人话进度名:声明了 title 的步走 title,否则回落 id(end/skipped 用)。
1306
- const titleOrId = (id: string | undefined): string =>
1307
- (id && setters.stepTitles?.get(id)) || id || '?';
1308
- switch (wf.type) {
1309
- case 'workflow_start':
1310
- setters.emitProgressLine?.(`${figures.step} 工作流 ${wf.name} 开始(共 ${wf.totalSteps} 步)`);
1311
- // 新工作流开始:清空上一条工作流残留的 title 映射,避免同名步串味。
1312
- setters.stepTitles?.clear();
1313
- // 工作流开始:立即刷新 token(可能已积累大量前置对话上下文)
1314
- setters.bump();
1315
- return;
1316
- case 'workflow_step_start':
1317
- // 声明了 title 的步显示人话名(与状态行一致、不显示块内相对序号——嵌套块
1318
- // 的 i/total 拼在一起会被误读成"跳步");未声明 title 的步保持 legacy 格式。
1319
- // ⎿ 前缀:step 行挂在「▶ 工作流 x 开始」下的层次连接符(figures.elbow 语汇,
1320
- // 与工具结果/蜂群树统一)。ephemeral 是尾部追加消息,不触任何既有前缀字节。
1321
- if (wf.title) {
1322
- if (wf.id) setters.stepTitles?.set(wf.id, wf.title);
1323
- setters.emitProgressLine?.(` ${figures.elbow} ${figures.step} ${wf.title}`);
1324
- } else {
1325
- setters.emitProgressLine?.(
1326
- ` ${figures.elbow} ${figures.step} ${(wf.index ?? 0) + 1}/${wf.total ?? '?'} ${wf.id} (${wf.kind})`,
1327
- );
1328
- }
1329
- // 暂态错误状态机:记下当前正在执行的 step id,进入"该步内的 llm_error 暂态化"窗口。
1330
- // 进新步先清掉上一步可能残留的暂态错误(上一步已 end、不应跨步显示)。
1331
- if (setters.workflowCtx) {
1332
- setters.workflowCtx.activeStepId = wf.id ?? null;
1333
- setters.workflowCtx.pendingLlmError = null;
1334
- }
1335
- setters.setTransientError?.(null);
1336
- return;
1337
- case 'workflow_step_end':
1338
- setters.emitProgressLine?.(
1339
- ` ${figures.elbow} ${wf.ok ? figures.ok : figures.fail} ${titleOrId(wf.id)}${wf.error ? `:${wf.error}` : ''}`,
1340
- );
1341
- // 暂态错误裁决(仅当 end 的是当前活跃步):
1342
- // ok:true → 重试/恢复成功 → 丢弃 pending llm_error,短暂显示"已恢复"后随刷新消失。
1343
- // ok:false → 该步彻底失败 → 把暂态错误提升为永久红字(chat.error,进 scrollback 上方)。
1344
- if (setters.workflowCtx && setters.workflowCtx.activeStepId === wf.id) {
1345
- const pending = setters.workflowCtx.pendingLlmError;
1346
- if (wf.ok) {
1347
- if (pending) {
1348
- // 成功收回:留一条灰底"已恢复"提示,下一次状态行刷新自然覆盖消失
1349
- setters.setTransientError?.(`${figures.ok} ${wf.id} 已恢复(此前 LLM 调用失败已重试成功)`);
1350
- } else {
1351
- setters.setTransientError?.(null);
1352
- }
1353
- } else {
1354
- // 彻底失败:暂态升永久红字。优先用 step.error,回落到 pending llm_error。
1355
- const finalMsg = wf.error ?? pending;
1356
- if (finalMsg) setters.setError(finalMsg);
1357
- setters.setTransientError?.(null);
1358
- }
1359
- setters.workflowCtx.activeStepId = null;
1360
- setters.workflowCtx.pendingLlmError = null;
1361
- }
1362
- // 步骤完成:强制刷新 token(agent/llm 步骤可能写入大量上下文)
1363
- setters.bump();
1364
- return;
1365
- case 'workflow_step_skipped':
1366
- setters.emitProgressLine?.(` ${figures.elbow} ${figures.skip} ${titleOrId(wf.id)}:${wf.reason ?? ''}`);
1367
- return;
1368
- case 'workflow_done':
1369
- setters.emitProgressLine?.(`${figures.ok} 工作流 ${wf.name} 完成`);
1370
- // 工作流结束:最终刷新(反映完整执行后的上下文量)
1371
- setters.bump();
1372
- return;
1373
- }
1414
+ // ⚠️ 这里曾有一整块 `workflow_start` / `workflow_step_start` / `workflow_step_end` /
1415
+ // `workflow_step_skipped` / `workflow_done` 的渲染逻辑(外加一个 activeStepId
1416
+ // 状态机)。那是 yaml DSL 引擎的事件,随引擎一起删除后**一个发送方都不剩**,
1417
+ // 只有测试自己合成它们 —— 纯死代码。
1418
+ //
1419
+ // V2 工作流发结构化 `workflow_progress`;旧插件仍可发 `plugin_progress`。
1374
1420
 
1375
1421
  // 收口到 LoopEvent:未识别的 PluginEvent type 走 default no-op
1376
1422
  const e = ev as LoopEvent;
@@ -1399,6 +1445,13 @@ export function handleEvent(
1399
1445
  setters.setRunningToolsMap(() => new Map());
1400
1446
  setters.bump();
1401
1447
  break;
1448
+ case 'assistant_draft_discarded':
1449
+ setters.flushStreamingBuffer();
1450
+ setters.setStreamingText(() => '');
1451
+ setters.cancelReasoningFlush();
1452
+ setters.setReasoningPreview(() => '');
1453
+ setters.setProgressStage('thinking');
1454
+ break;
1402
1455
  case 'tool_start':
1403
1456
  // 把工具加入运行集合;并行多个工具时这里会累积。
1404
1457
  // v2: argsFriendly 是 loop.ts 通过 LoopEvent 扩展字段传过来的人话描述
@@ -1423,7 +1476,11 @@ export function handleEvent(
1423
1476
  const current = prev.get(e.toolCallId);
1424
1477
  if (!current) return prev;
1425
1478
  const next = new Map(prev);
1426
- next.set(e.toolCallId, { ...current, progress: e.message, progressDetail: e.detail });
1479
+ next.set(e.toolCallId, {
1480
+ ...current,
1481
+ progress: e.message,
1482
+ progressDetail: e.detail,
1483
+ });
1427
1484
  return next;
1428
1485
  });
1429
1486
  setters.scheduleToolBump();
@@ -1482,33 +1539,12 @@ export function handleEvent(
1482
1539
  setters.bump();
1483
1540
  break;
1484
1541
  }
1485
- // 工作流步执行中的 max_turns 是可恢复截断信号:runner 会在共享 history 上续跑。
1486
- // 这里不要落入永久红字错误区;顶层非工作流 max_turns 仍保持原有错误展示。
1487
- if (
1488
- code === 'max_turns' &&
1489
- setters.workflowCtx &&
1490
- setters.workflowCtx.activeStepId !== null &&
1491
- setters.setTransientError
1492
- ) {
1493
- setters.setTransientError('↻ 当前步骤达到轮数预算,正在续跑…');
1494
- setters.bump();
1495
- break;
1496
- }
1497
- // 工作流步执行中的 llm_error:暂态而非永久红字 ——
1498
- // 该步通常会被重试,这里只暂存 + 喂 LiveArea 黄字,等 step_end 裁决去留。
1499
- // 非工作流场景(无活跃 step,或缺 setTransientError 降级)走原有永久红字路径。
1500
- if (
1501
- code === 'llm_error' &&
1502
- setters.workflowCtx &&
1503
- setters.workflowCtx.activeStepId !== null &&
1504
- setters.setTransientError
1505
- ) {
1506
- setters.workflowCtx.pendingLlmError = e.error;
1507
- setters.setTransientError(`⚠ LLM 调用失败(重试中):${e.error}`);
1508
- setters.bump();
1509
- break;
1510
- }
1511
- // 其余:顶层 halt(无 code)、非工作流 llm_error、aborted 等 → 永久红字。
1542
+ // 注:这里曾有两条「工作流步内的 max_turns / llm_error 降级成暂态黄字」分支,
1543
+ // 都以 `workflowCtx.activeStepId !== null` 为前提。那个 id 由 yaml DSL 的
1544
+ // workflow_step_start 事件写入,事件删除后恒为 null,两条分支永远走不到。
1545
+ // 代码编排引擎把这两种错误在 `api.ts` 内部就地消化(`continue`,不外泄给顶层),
1546
+ // 顶层根本收不到 —— 所以不是「少了个降级」,而是这层判断已无对象。
1547
+ // 其余:顶层 halt(无 code)、llm_error、aborted 等 → 永久红字。
1512
1548
  setters.setError(e.error);
1513
1549
  setters.bump();
1514
1550
  break;
@@ -1535,6 +1571,14 @@ export function handleEvent(
1535
1571
  max: e.max,
1536
1572
  message: e.message,
1537
1573
  });
1574
+ // 同时落一行进 scrollback。状态行是**覆盖刷新**的:一条二十步的工作流跑完,
1575
+ // 状态行只剩最后一步,前面十九步去哪了、在哪一步失败的全查不到 —— 而排障
1576
+ // 恰恰总发生在跑完之后。legacy 靠 workflow_step_* 事件做这件事,那套事件随
1577
+ // yaml 引擎删了,这里由 plugin_progress 承担同一职责。
1578
+ // 走 ephemeral 行:只进展示,不进 history、不被 saveContext 持久化。
1579
+ if (typeof e.message === 'string' && e.message.length > 0) {
1580
+ setters.emitProgressLine?.(e.message);
1581
+ }
1538
1582
  // 重试进度也镜像到暂态行(覆盖刷新):runner 在重试时发 "↻ 重试 x N/M…"。
1539
1583
  // 与 warning 分支互为补充(plugin_progress 文案更短,适合状态行下方一行)。
1540
1584
  if (
@@ -1546,6 +1590,24 @@ export function handleEvent(
1546
1590
  }
1547
1591
  setters.bump();
1548
1592
  break;
1593
+ case 'workflow_progress': {
1594
+ const indent = ' '.repeat(e.depth ?? 0);
1595
+ const count =
1596
+ e.current !== undefined && e.total !== undefined
1597
+ ? `${e.current}/${e.total} `
1598
+ : '';
1599
+ const message = `${indent}${count}${e.message ?? e.phase}`;
1600
+ setters.setPluginProgress({
1601
+ pluginId: e.workflow,
1602
+ current: e.current,
1603
+ max: e.total,
1604
+ message,
1605
+ });
1606
+ setters.emitProgressLine?.(message);
1607
+ if (e.status === 'warning') setters.setTransientError?.(message);
1608
+ setters.bump();
1609
+ break;
1610
+ }
1549
1611
  case 'reasoning':
1550
1612
  // reasoning delta 频率跟 text delta 一样高(60+/秒),不节流的话每个 delta 立即
1551
1613
  // setReasoningPreview → LiveArea rerender → Ink 重绘扛不住。