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
@@ -1,514 +1,451 @@
1
1
  /**
2
2
  * ============================================================
3
- * plugins/workflow-runner/src/runner.ts —— workflow 顺序执行器
3
+ * plugins/workflow-runner/src/runner.ts —— 一次工作流运行的编排
4
4
  * ------------------------------------------------------------
5
- * AsyncGenerator,按 yaml.steps[] 顺序执行:
6
- * - when: 不满足 → yield workflow_step_skipped
7
- * - 命中动作字段 → 调对应 step executor
8
- * - capture: 把 result.raw 上的字段绑到 VarStack
9
- * - onError: halt(默认)→ yield error 后 return;continue → 记录后跳过
10
- * - 信号 abort → yield interrupted 后 return
5
+ * 串起:任务身份判定 → 账本装载 → 目标锚构造 → 组装 WorkflowContext → 执行 ESM → 收尾归档。
11
6
  *
12
- * 支持的 step kind(loader 已校验):
13
- * - tool / llm / assert —— 原子动作(execXxxStep 返回 StepResult)
14
- * - agent / skill —— 有界 TAOR:runQuery(agent) / runQuery(skill);
15
- * 透传子事件(stage_change/text/reasoning/tool_*)
16
- * - branch / loop / pause / echo —— 控制流,直接在 runSteps 内处理(不走 executor)
17
- * - break / continue —— 循环信号(可带 target 穿透到指定外层循环)
18
- *
19
- * 控制流嵌套通过 runSteps 递归实现:branch.then/else / loop.do 子数组
20
- * 是 StepDef[],调用方语义与顶层 def.steps 一致;变量栈用 VarStack.push/pop
21
- * 在 loop 迭代间隔离 `${as}` / `${as}_idx`。
7
+ * ⚠️ **为什么需要事件队列**:ESM 的 run(wf) 是一个普通 async 函数,它执行期间
8
+ * 没有任何机制能「yield 一个事件出去」;而宿主要的是一条实时事件流(否则
9
+ * 整条工作流跑完之前 UI 一片死寂)。所以 `wf` 上下文把事件推进队列,本函数
10
+ * 作为 async generator 一边 await 脚本的 promise、一边把队列排空往外 yield。
22
11
  * ============================================================
23
12
  */
24
- import { existsSync } from 'node:fs';
25
13
  import { resolve } from 'node:path';
26
- import { dlog, getWorkingDir } from '../../../src/plugin-sdk.js';
27
- import { evalExpr, interpolate } from './expressions.js';
28
- import { buildTaskArbitrator, isContinuationPhrase } from './taskIdentity.js';
29
- import { execAgentStep } from './stepExecutors/agent.js';
30
- import { execAssertStep } from './stepExecutors/assert.js';
31
- import { execVerifyStep } from './stepExecutors/verify.js';
32
- import { execLlmStep } from './stepExecutors/llm.js';
33
- import { execSkillStep } from './stepExecutors/skill.js';
34
- import { execToolStep } from './stepExecutors/tool.js';
35
- import { execEchoStep } from './stepExecutors/echo.js';
36
- import { execSetStep } from './stepExecutors/set.js';
37
- import { execBranchStep } from './controlFlow/branch.js';
38
- import { execLoopStep } from './controlFlow/loop.js';
39
- import { execPauseStep } from './controlFlow/pause.js';
40
- import { execSignalStep } from './controlFlow/signal.js';
41
- import { runStepWithRetry } from './stepPipeline.js';
42
- import { VarStack } from './types.js';
43
- import { WorkflowState, } from './workflowState.js';
44
- /**
45
- * 插件 ID:把私有 workflow_* 事件镜像成框架通用 plugin_progress 时挂这个 id。
46
- * LiveArea 状态行会渲染成 "[plugin:workflow-runner] i/total <message>"
47
- * (src/ui/LiveArea.tsx:123),让 TUI 里跑 workflow 不再"无声"。
48
- * (-p 非交互模式另由 src/cli/print.ts 把通用 warning 事件落 stderr。)
49
- */
14
+ import { ExecutionScope, createWorkflowContext } from './runtime/context.js';
15
+ import { buildGoalAnchor } from './goalAnchor.js';
16
+ import { coerceInputs } from './inputCoercion.js';
17
+ import { Journal, fingerprint } from './journal.js';
18
+ import { bindResourceInputs } from './resourceInputs.js';
19
+ import { WorkflowScriptError, runScript } from './sandbox.js';
20
+ import { DEFAULT_SESSION_ID, buildTaskArbitrator, sanitizeWorkflowDirName, } from './taskIdentity.js';
21
+ import { effectiveWorkflowCwd } from './toolRuntime.js';
22
+ import { resolveWorkflowBundle, } from './workflowDependencies.js';
23
+ import { WorkflowState } from './workflowState.js';
50
24
  const WF_PLUGIN_ID = 'workflow-runner';
51
- /**
52
- * 从工作流 inputs 定义 + 实际值中提取用户的原始目标,
53
- * 构建紧凑的目标锚定字符串。
54
- *
55
- * 提取策略:
56
- * 1. 优先取名为 "goal" 的输入(plan.yaml / ralph-loop.yaml 等通用约定)
57
- * 2. 其次取第一个 required: true 的 string 类型输入
58
- * 3. 都没有 → 返回空字符串(executor 自动跳过)
59
- *
60
- * 锚定字符串格式紧凑(~60-150 字符),注入每步 prompt 前置,
61
- * 确保 LLM 在任何步骤都能"看到"用户原始目标,避免长上下文稀释。
62
- */
63
- function buildGoalAnchor(inputDefs, inputValues) {
64
- let goalValue;
65
- // 策略 1:优先 "goal"
66
- const goalInput = inputDefs.find((d) => d.name === 'goal' && d.type === 'string');
67
- if (goalInput) {
68
- const v = inputValues[goalInput.name];
69
- if (typeof v === 'string' && v.trim()) {
70
- goalValue = v.trim();
71
- }
25
+ /** 事件队列:生产者随时 push,消费者按序 async 迭代,close 后排空即结束。 */
26
+ class EventQueue {
27
+ items = [];
28
+ waiter = null;
29
+ closed = false;
30
+ push(item) {
31
+ this.items.push(item);
32
+ this.waiter?.();
33
+ this.waiter = null;
34
+ }
35
+ close() {
36
+ this.closed = true;
37
+ this.waiter?.();
38
+ this.waiter = null;
72
39
  }
73
- // 策略 2:fallback 第一个 required string
74
- if (!goalValue) {
75
- const firstRequired = inputDefs.find((d) => d.required && d.type === 'string');
76
- if (firstRequired) {
77
- const v = inputValues[firstRequired.name];
78
- if (typeof v === 'string' && v.trim()) {
79
- goalValue = v.trim();
40
+ async *drain() {
41
+ while (true) {
42
+ while (this.items.length > 0) {
43
+ yield this.items.shift();
80
44
  }
45
+ if (this.closed)
46
+ return;
47
+ await new Promise((resolve) => {
48
+ this.waiter = resolve;
49
+ });
81
50
  }
82
51
  }
83
- if (!goalValue)
84
- return '';
85
- return `> 🎯 用户原始目标:${truncateGoal(goalValue)}\n> ⚠️ 所有操作必须直接回应此目标,不可偏离或遗忘。`;
86
52
  }
87
- /** 截断过长目标(>200 字符时保留首尾,中间用 … 连接)——goalAnchor 注入每步 prompt,必须紧凑。 */
88
- function truncateGoal(goal) {
89
- return goal.length > 200 ? `${goal.slice(0, 120)}…${goal.slice(-60)}` : goal;
90
- }
91
- /** 可中断的退避等待:用户 abort 时立即返回(让后续步骤检查 signal 收口,不在这里抛)。 */
92
- function sleepInterruptible(ms, signal) {
93
- return new Promise((resolve) => {
94
- if (signal?.aborted) {
95
- resolve();
96
- return;
97
- }
98
- const timer = setTimeout(() => {
99
- signal?.removeEventListener('abort', onAbort);
100
- resolve();
101
- }, ms);
102
- const onAbort = () => {
103
- clearTimeout(timer);
104
- resolve();
105
- };
106
- signal?.addEventListener('abort', onAbort, { once: true });
107
- });
108
- }
109
- /** 把一段用户输入并入贯穿目标(内存 + 状态目录持久化,resume 不丢)。 */
110
- async function addGoalSupplement(ctx, state, text) {
111
- const t = text.trim();
112
- if (!t)
113
- return;
114
- // 锚定必须紧凑(注入每步 prompt),逐条截断;完整原文在 capture 变量 / history 里
115
- const compact = t.length > 240 ? `${t.slice(0, 240)}…` : t;
116
- ctx.goalSupplements ??= [];
117
- ctx.goalSupplements.push(compact);
118
- await state.appendSupplement(compact);
119
- }
120
- function stepKind(step) {
121
- if (step.type === 'break')
122
- return 'break';
123
- if (step.type)
124
- return step.type;
125
- if (step.tool)
126
- return 'tool';
127
- if (step.skill)
128
- return 'skill';
129
- if (step.agent)
130
- return 'agent';
131
- if (step.llm)
132
- return 'llm';
133
- return 'assert'; // 不会到这里(loader 已校验),兜底
134
- }
135
- function resolvePositiveIterationLimit(raw, vars, defaultValue, label) {
136
- if (raw === undefined)
137
- return { ok: true, value: defaultValue };
138
- const interpolated = typeof raw === 'number' ? raw : interpolate(String(raw), vars);
139
- const n = typeof raw === 'number' ? raw : Number(String(interpolated).trim());
140
- if (!Number.isFinite(n) || n <= 0) {
141
- return {
142
- ok: false,
143
- error: `${label} max_iterations parse failed: ${JSON.stringify(raw)} -> "${String(interpolated)}" is not a positive number`,
53
+ /** 执行一个代码形态工作流,产出 LoopEvent 流。 */
54
+ export async function* runWorkflowScript(script, opts) {
55
+ try {
56
+ const bundle = await resolveWorkflowBundle(script, effectiveWorkflowCwd(opts.ctx));
57
+ opts.ctx.resourceLedger ??= {
58
+ inputTokens: 0,
59
+ outputTokens: 0,
60
+ limit: null,
61
+ trustworthy: true,
144
62
  };
63
+ const scope = new ExecutionScope();
64
+ const lease = await scope.scheduler.acquire('root', scope.rootRunId, opts.ctx.signal);
65
+ try {
66
+ yield* executeWorkflowScript(script, {
67
+ ...opts,
68
+ bundle,
69
+ scope,
70
+ runId: scope.rootRunId,
71
+ depth: 0,
72
+ });
73
+ }
74
+ finally {
75
+ lease.release();
76
+ }
145
77
  }
146
- return { ok: true, value: Math.floor(n) };
147
- }
148
- /**
149
- * 分发"动作"型 step(tool / agent / llm / skill / assert)到对应 executor。
150
- * 控制流 step(branch / loop / pause 等)由 runSteps 直接处理,不走这里。
151
- */
152
- async function* dispatchActionStep(step, vars, ctx) {
153
- if (step.type === 'assert')
154
- return execAssertStep(step, vars, ctx);
155
- if (step.type === 'verify')
156
- return await execVerifyStep(step, vars);
157
- // set / echo 从 runSteps 的内联块搬进执行器体系:它们不影响迭代、只产出 StepResult。
158
- // 搬过来的直接收益是 `capture:` 自然生效(此前它们从不调 bindCapture —— 静默失效)。
159
- if (step.type === 'set')
160
- return yield* execSetStep(step, vars);
161
- if (step.type === 'echo')
162
- return yield* execEchoStep(step, vars);
163
- if (step.tool)
164
- return yield* execToolStep(step, vars, ctx);
165
- if (step.agent)
166
- return yield* execAgentStep(step, vars, ctx);
167
- if (step.llm)
168
- return yield* execLlmStep(step, vars, ctx);
169
- if (step.skill)
170
- return yield* execSkillStep(step, vars, ctx);
171
- throw new Error(`step ${step.id}: 无可执行字段(tool/agent/llm/skill/assert/verify)`);
172
- }
173
- /**
174
- * 递归执行 steps[]。返回 StepFlow(completed / halted / break|continue 信号,见上方定义)。
175
- *
176
- * yields 混合事件:WorkflowEvent + LoopEvent(skill 子循环转发 / error / interrupted)。
177
- * index/total 字段相对**当前 block**,嵌套 block 自有计数(branch.then 内 3 步就报 total=3)。
178
- */
179
- async function* runSteps(steps, vars, ctx, state,
180
- // checkpoint 记录范围:顶层调用(runWorkflow → def.steps)传 { name: id=>id, descendIntoLoop:true };
181
- // 顶层 loop 的 do[] 由 loop 处理器派生复合名 scope;更深嵌套传 undefined(不记录)。
182
- cp,
183
- // resume:本列表中索引 < startFrom 的步已在上次运行完成,本次跳过(变量已由 runWorkflow 回放)。
184
- // 顶层与「loop resume 进迭代中间」(loop 处理器对 do[] 传 innerStartFrom)都用它。
185
- startFrom = 0,
186
- // 仅顶层传:进行中循环的恢复信息(迭代号 + 迭代内起点),由匹配 id 的 loop 消费一次。
187
- loopResume) {
188
- for (let i = 0; i < steps.length; i++) {
189
- const step = steps[i];
190
- if (ctx.signal?.aborted) {
78
+ catch (error) {
79
+ if (opts.ctx.signal?.aborted ||
80
+ (error instanceof Error && error.name === 'AbortError'))
191
81
  yield { type: 'interrupted' };
192
- return 'halted';
193
- }
194
- // resume:跳过已完成的步(变量已回放,无需重跑)。
195
- if (i < startFrom) {
82
+ else
196
83
  yield {
197
- type: 'workflow_step_skipped',
198
- id: step.id,
199
- reason: 'resume: 上次已完成',
84
+ type: 'error',
85
+ error: error instanceof Error ? error.message : String(error),
200
86
  };
201
- continue;
202
- }
203
- // when 条件:**所有** step 类型统一在这里判。控制流处理器里不要再判一次 ——
204
- // 重构前 break/continue 块自带一份,而它恒为真(false 时这里已经 continue 掉了),
205
- // 连它的 catch 分支都不可达。那是纯死代码,随本次重构删除。
206
- if (step.when) {
207
- let pass = false;
208
- try {
209
- pass = Boolean(evalExpr(step.when, vars));
210
- }
211
- catch (e) {
212
- const errMsg = `when 表达式错误: ${e.message}`;
213
- yield { type: 'workflow_step_end', id: step.id, ok: false, error: errMsg };
214
- yield {
215
- type: 'error',
216
- error: `Workflow halted at step "${step.id}": ${errMsg}`,
217
- };
218
- return 'halted';
219
- }
220
- if (!pass) {
221
- yield { type: 'workflow_step_skipped', id: step.id, reason: 'when=false' };
222
- await state.appendProgress(`- ${step.id} skipped (when=false)`);
223
- continue;
224
- }
225
- }
226
- const kind = stepKind(step);
227
- dlog('wf', `step ${i + 1}/${steps.length} START ${step.id} (${kind})`);
228
- // 已插值的人话进度名随 start 事件带给 UI:scrollback 进度行显示它而非块内相对
229
- // 序号,与状态行(下方 plugin_progress)保持一致。未声明 title 的步省略此字段。
230
- const interpolatedTitle = step.title ? interpolate(step.title, vars) : undefined;
231
- yield {
232
- type: 'workflow_step_start',
233
- id: step.id,
234
- kind,
235
- index: i,
236
- total: steps.length,
237
- title: interpolatedTitle,
238
- };
239
- // P1 TUI 可见:把私有 workflow_step_start 镜像成框架通用 plugin_progress。
240
- // 有 title 的步显示人话阶段名(如「阶段2/6·起草实现计划」)且不带块内序号——
241
- // 嵌套块的 i/total(顶层 5、循环体 12…)会被用户误读成"跳步/结构乱";
242
- // 未声明 title 的步保持 legacy 格式 "i/total step <id> (<kind>)"。
243
- yield {
244
- type: 'plugin_progress',
245
- pluginId: WF_PLUGIN_ID,
246
- current: i + 1,
247
- max: steps.length,
248
- message: interpolatedTitle ?? `step ${step.id} (${kind})`,
249
- };
250
- // ---------- 控制流:改变的是「循环怎么走」,不是产出一个 StepResult ----------
251
- //
252
- // 所以它们不塞进 stepExecutors 那套 (step,vars,ctx)→StepResult 的形状里,
253
- // 各自住在 controlFlow/ 并共用出口 ControlOutcome —— 这里只需一个分派。
254
- const controlDeps = {
255
- ctx,
256
- state,
257
- ...(cp ? { cp } : {}),
258
- runBlock: (blockSteps, blockVars, blockCp, blockStart, blockResume) => runSteps(blockSteps, blockVars, ctx, state, blockCp, blockStart, blockResume),
259
- index: i,
260
- total: steps.length,
261
- addGoalSupplement: (text) => addGoalSupplement(ctx, state, text),
262
- };
263
- let outcome = null;
264
- if (step.type === 'branch') {
265
- outcome = yield* execBranchStep(step, vars, controlDeps);
266
- }
267
- else if (step.type === 'loop') {
268
- outcome = yield* execLoopStep(step, vars, controlDeps, loopResume);
269
- // 恢复信息只消费一次:匹配到的那个 loop 用掉之后,后续 loop 不再套用。
270
- if (loopResume && loopResume.loopId === step.id)
271
- loopResume = undefined;
272
- }
273
- else if (step.type === 'pause') {
274
- outcome = yield* execPauseStep(step, vars, controlDeps);
275
- }
276
- else if (step.type === 'break' || step.type === 'continue') {
277
- outcome = yield* execSignalStep(step, controlDeps);
278
- }
279
- if (outcome) {
280
- if (outcome.kind === 'halted')
281
- return 'halted';
282
- if (outcome.kind === 'flow')
283
- return outcome.signal;
284
- continue;
285
- }
286
- // ---------- 动作步:tool / llm / skill / agent / assert / verify / set / echo ----------
287
- //
288
- // 统一走 runStepWithRetry:in-run 重试 → onError 三态 → commitStep 提交。
289
- // 「提交一步」只有 stepPipeline 那一份实现,这里不再自己写(见 stepPipeline 头注)。
290
- const actionOutcome = yield* runStepWithRetry({
291
- step,
292
- kind,
293
- vars,
294
- ctx,
295
- state,
296
- ...(cp ? { cp } : {}),
297
- index: i,
298
- total: steps.length,
299
- addGoalSupplement: (text) => addGoalSupplement(ctx, state, text),
300
- execute: () => dispatchActionStep(step, vars, ctx),
301
- });
302
- if (actionOutcome === 'halted')
303
- return 'halted';
304
87
  }
305
- return 'completed';
306
88
  }
307
- /**
308
- * 聚合工作流【声明】的产物路径(相对 cwd):def.artifacts + 所有 step.artifacts(含嵌套 then/else/do)。
309
- * 通用:由 yaml 的 `artifacts:` 声明驱动,runner 不硬编码任何具体文件名(如 PLAN.md)。
310
- * 用途:new-task 检测时清旧任务残留产物、completed 时报实际产物。
311
- */
312
- function collectDeclaredArtifacts(def) {
313
- const out = new Set();
314
- for (const a of def.artifacts ?? [])
315
- out.add(a);
316
- const walk = (steps) => {
317
- for (const s of steps ?? []) {
318
- for (const a of s.artifacts ?? [])
319
- out.add(a);
320
- walk(s.then);
321
- walk(s.else);
322
- walk(s.do);
323
- }
324
- };
325
- walk(def.steps);
326
- return [...out];
327
- }
328
- /**
329
- * 运行一个 workflow。
330
- *
331
- * yields 混合事件:本地 WorkflowEvent(workflow_* 系列,UI 静默忽略不渲染)
332
- * + 通用 LoopEvent(error / interrupted / 子 step 透传的 text/tool/stage_change 等,
333
- * UI 识别并渲染)。经 plugin.ts -> pluginRunner 透传给 UI;workflow_* 由 PluginEvent
334
- * 开放契约承载。
335
- *
336
- * 成功跑完以 `workflow_done` 收尾(携带 vars 快照);中途失败/中断只 yield error/interrupted。
337
- * **对外流式协议的通用终结事件 `turn_done` 由命令层 index.ts 在成功后补发**(见那里)——
338
- * 本函数自身事件序列保持不变,直测它的 runner.test.ts / 单元测试仍以 workflow_done 为最后一条。
339
- */
340
- export async function* runWorkflow(def, inputs, ctx) {
341
- const vars = new VarStack();
342
- vars.setGlobal('inputs', inputs);
343
- // onError:'recover' 用:把工作流总目标(description)透传给恢复 agent,
344
- // 让它知道「这条工作流到底想干什么」。已有 goal 则不覆盖。
345
- const runCtx = { ...ctx, goal: ctx.goal ?? def.description };
346
- // 通用目标锚定:从 inputs 中提取用户的原始目标,
347
- // 注入 RunContext.goalAnchor,让 agent/llm/skill 步自动前置到每步 prompt。
348
- // 提取策略:优先 "goal" 命名的输入 → 其次第一个 required string 输入。
349
- runCtx.goalAnchor = buildGoalAnchor(def.inputs ?? [], inputs);
350
- // self_heal:把全局 max_retries 默认透传给每步的 in-run retry(step.max_retries 可覆盖)。
351
- runCtx.selfHealMaxRetries = def.self_heal?.max_retries;
352
- runCtx.retryDelayMs = def.self_heal?.retry_delay_ms;
353
- runCtx.maxTurnsRetryDelayMs = def.self_heal?.max_turns_retry_delay_ms;
354
- // 声明产物清单:state.init 用于 new-task 清理/completed 报产物;
355
- // 挂到 runCtx 供 tool 步覆盖判定(声明产物自动 pre-Read 解 Write 守卫)。
356
- const declaredArtifacts = collectDeclaredArtifacts(def);
357
- runCtx.declaredArtifacts = declaredArtifacts;
358
- // 任务身份语义仲裁器(hash 不等 + 旧任务未完成时的二审,见 taskIdentity.ts):
359
- // 短语快路径 → LLM 单轮仲裁 → AskUserQuestion → 兜底 new-task。
360
- // init 非 generator,仲裁过程的人话记录收进 notes,init 返回后统一 yield。
89
+ /** Same executor for roots and children. Only roots emit terminal host events. */
90
+ async function* executeWorkflowScript(script, opts) {
91
+ const { ctx, inputs } = opts;
92
+ ctx.signal?.throwIfAborted();
93
+ const sessionId = sanitizeWorkflowDirName(opts.sessionId ?? ctx.runtimeContext?.sessionId ?? DEFAULT_SESSION_ID);
94
+ const scriptHash = opts.bundle.hash;
95
+ const state = new WorkflowState(effectiveWorkflowCwd(ctx), script.name, sessionId, script.meta.inputs, opts.taskId ?? ctx.managedExecution?.runId, opts.parentStateDir);
361
96
  const arbitrationNotes = [];
362
- const state = new WorkflowState(getWorkingDir(), def.name);
363
- const resume = await state.init(def.name, inputs, def.steps, def.self_heal?.backoff, declaredArtifacts, buildTaskArbitrator({
364
- provider: runCtx.provider,
365
- chat: runCtx.chat,
366
- executeTool: runCtx.executeTool,
367
- signal: runCtx.signal,
368
- notes: arbitrationNotes,
369
- }));
370
- yield {
371
- type: 'workflow_start',
372
- name: def.name,
373
- totalSteps: def.steps.length,
374
- };
375
- // 仲裁过程可见化(命中延续短语 / LLM 判定 / 用户确认 / 兜底)
376
- for (const n of arbitrationNotes) {
377
- yield { type: 'warning', message: n };
97
+ const resume = await state.init(inputs, scriptHash, script.meta.artifacts ?? [], ctx.managedExecution || opts.parentStateDir
98
+ ? undefined
99
+ : buildTaskArbitrator({
100
+ provider: ctx.provider,
101
+ chat: ctx.chat,
102
+ executeTool: ctx.executeTool,
103
+ signal: ctx.signal,
104
+ notes: arbitrationNotes,
105
+ }));
106
+ for (const note of arbitrationNotes) {
107
+ yield { type: 'warning', message: note };
378
108
  }
379
- // 同任务此前已成功完成(goal hash 匹配 + status=done)→ 不重跑,报产物后正常收尾。
380
- // 成功运行会 archive() 把状态目录改名 .done,init() 回退读 .done 检测到 completed。
109
+ // 已完成:报实际产物,不重跑。
381
110
  if (resume.mode === 'completed') {
382
- const arts = resume.artifacts?.length ? resume.artifacts.join(', ') : '(无记录)';
111
+ const restoredEvent = {
112
+ type: 'workflow_progress',
113
+ workflow: script.name,
114
+ runId: opts.runId,
115
+ rootRunId: opts.scope.rootRunId,
116
+ ...(opts.parentRunId ? { parentRunId: opts.parentRunId } : {}),
117
+ depth: opts.depth,
118
+ phase: 'workflow',
119
+ status: 'complete',
120
+ message: '恢复已完成结果',
121
+ };
122
+ await state.appendEvent(restoredEvent);
123
+ yield restoredEvent;
124
+ const list = resume.artifacts?.length
125
+ ? resume.artifacts.map((a) => ` · ${a}`).join('\n')
126
+ : ' (本次运行没有声明产物)';
383
127
  yield {
384
128
  type: 'text',
385
- delta: `✓ 同名任务此前已完成(产物:${arts})。如需重跑请先 /new 清理工作流状态。\n`,
129
+ delta: `工作流「${script.name}」的这个目标此前已完成,产物:\n${list}\n换个目标即可重新执行。\n`,
130
+ };
131
+ if (!opts.parentStateDir)
132
+ yield { type: 'turn_done' };
133
+ return {
134
+ workflow: script.name,
135
+ runId: opts.runId,
136
+ result: await state.readResult(),
137
+ artifacts: (resume.artifacts ?? []).map((p) => resolve(state.workingDir, p)),
138
+ reused: true,
386
139
  };
387
- yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
388
- return;
389
140
  }
390
- // 定义结构变更可见化:同任务但 yaml 改过结构 → 旧断点作废、从头开始。
391
- // 必须明示——曾有用户在 yaml 重构后同 goal 重跑,看到「从第 2 步续跑」
392
- // 困惑是 bug 还是残留;作废旧断点时不说原因同样会被当成 bug。
393
- if (resume.defChanged) {
141
+ if (resume.scriptChanged) {
394
142
  yield {
395
143
  type: 'warning',
396
- message: '♻ 工作流定义已变更(步骤结构与旧断点不再匹配),旧执行现场已作废,本次从头开始执行。',
144
+ message: `⚠ 工作流「${script.name}」的脚本/依赖已改变或产物/返回值缺失 —— 旧断点作废,本次全新开始。`,
397
145
  };
398
146
  }
399
- // fresh/new-task 启动清理可见化:全新任务开始前已删掉的残留产物(上次任务
400
- // 的声明产物 + /new 后无主孤儿)。用户约定:全新 plan 一律从干净状态开始;
401
- // 删除范围仅限本工作流声明的产物,同任务 resume 不清理。
402
- if (resume.cleanedArtifacts?.length) {
147
+ if (resume.archivedTo) {
403
148
  yield {
404
149
  type: 'warning',
405
- message: `全新任务:已清理上次残留的旧产物 ${[...new Set(resume.cleanedArtifacts)].join('、')},从干净状态开始。`,
150
+ message: `旧产物已归档到 ${resume.archivedTo}(不是删除,需要时可取回)。`,
406
151
  };
407
152
  }
408
- // 仲裁判定的延续 resume:本次 inputs 里的 goal 是「继续」这类延续指令,
409
- // 不能拿它当目标——两处都要换回旧任务的真实目标:
410
- // 1. goalAnchor:锚旧目标 + 新输入作为补充指令(如「继续,但改成 12 页」不丢失);
411
- // 2. VarStack 的 inputs:yaml prompt 正文大量内插 ${inputs.goal}(业务上下文),
412
- // 不替换的话重跑步骤会渲染成「用户的目标是:『继续』」——锚定与正文打架。
413
- // goal 承载键用与 buildGoalAnchor/extractGoal 同一策略(通用,零具体 yaml 耦合);
414
- // 磁盘 inputs.json 保持旧任务值不动,任务身份 hash 语义不受影响。
153
+ // 目标锚:仲裁判定的延续要锚**旧目标**,本次输入降级成补充要求。
154
+ ctx.goalAnchor = ctx.managedExecution
155
+ ? ctx.managedExecution.anchor
156
+ : resume.prevGoal
157
+ ? buildGoalAnchor({ goal: resume.prevGoal }, script.meta.inputs)
158
+ : buildGoalAnchor(inputs, script.meta.inputs);
159
+ ctx.goalSupplements = await state.readSupplements();
160
+ ctx.declaredArtifacts = new Set(script.meta.artifacts ?? []);
161
+ const journal = new Journal(state.journalPath, {
162
+ bundleHash: scriptHash,
163
+ rootRunId: opts.scope.rootRunId,
164
+ });
165
+ await journal.load(script.meta.resumeBackoff ?? 0);
415
166
  if (resume.mode === 'resume') {
416
- // 贯穿补充要求:上次运行累积的(pause 反馈 / 澄清结论)从状态目录恢复,
417
- // resume 后注入每步锚定不丢失。
418
- runCtx.goalSupplements = await state.readSupplements();
419
- }
420
- if (resume.mode === 'resume' && resume.prevGoal) {
421
- const goalKey = (def.inputs ?? []).find((d) => d.name === 'goal' && d.type === 'string')
422
- ?.name ??
423
- (def.inputs ?? []).find((d) => d.required && d.type === 'string')?.name;
424
- if (goalKey && typeof inputs[goalKey] === 'string') {
425
- vars.setGlobal('inputs', { ...inputs, [goalKey]: resume.prevGoal });
426
- }
427
- runCtx.goalAnchor = `> 🎯 用户原始目标:${truncateGoal(resume.prevGoal)}\n> ⚠️ 所有操作必须直接回应此目标,不可偏离或遗忘。`;
428
- // 新输入是带信息量的补充指令(非纯「继续」延续短语)→ 并入贯穿补充要求;
429
- // 经 goalSupplements 统一渲染(与 pause 反馈/澄清结论同通道),且去重防
430
- // 反复 resume 时同一句话累积成噪声。
431
- const supp = resume.supplementaryGoal;
432
- if (supp &&
433
- supp !== resume.prevGoal &&
434
- !isContinuationPhrase(supp) &&
435
- !(runCtx.goalSupplements ?? []).some((s) => s.startsWith(supp.slice(0, 240)))) {
436
- await addGoalSupplement(runCtx, state, supp);
437
- }
167
+ yield {
168
+ type: 'warning',
169
+ message: `↻ 续跑工作流「${script.name}」(上次已完成 ${journal.loadedCount} 次调用` +
170
+ `${resume.lastError ? `,上次失败于:${resume.lastError}` : ''})。`,
171
+ };
438
172
  }
439
- // resume:同任务中断后重跑 —— 回放已完成步的 capture 变量,从 startFrom 起跑。
440
- // ⚠️ 仅回放 VarStack 变量,不恢复 TAOR history(plan.yaml 全程用 capture 变量串联故可用;
441
- // 依赖隐式 history 的工作流 resume 后上下文较薄,见 types.ts 限制说明)。
442
- let startFrom = 0;
443
- if (resume.mode === 'resume' && ((resume.startFrom ?? 0) > 0 || resume.loopResume)) {
444
- startFrom = resume.startFrom ?? 0;
445
- if (resume.capturedVars && resume.completedStepIds) {
446
- for (const sid of resume.completedStepIds) {
447
- const caps = resume.capturedVars[sid];
448
- if (caps)
449
- for (const [k, v] of Object.entries(caps))
450
- vars.setGlobal(k, v);
173
+ const queue = new EventQueue();
174
+ const workflowContext = createWorkflowContext({
175
+ ctx,
176
+ state,
177
+ journal,
178
+ emit: (ev) => queue.push(ev),
179
+ args: inputs,
180
+ workflowName: script.name,
181
+ runId: opts.runId,
182
+ parentRunId: opts.parentRunId,
183
+ depth: opts.depth,
184
+ session: sessionId,
185
+ task: state.taskId,
186
+ workflowDir: script.dir,
187
+ inputResourceMap: ctx.inputResourceMap,
188
+ scope: opts.scope,
189
+ invokeWorkflow: async (name, rawInputs, site, label) => {
190
+ const bundle = opts.bundle.children.get(name);
191
+ if (!bundle)
192
+ throw new Error(`子工作流 ${name} 未声明在 meta.dependencies 中`);
193
+ const bound = await bindResourceInputs({
194
+ workflowName: name,
195
+ sessionId,
196
+ cwd: effectiveWorkflowCwd(ctx),
197
+ rawInputs: rawInputs,
198
+ defs: bundle.script.meta.inputs,
199
+ attachments: [],
200
+ });
201
+ const childInputs = coerceInputs(bound.inputs, bundle.script.meta.inputs);
202
+ if (bound.resourceIdentity.length)
203
+ childInputs.__resource_fingerprint = bound.resourceIdentity.join('|');
204
+ const taskId = fingerprint('child-instance-v1', {
205
+ parent: [script.name, state.taskId],
206
+ site,
207
+ name,
208
+ inputs: childInputs,
209
+ });
210
+ const childRunId = fingerprint('child-run-v2', {
211
+ rootRunId: opts.scope.rootRunId,
212
+ parentRunId: opts.runId,
213
+ site,
214
+ taskId,
215
+ });
216
+ const childCtx = {
217
+ ...ctx,
218
+ cwd: effectiveWorkflowCwd(ctx),
219
+ history: structuredClone(ctx.history.filter((m) => m.role === 'system')),
220
+ sessionState: ctx.sessionState
221
+ ? structuredClone(ctx.sessionState)
222
+ : undefined,
223
+ goal: undefined,
224
+ goalAnchor: undefined,
225
+ goalSupplements: undefined,
226
+ declaredArtifacts: undefined,
227
+ transientContext: undefined,
228
+ transientBundle: undefined,
229
+ managedExecution: undefined,
230
+ managedBridge: undefined,
231
+ usageRecorder: ctx.usageRecorder ??
232
+ ctx.managedBridge?.recordUsage?.bind(ctx.managedBridge),
233
+ inputResources: Object.values(bound.resourceMap).flat(),
234
+ inputResourceMap: bound.resourceMap,
235
+ };
236
+ const prefix = `${script.name} → ${label ?? name}`;
237
+ await state.appendProgress(`▶ 子流程 ${prefix} (${site}, ${taskId})`);
238
+ const run = executeWorkflowScript(bundle.script, {
239
+ ctx: childCtx,
240
+ inputs: childInputs,
241
+ sessionId,
242
+ bundle,
243
+ scope: opts.scope,
244
+ parentStateDir: state.dir,
245
+ taskId,
246
+ runId: childRunId,
247
+ parentRunId: opts.runId,
248
+ depth: opts.depth + 1,
249
+ });
250
+ try {
251
+ while (true) {
252
+ const next = await run.next();
253
+ if (next.done) {
254
+ if (!next.value)
255
+ throw new Error(`子工作流 ${name} 未返回执行结果`);
256
+ const { reused, ...value } = next.value;
257
+ await state.appendProgress(`✓ 子流程 ${prefix} (${reused ? '复用' : '执行完成'})`);
258
+ return { value, reused };
259
+ }
260
+ const ev = next.value;
261
+ if (ev.type === 'plugin_progress' ||
262
+ ev.type === 'warning' ||
263
+ ev.type === 'text' ||
264
+ ev.type === 'assistant_message') {
265
+ const message = ev.type === 'text'
266
+ ? ev.delta
267
+ : ev.type === 'assistant_message'
268
+ ? typeof ev.message.content === 'string'
269
+ ? ev.message.content
270
+ : JSON.stringify(ev.message.content)
271
+ : ev.message;
272
+ queue.push({
273
+ type: 'plugin_progress',
274
+ pluginId: WF_PLUGIN_ID,
275
+ message: `[${prefix}] ${message}`,
276
+ });
277
+ }
278
+ else if (ev.type !== 'turn_done' &&
279
+ ev.type !== 'interrupted' &&
280
+ ev.type !== 'error')
281
+ queue.push(ev);
282
+ }
451
283
  }
452
- }
453
- // 循环中断恢复:回放中断时刻的最近 vars 快照(含 setGlobal 的教训/裁决与
454
- // 迭代内已完成步的 capture)。inputs 不回放——goal 承载键已由上方仲裁逻辑处理。
455
- if (resume.loopResume?.lastVarsSnapshot) {
456
- for (const [k, v] of Object.entries(resume.loopResume.lastVarsSnapshot)) {
457
- if (k === 'inputs')
458
- continue;
459
- vars.setGlobal(k, v);
284
+ catch (error) {
285
+ await state.appendProgress(`✗ 子流程 ${prefix}: ${String(error)}`);
286
+ throw error;
460
287
  }
461
- }
462
- const loopNote = resume.loopResume
463
- ? `,循环 ${resume.loopResume.loopId} 恢复到第 ${resume.loopResume.iteration + 1} 轮迭代`
464
- : '';
465
- const msg = `♻ 检测到同名未完成任务,从第 ${startFrom + 1} 步续跑(已回放 ${resume.completedStepIds?.length ?? 0} 个完成步变量${loopNote})。要全新开始请先 /new。`;
466
- yield { type: 'warning', message: msg };
288
+ },
289
+ });
290
+ if (resume.supplementaryGoal) {
291
+ await workflowContext.state.addGoalSupplement(resume.supplementaryGoal);
467
292
  }
468
- // P1 TUI 可见:开工时给 LiveArea 状态行一条 plugin_progress(startFrom/total)。
469
- yield {
470
- type: 'plugin_progress',
471
- pluginId: WF_PLUGIN_ID,
472
- current: startFrom,
473
- max: def.steps.length,
474
- message: `▶ workflow ${def.name}`,
293
+ const startEvent = {
294
+ type: 'workflow_progress',
295
+ workflow: script.name,
296
+ runId: opts.runId,
297
+ rootRunId: opts.scope.rootRunId,
298
+ ...(opts.parentRunId ? { parentRunId: opts.parentRunId } : {}),
299
+ depth: opts.depth,
300
+ phase: 'workflow',
301
+ status: 'start',
302
+ message: `开始 ${script.name}`,
475
303
  };
476
- let succeeded = false;
477
- try {
478
- const flow = yield* runSteps(def.steps, vars, runCtx, state, { name: (id) => id, descendIntoLoop: true }, startFrom, resume.mode === 'resume' ? resume.loopResume : undefined);
479
- // 'halted' → 失败/中断(error/interrupted 已 yield),保留现场不归档供排查与 resume;
480
- // 'completed' / 信号对象(顶层游离 break/continue)→ 正常完成。
481
- if (flow === 'halted')
482
- return;
483
- const missingArtifacts = declaredArtifacts.filter((a) => !existsSync(resolve(runCtx.cwd, a)));
484
- if (missingArtifacts.length > 0) {
485
- const why = `工作流声明的产物缺失:${missingArtifacts.join('、')}` +
486
- `——所有步骤执行完毕但目标产物未落盘,不标记完成(保留现场供排查/续作)`;
487
- await state.recordFailure('<finalize>', why);
488
- yield { type: 'workflow_step_end', id: '<finalize>', ok: false, error: why };
489
- yield { type: 'error', error: `Workflow halted: ${why}` };
304
+ await state.appendEvent(startEvent);
305
+ yield startEvent;
306
+ let failure = null;
307
+ let scriptResult;
308
+ const done = runScript(script, workflowContext, scriptHash)
309
+ .then(async (result) => {
310
+ ctx.signal?.throwIfAborted();
311
+ scriptResult = await state.writeResult(result);
312
+ })
313
+ .catch((e) => {
314
+ failure = e;
315
+ })
316
+ .finally(() => queue.close());
317
+ for await (const ev of queue.drain())
318
+ yield ev;
319
+ await done;
320
+ if (failure) {
321
+ // 中断(用户 ESC)与脚本出错语义完全不同:前者保留现场可 resume。
322
+ if (ctx.signal?.aborted ||
323
+ (failure instanceof Error && failure.name === 'AbortError')) {
324
+ await state.recordFailure('用户中断', [...(ctx.declaredArtifacts ?? [])]);
325
+ await state.appendProgress('■ 用户中断');
326
+ if (opts.parentStateDir)
327
+ throw Object.assign(new Error('用户中断'), { name: 'AbortError' });
328
+ const interruptedEvent = {
329
+ type: 'workflow_progress',
330
+ workflow: script.name,
331
+ runId: opts.runId,
332
+ rootRunId: opts.scope.rootRunId,
333
+ depth: opts.depth,
334
+ phase: 'workflow',
335
+ status: 'warning',
336
+ message: '用户中断,现场已保留',
337
+ };
338
+ await state.appendEvent(interruptedEvent);
339
+ yield interruptedEvent;
340
+ await ctx.managedBridge?.finish({
341
+ status: 'stopped',
342
+ artifacts: [...(ctx.declaredArtifacts ?? [])],
343
+ blocker: '用户停止,现场已保留',
344
+ });
345
+ yield { type: 'interrupted' };
490
346
  return;
491
347
  }
492
- // 完成态也给一条 plugin_progress(放在 workflow_done 之前,保证
493
- // workflow_done 仍是事件流最后一条——e2e 测试依赖 events[last]===workflow_done)。
494
- yield {
495
- type: 'plugin_progress',
496
- pluginId: WF_PLUGIN_ID,
497
- current: def.steps.length,
498
- max: def.steps.length,
499
- message: `✓ workflow ${def.name} 完成`,
348
+ const message = failure instanceof WorkflowScriptError
349
+ ? failure.message
350
+ : `工作流「${script.name}」执行失败:${failure instanceof Error ? failure.message : String(failure)}`;
351
+ // 工作流可以在 Error 上附带通用的 workflowOutcome。Runner 只透传这个
352
+ // 生命周期标签,不解释其业务含义。
353
+ const workflowOutcome = workflowOutcomeFromFailure(failure);
354
+ await state.recordFailure(message, [...(ctx.declaredArtifacts ?? [])]);
355
+ await state.appendProgress(`■ 失败:${message}`);
356
+ if (opts.parentStateDir)
357
+ throw failure;
358
+ const failureEvent = {
359
+ type: 'workflow_progress',
360
+ workflow: script.name,
361
+ runId: opts.runId,
362
+ rootRunId: opts.scope.rootRunId,
363
+ ...(opts.parentRunId ? { parentRunId: opts.parentRunId } : {}),
364
+ depth: opts.depth,
365
+ phase: 'workflow',
366
+ status: 'warning',
367
+ message,
500
368
  };
501
- // v0.9 重构:不再需要 assistant_message 摘要注入。
502
- // agent/llm/skill 步已通过 runQuery(TAOR) 直接写入共享 ctx.history,
503
- // 每步的完整对话上下文(user→assistant→tool→...)都自然积累在 history 中。
504
- // 后续对话和用户调整都能看到完整的工作流执行过程。
505
- await state.markDone();
506
- yield { type: 'workflow_done', name: def.name, vars: vars.snapshot() };
507
- succeeded = true;
369
+ await state.appendEvent(failureEvent);
370
+ yield failureEvent;
371
+ await ctx.managedBridge?.finish({
372
+ status: 'failed',
373
+ artifacts: [...(ctx.declaredArtifacts ?? [])],
374
+ blocker: message,
375
+ ...(workflowOutcome ? { outcome: workflowOutcome } : {}),
376
+ });
377
+ yield { type: 'error', error: message };
378
+ return;
508
379
  }
509
- finally {
510
- // 成功时归档(保留 vars.json/progress.md/checkpoints 供后续回溯);失败/中断时保留原状供排查与 resume
511
- if (succeeded)
512
- await state.archive();
380
+ // 把运行时经 declareArtifact() 登记的产物一并落盘 —— 只写 meta.artifacts
381
+ // 的话,循环体里的逐迭代产物会静默丢失(见 markDone 的注释)。
382
+ await state.markDone([...(ctx.declaredArtifacts ?? [])]);
383
+ await state.appendProgress(`✓ 完成(重放 ${journal.replayedCount} 次 / 上次账本 ${journal.loadedCount} 条)`);
384
+ const completeEvent = {
385
+ type: 'workflow_progress',
386
+ workflow: script.name,
387
+ runId: opts.runId,
388
+ rootRunId: opts.scope.rootRunId,
389
+ ...(opts.parentRunId ? { parentRunId: opts.parentRunId } : {}),
390
+ depth: opts.depth,
391
+ phase: 'workflow',
392
+ status: 'complete',
393
+ message: '执行完成',
394
+ };
395
+ await state.appendEvent(completeEvent);
396
+ yield completeEvent;
397
+ await state.archive();
398
+ const artifacts = (await state.allArtifacts()).map((p) => resolve(state.workingDir, p));
399
+ if (opts.parentStateDir)
400
+ return {
401
+ workflow: script.name,
402
+ runId: opts.runId,
403
+ result: scriptResult,
404
+ artifacts,
405
+ reused: false,
406
+ };
407
+ const result = scriptResult && typeof scriptResult === 'object'
408
+ ? scriptResult
409
+ : undefined;
410
+ const evidence = Array.isArray(result?.evidence)
411
+ ? result.evidence.map((item) => String(item))
412
+ : [];
413
+ const finished = await ctx.managedBridge?.finish({
414
+ status: 'verified',
415
+ artifacts,
416
+ evidence,
417
+ });
418
+ // ⚠️ 收尾状态必须过进程边界。传进去 'verified' 拿回来的很可能是
419
+ // 'ready_for_acceptance' —— 机器自检通过但还没人验收,这两者对宿主是天差地别。
420
+ // 就地丢弃会让 `-p --output-format json` 的宿主把「等待验收」读成「办完了」。
421
+ yield { type: 'turn_done', ...managedStatusOf(finished) };
422
+ return {
423
+ workflow: script.name,
424
+ runId: opts.runId,
425
+ result: scriptResult,
426
+ artifacts,
427
+ reused: false,
428
+ };
429
+ }
430
+ /**
431
+ * Read the structured outcome after the ESM loader has wrapped a workflow
432
+ * exception. The wrapper is deliberately treated as an implementation detail:
433
+ * both its own field and a bounded `cause` chain are accepted so the
434
+ * workflow → loader → runner path cannot silently downgrade budget_limited to
435
+ * a generic failed result.
436
+ */
437
+ export function workflowOutcomeFromFailure(failure) {
438
+ let current = failure;
439
+ for (let depth = 0; depth < 8 && current && typeof current === 'object'; depth++) {
440
+ const outcome = current.workflowOutcome;
441
+ if (typeof outcome === 'string' && outcome.trim())
442
+ return outcome.trim();
443
+ current = current.cause;
513
444
  }
445
+ return undefined;
446
+ }
447
+ /** 从 bridge.finish 的返回值里取生命周期状态;拿不到就不带这个字段。 */
448
+ function managedStatusOf(finished) {
449
+ const status = finished?.status;
450
+ return typeof status === 'string' ? { managedStatus: status } : {};
514
451
  }