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
@@ -0,0 +1,779 @@
1
+ # 结构模式 —— 把守卫放在该放的地方
2
+
3
+ 这份文档解决的不是「怎么调 API」,而是**「复杂任务该怎么搭结构」**。
4
+
5
+ ## 0. 先理解这个架构为什么不像 LangChain 那样脆
6
+
7
+ LangChain / 低码平台的脆是**结构性**的:节点边界就是失败终止的地方,
8
+ 那里根本没有放处理器的位置。这里不一样,有两层网:
9
+
10
+ **第一层(免费,你什么都不用写)**:`wf.model.agent()` 内部是 TAOR 循环,
11
+ 工具失败**从不抛异常** —— 内核把 `{ok:false, error}` 转成一条 `tool` 消息回填历史,
12
+ 下一轮模型就看得见并自己改路。实测:某次 agent 试 `Edit` 被权限拒绝,
13
+ 它自己推断出「当前宿主只授权了只读类工具」然后换用 `Read/Grep/Glob` 继续。
14
+
15
+ **第二层(要你写)**:`wf.tools.call()` 失败会抛、`wf.model.agent()` 可能烂尾。
16
+ 这一层的网是你的 `try/catch` 和核实检查。**忘了写就没有。**
17
+
18
+ > 一句话:**适应性是免费的,可靠性是你写出来的。**
19
+
20
+ ---
21
+
22
+ ## 1. 旋钮:这一步用 `wf.tools.call()` 还是 `wf.model.agent()`
23
+
24
+ | | `wf.tools.call()` | `wf.model.agent()` |
25
+ |---|---|---|
26
+ | 语义 | 确定性执行 | 把判断权交给模型 |
27
+ | 失败 | **抛异常**,你必须 catch | 模型自己看到错误并改路 |
28
+ | 适合 | 路径明确的动作:写文件、跑脚本、转格式 | 开放任务:调研、整理、"看情况处理" |
29
+ | 代价 | 不会随机应变 | 慢、贵、可能烂尾 |
30
+
31
+ **默认选 `wf.tools.call()`。** 只有当「怎么做得看现场」时才升级到 `wf.model.agent()` ——
32
+ `wf.model.agent()` 是你花钱买的适应性,不是默认档位。
33
+
34
+ ```javascript
35
+ // ✅ 路径明确 → tool
36
+ await wf.tools.call('Write', { file_path: OUT, content: text })
37
+
38
+ // ✅ 开放任务 → agent
39
+ const notes = await wf.model.agent('调研这个主题,整理成 markdown 写到 notes.md', {
40
+ allowedTools: ['Read', 'Write', 'Grep', 'Glob'],
41
+ })
42
+ ```
43
+
44
+ `allowedTools` 是给 `wf.model.agent()` 收窄注意力的手段,不是安全边界 ——
45
+ 弱模型工具一多就容易乱选。只读探索就只给只读工具。
46
+
47
+ ---
48
+
49
+ ## 2. 脊梁:必需后置条件可观察 + 有界恢复
50
+
51
+ 先为阶段写清楚必需后置条件,再选择与它匹配的证据和恢复方式。文件落盘用 `exists/verify`,
52
+ 结构化判断用 `schema`,状态协议用精确令牌,外部应用成果用真实打开/渲染检查。只有文件缺失且再次
53
+ 提醒确实可能解决时才使用 nudge;不要给纯分析、分类和可选探索机械套写文件循环。
54
+
55
+ ⚠️ **核实要查产物本身,不要查模型的话**:
56
+
57
+ ```javascript
58
+ // ❌ 错:模型说做完了就信
59
+ if (out.includes('完成')) { /* … */ }
60
+
61
+ // ✅ 对:查磁盘
62
+ if (!(await wf.fs.exists(OUT))) { /* nudge */ }
63
+ ```
64
+
65
+ 复杂产物(pptx/docx/pdf)要**忠实校验**,不能只测「某个库能打开」——
66
+ 真正的判据是「目标程序能干净打开」。
67
+
68
+ ---
69
+
70
+ ## 3. 降级不阻断(对应 yaml 的 `onError: continue`)
71
+
72
+ 某一步失败不该拖垮整条工作流时,给它一个占位值继续:
73
+
74
+ ```javascript
75
+ let research = '(探索降级,按目标与常识推断)'
76
+ try {
77
+ research = await wf.model.agent('探索代码库…', { allowedTools: ['Read', 'Grep', 'Glob'] })
78
+ } catch (e) {
79
+ wf.output.progress(`探索阶段降级(不阻断):${e.message}`)
80
+ }
81
+ ```
82
+
83
+ **占位值要写成人话**,因为它会被拼进下游的 prompt —— 下游模型看到
84
+ 「(探索降级)」就知道该自己推断,看到空字符串则会以为探索结果真的是空。
85
+
86
+ ### 3.1 组件级部分交付
87
+
88
+ 不要把复合成果压成一个布尔值。主体可用、附件缺失时,应保留主体并把缺失组件结构化传给下游:
89
+
90
+ ```javascript
91
+ return {
92
+ completeness: 'partial',
93
+ components: {
94
+ body: { status: 'complete', path: BODY },
95
+ attachment: {
96
+ status: 'source_limited',
97
+ reason: '原始输入没有包含该附件',
98
+ },
99
+ },
100
+ }
101
+ ```
102
+
103
+ 下游只为 `attachment` 放占位或请求补充,不能丢弃 `body`。如果缺的是主体必需输入,
104
+ 则诚实停止或生成明确的整项占位。`source_limited`、`processing_failed`、`ambiguous`
105
+ 要分开:前者重试无收益,后两者才可能通过换输入、修定位或补证据恢复。
106
+
107
+ ---
108
+
109
+ ## 4. 就地自愈(对应 yaml 的 `onError: recover`)
110
+
111
+ 比降级更进一步:失败时起一个 agent,**把真实错误给它看**,让它想办法把结果做出来。
112
+
113
+ ```javascript
114
+ let count
115
+ try {
116
+ const r = await wf.exec.shell(`wc -l ${NOTES}`)
117
+ if (r.exitCode !== 0) throw new Error(r.stderr)
118
+ count = r.stdout.trim()
119
+ } catch (e) {
120
+ wf.output.progress(`统计失败,起 agent 自愈:${e.message}`)
121
+ count = await wf.model.agent(
122
+ `执行 \`wc -l ${NOTES}\` 失败了:${e.message}\n` +
123
+ '请先用 Glob/Read 定位文件真实位置,再统计行数。',
124
+ { label: 'recover-count', allowedTools: ['Read', 'Glob', 'Bash'] },
125
+ )
126
+ }
127
+ ```
128
+
129
+ **关键是把 `e.message` 原样给它** —— 恢复 agent 最需要的信息就是「到底错在哪」。
130
+ 只说「上一步失败了」等于让它从零猜。
131
+
132
+ 恢复前再问一句:**下一轮与上一轮相比,什么会改变?** 如果答案是“什么都不变”,就不要重试。
133
+ 结构格式错误交给 `schema` 的有界修复;坐标或范围错误要按缺失方向改输入;工具路径错误要先定位
134
+ 真实路径;原始输入没有信息则记录部分结果并结束该组件。重复同一图片、同一框和笼统提示不是自愈。
135
+
136
+ ---
137
+
138
+ ## 5. 打回 vs 只许向前(带标签的 continue / break)
139
+
140
+ 多阶段工作流常有两种"回退",语义完全不同,别混:
141
+
142
+ ```javascript
143
+ planLoop: for (let round = 1; round <= 3; round++) {
144
+ // …规划…
145
+ for (let a = 1; a <= 5; a++) { // 审批小循环
146
+ const fb = await wf.flow.ask('请审批', [/* … */])
147
+ if (isFundamental(fb)) {
148
+ learnings.push(fb)
149
+ continue planLoop // ← 方向性变化:回到规划阶段重来
150
+ }
151
+ // …小改就地修订,本小循环下一轮再确认…
152
+ }
153
+
154
+ for (let ex = 1; ex <= 3; ex++) { // 执行闭环
155
+ await wf.model.agent('执行计划…')
156
+ const ok = await wf.check.verify(CHECKS)
157
+ if (ok.passed) break planLoop // ← 验收通过:直达收尾
158
+ // ⚠️ 不合格**只重执行**,绝不回规划阶段
159
+ }
160
+ throw new Error('执行 3 轮仍未通过验收,诚实停止')
161
+ }
162
+ ```
163
+
164
+ **一条实战裁决**:用户批准过的计划**只许向前**。验收不合格时带着阻塞原因重新执行,
165
+ 不要打回规划、不要重新审批 —— 那会让用户反复看到同一个审批闸门,被误读成死循环。
166
+
167
+ ---
168
+
169
+ ## 6. 扇出 —— 用 `wf.flow.pipeline()`,**不要**裸 `Promise.all`
170
+
171
+ ```javascript
172
+ const batch = await wf.flow.pipeline(files, {
173
+ label: '逐文件审计',
174
+ concurrency: 6,
175
+ errorMode: 'collect',
176
+ stages: [async (_previous, file) =>
177
+ wf.model.agent(`审计 ${file}`, { label: file })],
178
+ })
179
+ const results = batch.results
180
+ .filter((item) => item.ok)
181
+ .map((item) => item.value)
182
+ ```
183
+
184
+ ⚠️ **这一节以前给的范例是 `Promise.all(files.map(f => wf.model.agent(...)))`,那是错的。**
185
+ 账本按「**车道内序号** + 内容指纹」定位断点。裸 `Promise.all` 让所有 agent 挤在
186
+ **同一条车道**上并发记账 —— 序号取决于谁先返回,也就是取决于 LLM 延迟。同一个脚本
187
+ 两次运行的序号分配可能完全不同 → 首个失配 → 其后全部实跑,**断点续跑静默失效**。
188
+
189
+ **最阴的地方是它测不出来**:扇出 N ≤ 8(并发闸门)时所有项同时起跑、完成顺序恰好
190
+ 稳定,小规模用例恒绿。等真实任务扇出 12 项才炸,而那时你已经不在改这段代码了。
191
+ 引擎检测到同车道并发会发一次 warning(文案里有「断点续跑随之失效」),看到它就是
192
+ 在提示你改用 `pipeline`。
193
+
194
+ `pipeline` 给每一项一条确定的车道(`<父车道>/p<第几个>i<第几项>`),车道内串行、
195
+ 序号与调度无关,**扇出与 resume 因此可以兼得**;嵌套 pipeline 同样成立。
196
+
197
+ **多阶段无屏障**:`wf.flow.pipeline(items, { stages: [阶段1, 阶段2] })` 里每项独立走完全部阶段 ——
198
+ A 项可以在阶段 2 而 B 项还在阶段 1,不必等齐。阶段回调签名是
199
+ `(上一阶段结果, 原始项, 下标)`,所以后续阶段拿得到原始项,不用把上下文串进返回值。
200
+
201
+ **需要屏障时不必找别的原语**:`pipeline` 本身就是 await 全部完成后返回数组,
202
+ 单阶段的 `wf.flow.pipeline(items, { stages: [阶段] })` 就是「全部跑完再往下」。真正需要屏障的只有
203
+ 「下一步要看全部结果」的场景(跨项去重、汇总、零结果早退)。
204
+
205
+ **失败语义必须显式选择**:`errorMode:'collect'` 会把失败项保留为
206
+ `{ok:false,index,error:{name,message,code}}` 并继续其它项;`fail-fast` 停止调度尚未开始项、
207
+ 等待已开始项退出后抛 `AggregateError`。返回顺序恒等于输入顺序,不能再用 `null` 猜失败原因。
208
+
209
+ ---
210
+
211
+ ## 7. loop-until-dry(未知规模的发现类任务)
212
+
213
+ 固定轮数会漏掉尾巴。改成「连续 K 轮没有新东西才停」:
214
+
215
+ ```javascript
216
+ const seen = new Set()
217
+ let dry = 0
218
+ for (let round = 1; round <= 10 && dry < 2; round++) {
219
+ wf.state.resetModelContext()
220
+ const found = await wf.model.agent(`找出还没被发现的问题。已知:${[...seen].join('、')}`)
221
+ const fresh = extractItems(found).filter((x) => !seen.has(x))
222
+ if (fresh.length === 0) { dry++; continue }
223
+ dry = 0
224
+ for (const x of fresh) seen.add(x)
225
+ }
226
+ ```
227
+
228
+ **去重要对 `seen`(全部见过的),不是对"已确认的"** —— 否则被否掉的项每轮都会重新出现,永远不收敛。
229
+
230
+ ---
231
+
232
+ ## 8. 验收员与执行者分离
233
+
234
+ 同一个模型评自己的产出会偏袒。让验收走**独立的一次调用**,并明确要求它**亲自核验**:
235
+
236
+ ```javascript
237
+ const assess = await wf.model.agent(
238
+ `你是独立验收员。不要轻信执行 agent 的自述,也不要把"写了一部分"当完成。
239
+ 用 Bash/Read/Glob **亲自核实**产物真实存在、满足每条成功标准。
240
+ 只输出 JSON:{"qualified":bool,"blocking_reason":string,"summary":string}`,
241
+ { allowedTools: ['Read', 'Bash', 'Glob'], contextFiles: [{ path: PLAN, hint: '成功标准' }] },
242
+ )
243
+ const v = parseJson(assess) ?? { qualified: false, blocking_reason: '验收本身失败' }
244
+ ```
245
+
246
+ ⚠️ **上面这段是旧写法,现在应该传 `schema:`**(见第 18 节):
247
+
248
+ ```javascript
249
+ const v = await wf.model.agent(`你是独立验收员。……`, {
250
+ allowedTools: ['Read', 'Bash', 'Glob'],
251
+ contextFiles: [{ path: PLAN, hint: '成功标准' }],
252
+ schema: {
253
+ type: 'object',
254
+ required: ['qualified', 'blocking_reason', 'summary'],
255
+ properties: {
256
+ qualified: { type: 'boolean' },
257
+ blocking_reason: { type: 'string' },
258
+ summary: { type: 'string' },
259
+ },
260
+ },
261
+ })
262
+ ```
263
+
264
+ 这一节以前写着「**不要依赖 provider 的 json_schema** —— 弱 provider 不支持时整条会挂掉」。
265
+ 那条顾虑是真的,但 `schema:` 不等于 provider 原生约束,它是**三层**:
266
+
267
+ 1. provider 原生 `response_format`(**仅零工具调用时**开;Anthropic 格式直接跳过)
268
+ 2. 引擎侧抽 JSON + 按 schema 校验(手写子集,不认识的关键字忽略)
269
+ 3. 不合格就把**逐条违规**回喂模型做有界修复(2 轮)
270
+
271
+ 所以第 1 层没有也不会挂 —— 2、3 层是可移植的地板。这正是「弱 provider 也能用结构化输出」
272
+ 的实现方式,不是绕过它。
273
+
274
+ ⚠️ 但 `wf.model.agent()` 的工具循环**不能**开原生约束(`response_format` 要求每条 assistant
275
+ 消息都是 JSON,与 `tool_calls` 互斥)—— 引擎的做法是「带工具干完活,再零工具整形」,
276
+ 你不用管,但要知道带工具的 `schema` 会多一次调用。
277
+
278
+ ### ⚠️ 从「解析失败就降级」的旧封装迁过来时,**别忘了包 try/catch**
279
+
280
+ 这是 `plan` 迁移时踩到的:旧的 `askJson(prompt, opts, fallback)` 语义是
281
+ 「解析不出来就返回 fallback,**不抛**」,调用点靠这个降级继续
282
+ (分类判不出来当 minor、验收判不出来当不合格)。而 `schema` **修完两轮仍不合格会抛**。
283
+
284
+ ```javascript
285
+ // ❌ 直接把 parseJson 换成 schema —— 一次分类失败就让整条工作流崩在审批闸门上
286
+ return await wf.model.agent(prompt, { ...opts, schema })
287
+
288
+ // ✅ 失败语义要自己接回来
289
+ async function askJson(prompt, opts, fallback) {
290
+ try {
291
+ return await wf.model.agent(prompt, opts) // opts.schema 由调用点给
292
+ } catch (e) {
293
+ wf.output.progress(`结构化输出失败,降级到默认值:${e.message}`)
294
+ return fallback
295
+ }
296
+ }
297
+ ```
298
+
299
+ **先问自己这个调用点该不该抛**:产出物类的(写文件、给结论)该抛,
300
+ 让阶段契约定义的有界恢复接住;分类/判定类的往往需要一个「破坏最小的默认值」
301
+ 继续走 —— 后者才需要这层 try/catch。
302
+
303
+ ### `enum` 挡的是「分支被静默走错」
304
+
305
+ 只要下游有 `if (x.category === 'fundamental')` 这样的分支,就给它 `enum`:
306
+
307
+ ```javascript
308
+ properties: { category: { enum: ['minor', 'fundamental'] } }
309
+ ```
310
+
311
+ 没有 enum 时模型吐 `"Fundamental"` / `"方向性"` / `"major"`,代码会**静默落进
312
+ else 分支**。`plan` 里这意味着用户提了方向性意见却被当成小改就地改两句 ——
313
+ 这类错不报警、不留痕,只是结果不对。
314
+
315
+ ---
316
+
317
+ ## 9. HITL 闸门放哪
318
+
319
+ `wf.flow.ask()` 会真停等。放的位置有讲究:
320
+
321
+ - ✅ **方案定型后、开始烧钱之前**(执行前审批)
322
+ - ✅ **不可逆动作之前**(发布、删除、提交)
323
+ - ❌ 不要放在循环体里每轮都问 —— 非交互宿主会自动选推荐项,等于没问;交互宿主则烦死人
324
+ - ❌ 不要用它替代核实。`wf.flow.ask()` 问的是「要不要继续」,`wf.check.verify()` 问的是「做成了没有」
325
+
326
+ **推荐项放第一个**并在 label 写「(推荐)」:非交互宿主自动选第一个。
327
+
328
+ ---
329
+
330
+ ## 10. 长循环记得 `wf.state.resetModelContext()`
331
+
332
+ 每轮都带着前几十轮的对话跑,模型会被旧上下文带偏,token 也线性膨胀。
333
+ 截回入口快照(含 Skills 目录,不是清空):
334
+
335
+ ```javascript
336
+ for (let i = 1; i <= MAX; i++) {
337
+ wf.state.resetModelContext()
338
+ // …本轮…
339
+ }
340
+ ```
341
+
342
+ 跨轮要保留的东西放**变量**里(`learnings` 数组之类),它们不受 `wf.state.resetModelContext()` 影响。
343
+ 别用「写文件 + 每轮读回来」的土办法 —— Read 的输出带行号和空文件标记,
344
+ 逐轮把格式垃圾累积进档案。
345
+
346
+ ---
347
+
348
+ ## 11. 大工作流拆多文件(`research` / `illustrate` 实战)
349
+
350
+ 超过两三个阶段、或 prompt 很长的工作流,**别都塞进一个 `workflow.mjs`**。
351
+ 主入口应该读起来像**流程的目录**,细节下沉:
352
+
353
+ ```
354
+ workflows/research/
355
+ ├── workflow.mjs 主入口 —— 一眼看清六个阶段、守卫在哪
356
+ ├── prompts/s0-goal.md … 九段长 prompt(改措辞不用动代码,diff 干净)
357
+ └── scripts/build-report.mjs 报告拼装(可单独跑,见下)
358
+ ```
359
+
360
+ **读 prompt 文件的标准写法**:
361
+
362
+ ```javascript
363
+ const PROMPTS = 'workflows/<名字>/prompts'
364
+
365
+ async function prompt(name, vars = {}) {
366
+ let text = await wf.fs.readText(`${PROMPTS}/${name}.md`)
367
+ if (text === null) throw new Error(`缺少 prompt:${PROMPTS}/${name}.md`)
368
+ for (const [k, v] of Object.entries({ topic: args.topic, dir: DIR, ...vars })) {
369
+ text = text.split(`{{${k}}}`).join(String(v ?? ''))
370
+ }
371
+ return text
372
+ }
373
+
374
+ await wf.model.agent(await prompt('s1-map'), { label: '阶段1', allowedTools: [...] })
375
+ ```
376
+
377
+ 用 `{{name}}` 而不是 `${name}` 做占位符:prompt 文件里常有 JS/shell 代码示例,
378
+ `${}` 会撞车。
379
+
380
+ **确定性处理抽成 `scripts/*.mjs` 的三个理由**(不是为了好看):
381
+ 1. **可移植** —— 原 yaml 里报告拼装是一大段 bash(`sed -E` / `basename` / `set -euo pipefail`),
382
+ Windows 上要靠 Git Bash 才有,宿主回落 cmd 时整段不可用。
383
+ 2. **可单独跑** —— 拼装出问题时 `node workflows/research/scripts/build-report.mjs out/research/x standard`
384
+ 一条命令复现,不用等前面五个阶段重跑一遍。真实价值:迁移 `research` 时我就是这么
385
+ 先把拼装逻辑验证完的。
386
+ 3. **注入面为零** —— 不经过 shell,用户输入拼不进命令体。
387
+
388
+ **易变的东西当数据放 `assets/`**。`illustrate` 的三家搜索引擎抽取 JS 随页面改版
389
+ 必然失效,拆成 `assets/extract-*.js` 后,加一家引擎 = 加一个 json 条目 + 一个 js 文件,
390
+ 主脚本一行不动。
391
+
392
+ ---
393
+
394
+ ## 12. 把脊梁规则收进一个 `stage()`(多阶段工作流必备)
395
+
396
+ 阶段一多,"调 agent → 核实产出 → 没有就 nudge" 会重复十几遍。收进一个函数:
397
+
398
+ ```javascript
399
+ async function stage(label, promptText, outputs, opts = {}) {
400
+ let out = await wf.model.agent(promptText, { label, maxTurns: opts.maxTurns ?? 25, ...opts })
401
+ for (let i = 1; i <= 2; i++) {
402
+ const missing = []
403
+ for (const f of outputs) if (!(await wf.fs.exists(f))) missing.push(f)
404
+ if (missing.length === 0) break
405
+ wf.output.progress(`${label}:还缺 ${missing.length} 个文件,第 ${i} 次提醒`)
406
+ out = await wf.model.agent(
407
+ `以下文件还没有生成:\n${missing.map((m) => `- ${m}`).join('\n')}\n\n` +
408
+ '请**现在就调用 Write 工具**把它们写出来,不要只描述你打算做什么。',
409
+ { label: `${label}·nudge-${i}`, allowedTools: opts.allowedTools, maxTurns: 12 },
410
+ )
411
+ }
412
+ const still = []
413
+ for (const f of outputs) if (!(await wf.fs.exists(f))) still.push(f)
414
+ if (still.length > 0) throw new Error(`${label} 未能产出:${still.join('、')}`)
415
+ for (const f of outputs) wf.output.artifact(f)
416
+ return out
417
+ }
418
+ ```
419
+
420
+ 之后每个阶段一行:
421
+
422
+ ```javascript
423
+ await stage('阶段1/6·测绘领域地图', await prompt('s1-map'),
424
+ [`${DIR}/02-map.md`, `${DIR}/early-exit.json`],
425
+ { allowedTools: RESEARCH_TOOLS, maxTurns: 25 })
426
+ ```
427
+
428
+ **收益不只是少写字**:产出核实与 `declareArtifact` 变成"经过这个函数就一定有",
429
+ 而不是每个阶段作者自觉。漏写一处 = 一个静默不落盘的产物。
430
+
431
+ ---
432
+
433
+ ## 13. 从 DSL 迁过来时,**把不该给模型的活收回来**
434
+
435
+ yaml 里有些 `agent:` 步不是因为该让模型做,而是因为 **DSL 表达不了**。
436
+ 迁移时要认出来并改成确定性代码。
437
+
438
+ 真实例子(`illustrate` 阶段6):yaml 里是一个 agent,让模型 Glob 出十几个
439
+ `score_*.json`、逐个 Read、心算 `relevance × quality`、排序、取前 N、剔除广告图。
440
+
441
+ JS 里这是三行:
442
+
443
+ ```javascript
444
+ const picks = scored
445
+ .filter((s) => !s.is_ad)
446
+ .sort((a, b) => b.relevance * b.quality - a.relevance * a.quality)
447
+ .slice(0, wanted)
448
+ ```
449
+
450
+ 模型做这件事会算错乘积、会漏剔广告、而且**结果不可复现**。
451
+
452
+ > **判据**:这一步的输入输出如果能写成一个纯函数,就不该用 `wf.model.agent()`。
453
+ > 需要判断力(读代码、看图、写文章、决定方案)才用模型。
454
+
455
+ ---
456
+
457
+ ## 14. 外部依赖失败时,**报真实错误而不是猜测**
458
+
459
+ 工作流里凡是「依次尝试多个来源」的地方,失败时必须把**每一家的真实报错**带出来。
460
+
461
+ 反例(`illustrate` 第一版):三家图搜都没抓到候选,报错写的是
462
+ 「可能全被墙/反爬,或抽取规则需随页面改版调整」—— 纯猜测。
463
+ 而真实原因是「WebBrowser 的 eval 需要 Developer Mode 授权」,
464
+ 照着那句猜测去查抽取规则会白费很多时间。
465
+
466
+ 正确写法:
467
+
468
+ ```javascript
469
+ const failures = []
470
+ for (const engine of engines) {
471
+ try { /* …尝试… */ }
472
+ catch (e) { failures.push(`${engine.label}:${e.message}`) }
473
+ }
474
+
475
+ if (got.length === 0) {
476
+ const detail = failures.length
477
+ ? `\n\n各家的真实报错:\n${failures.map((f) => ` · ${f}`).join('\n')}`
478
+ : ''
479
+ throw new Error(`全部来源都失败。${detail}\n\n常见原因:① … ② … ③ …`)
480
+ }
481
+ ```
482
+
483
+ **真实报错在前,可能原因在后**。前者是事实,后者是提示。
484
+ 只给提示等于让用户去猜,而猜错的成本是几十分钟。
485
+
486
+ ---
487
+
488
+ ## 15. 有外部前置条件的工作流,把它写进 README 开头
489
+
490
+ `illustrate` 需要「设置 → 浏览器」里开启 Developer Mode 并把三个搜索站点
491
+ 加进白名单,否则 `eval` 一定失败。这类**环境前置条件**不写清楚,
492
+ 用户第一次跑必然撞墙,还以为是工作流坏了。
493
+
494
+ README 里单开一节 `## ⚠️ 前置条件`,写明:要什么、为什么要、**没有时的表现是什么**。
495
+ 第三条最有用 —— 用户是靠"表现"反查到那一节的。
496
+
497
+ ---
498
+
499
+ ## 16. 给工作流写单元测试(依赖外部环境时的唯一出路)
500
+
501
+ 有些工作流依赖跑不动的东西 —— `wechat-reading-shots` 要 ComputerUse,而它目前
502
+ 只吃 macOS,Windows 上**端到端根本跑不了**。但这类工作流真正容易错的地方全在
503
+ **控制流**上,而控制流可以脱离外部环境测:给默认导出的 `run(wf)` 传入最小的
504
+ `WorkflowContext` stub。只有要验证 ESM 装载、journal 或恢复时才经过 Runner。
505
+
506
+ ```typescript
507
+ const { default: run, meta } = await import(
508
+ new URL('../../../workflows/<名字>/workflow.mjs', import.meta.url).href
509
+ )
510
+ expect(meta.apiVersion).toBe(2)
511
+
512
+ // stub 按 label 决定每次调用返回什么 —— 这就是“剧本”
513
+ const wf = {
514
+ input: { values: args, resources: {} },
515
+ runtime: { sessionId: 's', taskId: 't', goal: '' },
516
+ model: {
517
+ agent: async (_prompt, opts = {}) => {
518
+ if (opts.label?.includes('读页码')) return pageReads[i++]
519
+ if (opts.label?.includes('打开正文')) return 'OPEN=ok'
520
+ return ''
521
+ },
522
+ },
523
+ tools: { callResult: async (name, input) => { /* 记调用、按 action 返回 */ } },
524
+ output: {
525
+ message: (message) => log.messages.push(message),
526
+ artifact: (path) => log.artifacts.push(path),
527
+ },
528
+ /* 其余被工作流实际访问的分组给最小实现 */
529
+ }
530
+
531
+ const result = await run(wf)
532
+ ```
533
+
534
+ **能测到什么**(都是真实踩过的坑):
535
+
536
+ | 断言 | 防的是什么 |
537
+ |---|---|
538
+ | 模型回复带前言/围栏时参数仍能提取 | 「只回一行 JSON」这种约定**没法强制** |
539
+ | `'ok 的路子都走不通'` 不得判成成功 | 用 `includes('ok')` 而非精确取值 |
540
+ | `PAGE=112 113` 不因含子串 `12` 误判 | 同上,判据要精确 |
541
+ | 请求 N 张就拍 N 张 | 张数交给模型 = 它会数错并且说谎 |
542
+ | 撞到边界提前收工且如实报数 | 继续拍只会得到 N 张一样的图 |
543
+ | 每次调用都带 `uses:['computer']` | 漏了宿主不转授权,工具必失败 |
544
+
545
+ **给 label 起有意义的名字**不只是为了日志好看 —— stub 靠它路由,
546
+ 测试的可读性直接取决于它。
547
+
548
+ ---
549
+
550
+ ## 17. 核实不一定是查文件
551
+
552
+ 脊梁规则的本质是「**不盲信 agent 的自述**」,核实形态有三种:
553
+
554
+ | 形态 | 写法 | 适用 |
555
+ |---|---|---|
556
+ | 核实文件 | `wf.fs.exists()` / `wf.check.verify()` | agent 应当产出文件 |
557
+ | **核实协议令牌** | `text.match(/OPEN=([a-z]+)/)?.[1] === 'ok'` | agent 报告一个状态 |
558
+ | **核实结构化产出** | 传 `schema:` 让引擎校验(旧写法是 `parseJson` 后自己判字段) | agent/llm 返回 JSON |
559
+
560
+ ⚠️ 第三种现在应该用 `schema:` 而不是手写 `parseJson` —— 见第 18 节,
561
+ 手写那套的问题不在于啰嗦,在于**兜底会掩盖失败**。
562
+
563
+ ⚠️ **协议令牌一定要精确取值,不要在自由文本里找关键词。**
564
+ `includes('ok')` 会把「ok 的路子都走不通」判成成功;
565
+ `includes('12')` 会把 `PAGE=112 113` 判成到了第 12 页。两条都是实测踩出来的。
566
+
567
+ `validate.mjs` 的脊梁检查认这三种形态。早先它只认第一种,于是
568
+ `wechat-reading-shots`(守卫其实很严,只是核实的是令牌不是文件)被误报 ——
569
+ **误报比漏报更伤**:作者一旦习惯「这个警告可以忽略」,真正该看的那次也会被划过去。
570
+
571
+ ---
572
+
573
+ ## 18. 逐项独立评估 = `pipeline` + `schema` + `isolated`
574
+
575
+ 「拿 N 个东西,逐个让模型评估打分,再确定性选优」是最常见的扇出形状
576
+ (`illustrate` 的识图选图、研究类工作流的候选打分、批量质检)。
577
+ 三项能力**各自解决这个形状里的一个真实缺陷**,缺一个都留着坑:
578
+
579
+ ```javascript
580
+ const SCORE = {
581
+ type: 'object',
582
+ required: ['relevance', 'quality', 'is_ad', 'reason'],
583
+ properties: {
584
+ relevance: { type: 'number', minimum: 0, maximum: 10 },
585
+ quality: { type: 'number', minimum: 0, maximum: 10 },
586
+ is_ad: { type: 'boolean' },
587
+ reason: { type: 'string' },
588
+ },
589
+ }
590
+
591
+ const scored = (
592
+ await wf.flow.pipeline(
593
+ candidates,
594
+ // 阶段 1:评估。拿到的是**校验过的对象**,不是待解析的字符串
595
+ (cand) =>
596
+ wf.model.generate(`评估这张图……只输出 JSON 对象本身。`, {
597
+ label: `打分 #${cand.index}`,
598
+ images: [{ source: cand.path, detail: 'high' }],
599
+ schema: SCORE,
600
+ isolated: true,
601
+ }),
602
+ // 阶段 2:与原始元信息合并(纯 JS,不烧 token)
603
+ (s, cand) => ({ ...cand, ...s }),
604
+ )
605
+ ).filter(Boolean)
606
+
607
+ // 阶段 3:确定性选优 —— 排序、剔除、取前 N 都不该交给模型
608
+ const picks = scored.filter((s) => !s.is_ad)
609
+ .sort((a, b) => b.relevance * b.quality - a.relevance * a.quality)
610
+ .slice(0, wanted)
611
+ ```
612
+
613
+ 各自挡的是什么:
614
+
615
+ | 能力 | 挡掉的缺陷 |
616
+ |---|---|
617
+ | `schema` | 手写 `parseJson` + `Number(s.x ?? 0)` 兜底 —— 见下,**兜底会掩盖失败** |
618
+ | `pipeline` | 串行排队;以及裸 `Promise.all` 导致的 resume 静默失效(第 6 节) |
619
+ | `isolated` | **前项锚定后项**:共享主线时第 10 项打分能看见前 9 项的评语 |
620
+
621
+ ### ⚠️ 上了 `schema` 就**删掉**兜底,别两个都留
622
+
623
+ ```javascript
624
+ // ❌ schema 已经保证 relevance 是 0-10 的 number,这个 ?? 0 是在拆网
625
+ relevance: Number(s.relevance ?? 0)
626
+ // ✅
627
+ relevance: s.relevance
628
+ ```
629
+
630
+ `?? 0` 看着"稳健",实际是把「打分没解析出来」伪装成「这张图得 0 分」——
631
+ 它被排到末位淘汰掉,你在结果里**看不到任何异常**。schema 的价值恰恰是让这种
632
+ 失败抛出来:两轮修复(引擎会把逐条违规回喂给模型)都不合规才抛,
633
+ pipeline 接住变 `null`,`.filter(Boolean)` 之后数量对不上你就发现了。
634
+
635
+ ### ⚠️ `schema` 保证「形状对」,保证不了「模型真看见了输入」
636
+
637
+ 一个完全没收到图片的模型照样能吐出合法 JSON。带 `images` 时值得再加一条判据:
638
+
639
+ ```javascript
640
+ const subjects = new Set(results.map((r) => r.subject))
641
+ if (results.length > 1 && subjects.size === 1) {
642
+ throw new Error('不同的图给出了完全相同的描述 —— images 通道很可能没真的送到')
643
+ }
644
+ ```
645
+
646
+ ### ⚠️ 扇出的项来自 `sh` 时,**排序**,否则指纹不稳
647
+
648
+ ```javascript
649
+ // ❌ find/ls 的输出顺序不保证 —— 顺序一变每项就换了车道,resume 整片失配
650
+ const r = await wf.exec.shell(`find "${dir}" -iname '*.png'`)
651
+ // ✅
652
+ const r = await wf.exec.shell(`find "${dir}" -iname '*.png' | sort`)
653
+ ```
654
+
655
+ 确定性纪律不只管 `Date.now()`/`Math.random()`(那两个 `scriptGuard` 会拦),
656
+ **任何进入指纹的东西都得稳定**,而扇出项的顺序决定了车道分配。这条 guard 拦不住。
657
+
658
+ ### 实测过的组合边界
659
+
660
+ - `images` + `schema` **可用**:图片走消息附件通道、不需要工具,所以仍是零工具调用,
661
+ provider 原生 `response_format` 照常开。已在真实 provider(豆包 seed-2.1,OpenAI 格式)
662
+ 上跑通 10 项扇出,schema 校验与 enum 全部生效。
663
+ - 修复轮**仍然带图**(要模型看着图改分才有意义),所以一次修复的成本约等于一次原调用。
664
+ - `isolated` 与 `images` 正交:`isolated` 只换 history(只留系统提示),不碰附件通道。
665
+ - 探针 `workflows/e2e-vision-schema/` 就是锁这条组合路径的,改引擎后可以直接跑它。
666
+
667
+ ---
668
+
669
+ ## 19. Goal Runtime 管理下的自主循环(`workflows/goal/` 实战)
670
+
671
+ Goal 生命周期属于外层 `Goal Runtime`:稳定 Goal/契约版本、Attempt、暂停恢复、资源状态和人类最终
672
+ 验收都不能由 Workflow Runner 或脚本自行发明。具体循环策略属于
673
+ `workflows/goal/workflow.mjs`:契约化、执行、硬门、独立审计、停滞检测和报告都在工作流目录内实现。
674
+ 该工作流用 `managedRuntime: 'goal'` 获取通用执行租约;Runner 不理解 Goal 字段。
675
+
676
+ 下面是可按业务裁剪的循环设计要点,不是让所有工作流复制一套 Goal 生命周期:
677
+
678
+ ### ① 先把目标**契约化**,再开始循环
679
+
680
+ 自主循环的成败**在第一步就决定了**:契约就是停止条件本身。
681
+ 写得可验证,循环自己收敛;写成「代码更好」「性能优化」,循环永远判不了何时结束 ——
682
+ 要么早早误判完成,要么一直空转烧钱。
683
+
684
+ 所以阶段 0 不是「理解目标」,是**产出一份可机器验证的契约**:产出物清单 +
685
+ `hard_checks`(`wf.check.verify()` 能吃的四种格式)+ 只评估不阻塞的质量维度。
686
+ 拟不出判据时**反过来问用户**(`wf.flow.ask()`,开头一次问完,之后全程自主)。
687
+
688
+ ### ② 校验模型给的检查项,**用引擎自己的解析器**
689
+
690
+ 模型写的 `hard_checks` 经常自创前缀(`command:` / `assert:` / `exists:`)。
691
+ 不要在工作流里再写一份正则去校验 —— `wf.check.verify()` 遇到无法解析的串会**响亮抛错**,
692
+ 直接拿它当校验器,配一个有界修复循环:
693
+
694
+ ```javascript
695
+ for (let fix = 0; fix <= 2; fix++) {
696
+ try { await wf.check.verify(contract.hard_checks); break } // 能解析就行,通不通过不重要
697
+ catch (e) {
698
+ if (fix === 2) throw new Error(`检查项格式反复不合法:${e.message}`)
699
+ contract = await wf.model.generate(`${基础提示}\n⚠️ 上次的 hard_checks 无法解析:${e.message}`, { schema })
700
+ }
701
+ }
702
+ ```
703
+
704
+ 好处是判据只有一份(引擎的),不会漂移;坏处是要多跑一次 `verify`,很便宜。
705
+
706
+ ### ③ 完成判定要**两道独立的关**,别让干活的模型自己打分
707
+
708
+ ```javascript
709
+ const gate = await wf.check.verify(hardChecks) // 关 1:引擎真跑命令,不问模型
710
+ if (!gate.passed) { /* 带失败项进下一轮 */ }
711
+
712
+ const audit = await wf.model.agent(审计提示, { // 关 2:独立审计
713
+ allowedTools: ['Read','Glob','Grep','Bash'], // 只读
714
+ isolated: true, // ← 关键:看不见执行者说过什么
715
+ schema: AUDIT_SCHEMA,
716
+ })
717
+ ```
718
+
719
+ `isolated: true` 是这一关的**承重部分**。审计员一旦看得见执行者的自述,
720
+ 就会被「我已经完成了 X」带着走;隔离之后它只能自己去查文件系统。
721
+
722
+ 审计要专门查两件硬门查不出来的事:
723
+ - **为通过检查而削弱检查**(改松断言、`|| true`、`--passWithNoTests`、关键字塞注释里)
724
+ - **检查覆盖不全**(目标要三种格式,而检查只测了已实现的那一种)
725
+
726
+ ### ④ 停滞指纹同时看失败项和归一化证据
727
+
728
+ 这条最容易写错,而且错了**看起来一切正常**:
729
+
730
+ ```javascript
731
+ // ❌ 原始 summary/output 含时间、耗时等噪声,每轮都会变化
732
+ const sig = gate.summary
733
+
734
+ // ❌ 只看检查名称也会误伤:检查仍失败,但错误行、缺失项或证据可能正在收敛
735
+ const sig = failed.map(d => checkLabel(d.check)).sort().join(' | ')
736
+
737
+ // ✅ 失败项 + 去掉时间/耗时噪声后的失败证据共同组成进展指纹
738
+ const sig = evidenceFingerprint(gate, failed)
739
+ ```
740
+
741
+ `wf.check.verify()` 返回 `{ passed, summary, details: [{check, passed, output}] }`,
742
+ `check` 是结构化对象(`{type, command?, file?, pattern?, minCount?}`)。实际实现见
743
+ `workflows/goal/workflow.mjs::evidenceFingerprint`,避免参考文档与工作流分叉。
744
+
745
+ **为什么非要有卡死检测**:纯轮数上限拦不住空转 —— 目标模糊时循环会反复无效尝试,
746
+ 一路烧到撞顶才停。实测(`max_iterations=5, stall_rounds=2`)在第 3 轮就止损了。
747
+
748
+ ### ⑤ 质量评估**只报告、不阻塞**
749
+
750
+ 功能达成做门;性能 / 易用性 / 鲁棒性做报告。把开放式质量判断放进停止条件,
751
+ 循环永远不收敛(总能再挑出一条可优化的)—— 本仓库的 PPT 视觉门为此付过学费。
752
+
753
+ 在审计的 schema 里带一个嵌套的 `quality` 对象即可(校验器支持嵌套,违规消息带路径
754
+ 如 `quality.performance`),并在提示里明说「这几个分数不影响 achieved 判定,
755
+ 请诚实打分,不会因为打低分就多跑一轮」。
756
+
757
+ ### ⑥ 落盘要在抛错**之前**
758
+
759
+ 未达成时抛错是对的(让 `-p --output-format json` 的 `ok`/`stop_reason` 如实反映失败),
760
+ 但报告是排障的唯一线索:
761
+
762
+ ```javascript
763
+ await wf.tools.call('Write', { file_path: REPORT_PATH, content: ... }) // 先落盘
764
+ wf.output.artifact(REPORT_PATH)
765
+ if (status !== 'achieved') throw new Error(...) // 再抛
766
+ ```
767
+
768
+ ### ⑦ ⚠️ 含空格的 `--input` 值必须加引号(这条坑所有带贪婪首参的工作流)
769
+
770
+ ```bash
771
+ --input checks=shell:bun test test/x.ts # ❌ 截断成 `shell:bun`
772
+ --input "checks=shell:bun test test/x.ts" # ✅
773
+ ```
774
+
775
+ 引擎对「值被拆碎」有自动拼回 + 告警,但**只在贪婪首参已经走了 `--input` 时才触发**;
776
+ 首参走位置参数时命中另一条分支,散落的词被**静默**拼进首参末尾。
777
+ 更阴的是截断后的值往往**仍然合法** —— 自测时 `shell:exit 1` 被截成 `shell:exit`,
778
+ 而不带参数的 `exit` 退出码是 0,于是那道本该永远失败的硬门反而"通过"了。
779
+ 在 README 里明确写出正确写法,别指望用户猜。