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,614 @@
1
+ /**
2
+ * plugins/workflow-runner/test/apiCapabilities.test.ts —— 本轮新增的四项能力
3
+ *
4
+ * ① schema 结构化输出:校验 + 有界修复,返回对象而不是字符串
5
+ * ② pipeline 无屏障流水线:扇出与断点续跑兼得
6
+ * ③ model 按模型 id 换 provider,解析不到回落基座并告警
7
+ * ④ isolated 隔离执行:不看前文、也不污染主线 history
8
+ *
9
+ * 用 DI 注入 chat stub(不用 mock.module —— 后者全局泄漏、污染同批其它文件)。
10
+ */
11
+
12
+ import { afterEach, describe, expect, test } from 'bun:test';
13
+ import { mkdtempSync, rmSync } from 'node:fs';
14
+ import { tmpdir } from 'node:os';
15
+ import { join } from 'node:path';
16
+
17
+ import type { LoopEvent, Message } from '../../../src/plugin-sdk.js';
18
+ import { createWorkflowContext } from '../src/runtime/context.js';
19
+ import { Journal } from '../src/journal.js';
20
+ import type { RunContext } from '../src/types.js';
21
+ import { WorkflowState } from '../src/workflowState.js';
22
+
23
+ const dirs: string[] = [];
24
+ afterEach(() => {
25
+ for (const d of dirs.splice(0)) {
26
+ try {
27
+ rmSync(d, { recursive: true, force: true, maxRetries: 3 });
28
+ } catch {
29
+ /* Windows 偶发 EBUSY */
30
+ }
31
+ }
32
+ });
33
+
34
+ /** 按调用次序依次吐出预设文本的 chat stub,并记录每次收到的参数。 */
35
+ function scriptedChat(texts: string[]) {
36
+ const calls: {
37
+ responseFormat: unknown;
38
+ model: string;
39
+ messageCount: number;
40
+ maxTokens: number | null;
41
+ }[] = [];
42
+ let n = 0;
43
+ const fn = async function* (a: {
44
+ provider: { model: string; apiFormat?: string; maxTokens?: number };
45
+ messages: Message[];
46
+ responseFormat?: unknown;
47
+ }) {
48
+ calls.push({
49
+ responseFormat: a.responseFormat ?? null,
50
+ model: a.provider.model,
51
+ messageCount: a.messages.length,
52
+ maxTokens: a.provider.maxTokens ?? null,
53
+ });
54
+ yield {
55
+ type: 'text_delta' as const,
56
+ delta: texts[Math.min(n++, texts.length - 1)],
57
+ };
58
+ };
59
+ return { fn, calls };
60
+ }
61
+
62
+ async function harness(
63
+ chat: unknown,
64
+ opts: {
65
+ apiFormat?: 'openai' | 'anthropic';
66
+ supportsJsonSchema?: boolean;
67
+ supportsVision?: boolean;
68
+ } = {},
69
+ ) {
70
+ const wd = mkdtempSync(join(tmpdir(), 'wf-cap-'));
71
+ dirs.push(wd);
72
+ const state = new WorkflowState(wd, 'demo', 's1');
73
+ await state.init({ goal: '测试目标' }, 'hash-v1', []);
74
+ const journal = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
75
+ await journal.load();
76
+
77
+ const events: LoopEvent[] = [];
78
+ const history: Message[] = [{ role: 'system', content: 'sys' }];
79
+ const ctx = {
80
+ provider: {
81
+ name: 'test',
82
+ model: 'base-model',
83
+ baseURL: 'x',
84
+ apiKey: 'k',
85
+ contextWindow: 128000,
86
+ supportsVision: opts.supportsVision ?? true,
87
+ ...(opts.apiFormat ? { apiFormat: opts.apiFormat } : {}),
88
+ ...(opts.supportsJsonSchema !== undefined
89
+ ? { supportsJsonSchema: opts.supportsJsonSchema }
90
+ : {}),
91
+ },
92
+ history,
93
+ cwd: wd,
94
+ tools: [],
95
+ chat,
96
+ } as unknown as RunContext;
97
+
98
+ const globals = createWorkflowContext({
99
+ ctx,
100
+ state,
101
+ journal,
102
+ emit: (ev) => events.push(ev),
103
+ args: {},
104
+ workflowName: 'demo',
105
+ session: 's1',
106
+ task: state.taskId,
107
+ });
108
+
109
+ return { globals, events, history, journal, journalPath: state.journalPath };
110
+ }
111
+
112
+ const NUMS = {
113
+ type: 'object',
114
+ required: ['items'],
115
+ properties: {
116
+ items: { type: 'array', minItems: 1, items: { type: 'number' } },
117
+ },
118
+ } as const;
119
+
120
+ describe('① schema 结构化输出', () => {
121
+ test('★ 返回的是校验过的对象,不是字符串', async () => {
122
+ const chat = scriptedChat(['{"items":[1,2,3]}']);
123
+ const h = await harness(chat.fn);
124
+ const r = await h.globals.model.generate('给我三个数', { schema: NUMS });
125
+ expect(r).toEqual({ items: [1, 2, 3] });
126
+ });
127
+
128
+ test('★ 剥 markdown 围栏与 <think> 后再校验', async () => {
129
+ const chat = scriptedChat([
130
+ '<think>先想想</think>\n```json\n{"items":[7]}\n```',
131
+ ]);
132
+ const h = await harness(chat.fn);
133
+ expect(await h.globals.model.generate('x', { schema: NUMS })).toEqual({ items: [7] });
134
+ });
135
+
136
+ test('★ 不合格 → 把逐条违规回喂模型做有界修复', async () => {
137
+ // 第一次给错类型,第二次修对
138
+ const chat = scriptedChat(['{"items":["a"]}', '{"items":[42]}']);
139
+ const h = await harness(chat.fn);
140
+ const r = await h.globals.model.generate('x', { schema: NUMS });
141
+ expect(r).toEqual({ items: [42] });
142
+
143
+ const warn = h.events.find(
144
+ (e) =>
145
+ e.type === 'warning' && String(e.message).includes('第 1/2 次修复'),
146
+ );
147
+ expect(warn).toBeDefined();
148
+ // 违规必须点名路径与实际类型,否则模型无从改起
149
+ expect(String((warn as { message: string }).message)).toContain('items[0]');
150
+ });
151
+
152
+ test('★ 修复用尽仍不合格 → 抛错,且错误里带最后一次原始输出', async () => {
153
+ const chat = scriptedChat(['不是 JSON', '还不是', '仍然不是']);
154
+ const h = await harness(chat.fn);
155
+ await expect(h.globals.model.generate('x', { schema: NUMS })).rejects.toThrow(
156
+ /2 次修复后仍不符合 schema/,
157
+ );
158
+ });
159
+
160
+ test('★ 修复循环只记一条账,且记的是**对象**(重放不必再解析一遍)', async () => {
161
+ // 记成多条会让车道序号随修复次数漂移 —— resume 时必然错位。
162
+ // 记原始文本则会让重放路径重演一次解析失败。
163
+ const chat = scriptedChat(['{"items":["a"]}', '{"items":[9]}']);
164
+ const h = await harness(chat.fn);
165
+ const first = await h.globals.model.generate('x', { schema: NUMS });
166
+ expect(first).toEqual({ items: [9] });
167
+ expect(chat.calls.length).toBe(2); // 确实修复过一次
168
+
169
+ const reloaded = new Journal(h.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
170
+ await reloaded.load();
171
+ expect(reloaded.loadedCount).toBe(1); // 两次 chat,一条账
172
+
173
+ // 同一份 prompt + schema 重来 → 命中重放,且拿到的是对象
174
+ const chat2 = scriptedChat(['绝不该被调用']);
175
+ const h2 = await harness(chat2.fn);
176
+ // 换掉 h2 的账本为刚才那份:直接在同一个 state 目录上再跑一次更真实,
177
+ // 这里用 Journal 层验证「结果是对象」这一点就够。
178
+ const replayed = await reloaded.step(
179
+ 'llm',
180
+ {
181
+ prompt: 'x',
182
+ allowedTools: null,
183
+ uses: null,
184
+ images: null,
185
+ contextFiles: null,
186
+ maxTurns: 1,
187
+ model: null,
188
+ schema: NUMS,
189
+ nativeSchema: true,
190
+ isolated: false,
191
+ timeoutMs: null,
192
+ maxTokens: null,
193
+ },
194
+ async (): Promise<unknown> => {
195
+ throw new Error('不该实跑');
196
+ },
197
+ );
198
+ expect(replayed).toEqual({ items: [9] });
199
+ expect(chat2.calls.length).toBe(0);
200
+ });
201
+
202
+ test('★ OpenAI 格式带原生 response_format;Anthropic 格式不带(否则 chat 会抛)', async () => {
203
+ const a = scriptedChat(['{"items":[1]}']);
204
+ const ha = await harness(a.fn, { apiFormat: 'openai' });
205
+ await ha.globals.model.generate('x', { schema: NUMS });
206
+ expect(a.calls[0].responseFormat).not.toBeNull();
207
+
208
+ const b = scriptedChat(['{"items":[1]}']);
209
+ const hb = await harness(b.fn, { apiFormat: 'anthropic' });
210
+ await hb.globals.model.generate('x', { schema: NUMS }); // 不得抛
211
+ expect(b.calls[0].responseFormat).toBeNull();
212
+ });
213
+
214
+ test('显式声明不支持原生 JSON Schema 时退回引擎校验,不向端点发送 response_format', async () => {
215
+ const chat = scriptedChat(['{"items":[1]}']);
216
+ const h = await harness(chat.fn, {
217
+ apiFormat: 'openai',
218
+ supportsJsonSchema: false,
219
+ });
220
+ expect(await h.globals.model.generate('x', { schema: NUMS })).toEqual({ items: [1] });
221
+ expect(chat.calls[0].responseFormat).toBeNull();
222
+ });
223
+
224
+ test('调用方可仅对不稳定步骤关闭原生 JSON Schema,仍保留引擎校验', async () => {
225
+ const chat = scriptedChat(['{"items":[1]}']);
226
+ const h = await harness(chat.fn, { apiFormat: 'openai' });
227
+ expect(
228
+ await h.globals.model.generate('x', { schema: NUMS, nativeSchema: false }),
229
+ ).toEqual({ items: [1] });
230
+ expect(chat.calls[0].responseFormat).toBeNull();
231
+ });
232
+ });
233
+
234
+ describe('① schema —— agent() 路径(带工具跑完再整形)', () => {
235
+ test('★ agent 的工具循环里不得开原生 response_format(会与 tool_calls 互斥)', async () => {
236
+ const chat = scriptedChat(['{"items":[5]}']);
237
+ const h = await harness(chat.fn, { apiFormat: 'openai' });
238
+ const r = await h.globals.model.agent('干活并给结论', { schema: NUMS });
239
+ expect(r).toEqual({ items: [5] });
240
+ // 第一次是带工具的正式调用 —— 必须不带原生约束
241
+ expect(chat.calls[0].responseFormat).toBeNull();
242
+ });
243
+
244
+ test('★ agent 首次输出不合格 → 零工具修复轮,此时才开原生约束', async () => {
245
+ const chat = scriptedChat(['我已经完成了,结果很好', '{"items":[8]}']);
246
+ const h = await harness(chat.fn, { apiFormat: 'openai' });
247
+ expect(await h.globals.model.agent('干活', { schema: NUMS })).toEqual({
248
+ items: [8],
249
+ });
250
+ expect(chat.calls[0].responseFormat).toBeNull(); // 工具轮
251
+ expect(chat.calls[1].responseFormat).not.toBeNull(); // 修复轮
252
+ });
253
+ });
254
+
255
+ describe('② pipeline 无屏障流水线', () => {
256
+ test('★ 每项独立走完全部阶段,(prev, item, index) 都对', async () => {
257
+ const h = await harness(scriptedChat(['ignored']).fn);
258
+ const seen: string[] = [];
259
+ const out = await h.globals.flow.pipeline(
260
+ ['a', 'b', 'c'],
261
+ { stages: [(prev: unknown, item: unknown, i: number) => {
262
+ seen.push(`s1:${item}:${i}`);
263
+ return `${prev}1`;
264
+ },
265
+ (prev: unknown, item: unknown, i: number) => {
266
+ seen.push(`s2:${prev}:${item}:${i}`);
267
+ return `${prev}2`;
268
+ }] },
269
+ );
270
+ expect(out.results.map((entry: any) => entry.value)).toEqual(['a12', 'b12', 'c12']);
271
+ expect(seen).toContain('s2:b1:b:1'); // prev 是上阶段结果,item/index 仍是原始项
272
+ });
273
+
274
+ test('★ 无屏障:慢项还在第 1 阶段时,快项已经进到第 2 阶段', async () => {
275
+ const h = await harness(scriptedChat(['x']).fn);
276
+ const timeline: string[] = [];
277
+ await h.globals.flow.pipeline(
278
+ [0, 1],
279
+ { stages: [async (_p: unknown, item: number) => {
280
+ // 第 0 项慢
281
+ await new Promise((r) => setTimeout(r, item === 0 ? 60 : 1));
282
+ timeline.push(`s1-done:${item}`);
283
+ return item;
284
+ },
285
+ async (_p: unknown, item: number) => {
286
+ timeline.push(`s2-start:${item}`);
287
+ return item;
288
+ }] },
289
+ );
290
+ // 有屏障的话,s2-start:1 必然排在 s1-done:0 之后
291
+ expect(timeline.indexOf('s2-start:1')).toBeLessThan(
292
+ timeline.indexOf('s1-done:0'),
293
+ );
294
+ });
295
+
296
+ test('★ 某项抛错 → 返回结构化失败并跳过其余阶段,其它项照跑', async () => {
297
+ const h = await harness(scriptedChat(['x']).fn);
298
+ const reached: number[] = [];
299
+ const out = await h.globals.flow.pipeline(
300
+ [0, 1, 2],
301
+ { errorMode: 'collect', stages: [(_p: unknown, item: number) => {
302
+ if (item === 1) throw new Error('boom');
303
+ return item;
304
+ },
305
+ (_p: unknown, item: number) => {
306
+ reached.push(item);
307
+ return item * 10;
308
+ }] },
309
+ );
310
+ expect(out.results[0]).toMatchObject({ ok: true, value: 0 });
311
+ expect(out.results[1]).toMatchObject({ ok: false, error: { message: 'boom' } });
312
+ expect(out.results[2]).toMatchObject({ ok: true, value: 20 });
313
+ expect(reached).toEqual(expect.arrayContaining([0, 2]));
314
+ expect(reached).not.toContain(1);
315
+ expect(out.failed).toBe(1);
316
+ });
317
+
318
+ test('零阶段原样返回;非数组抛错', async () => {
319
+ const h = await harness(scriptedChat(['x']).fn);
320
+ expect(await h.globals.flow.pipeline([1, 2], { stages: [] })).toMatchObject({
321
+ succeeded: 2,
322
+ failed: 0,
323
+ });
324
+ await expect(h.globals.flow.pipeline('nope' as never, { stages: [] })).rejects.toThrow(
325
+ /必须是数组/,
326
+ );
327
+ });
328
+
329
+ test('fail-fast 停止未开始项,等待已开始项后抛 AggregateError', async () => {
330
+ const h = await harness(scriptedChat(['x']).fn);
331
+ const started: number[] = [];
332
+ await expect(
333
+ h.globals.flow.pipeline([0, 1, 2, 3], {
334
+ label: 'fail-fast-test',
335
+ concurrency: 2,
336
+ errorMode: 'fail-fast',
337
+ stages: [async (_prev: unknown, item: number) => {
338
+ started.push(item);
339
+ if (item === 0) throw new Error('first failure');
340
+ await new Promise((resolve) => setTimeout(resolve, 10));
341
+ return item;
342
+ }],
343
+ }),
344
+ ).rejects.toBeInstanceOf(AggregateError);
345
+ expect(started).not.toContain(2);
346
+ expect(started).not.toContain(3);
347
+ });
348
+
349
+ test('★ 裸 Promise.all 并发记账 → 引擎必须告警(提示改用 pipeline)', async () => {
350
+ const chat = scriptedChat(['ok']);
351
+ const h = await harness(chat.fn);
352
+ await Promise.all([h.globals.model.generate('a'), h.globals.model.generate('b')]);
353
+ expect(
354
+ h.events.some(
355
+ (e) =>
356
+ e.type === 'warning' &&
357
+ String(e.message).includes('断点续跑随之失效'),
358
+ ),
359
+ ).toBe(true);
360
+ });
361
+ });
362
+
363
+ describe('①+② 联动:pipeline 扇出后仍能断点续跑(这是整轮改动的验收点)', () => {
364
+ test('★ 12 项(> 并发上限 8)扇出,第二次全部重放、一次模型都不调', async () => {
365
+ const wd = mkdtempSync(join(tmpdir(), 'wf-cap-resume-'));
366
+ dirs.push(wd);
367
+ const N = 12;
368
+
369
+ const build = async (chatFn: unknown) => {
370
+ const state = new WorkflowState(wd, 'demo', 's1');
371
+ await state.init({ goal: 'g' }, 'hash-v1', []);
372
+ const journal = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
373
+ await journal.load();
374
+ const events: LoopEvent[] = [];
375
+ const ctx = {
376
+ provider: {
377
+ name: 't',
378
+ model: 'base-model',
379
+ baseURL: 'x',
380
+ apiKey: 'k',
381
+ contextWindow: 128000,
382
+ },
383
+ history: [{ role: 'system', content: 'sys' }] as Message[],
384
+ cwd: wd,
385
+ tools: [],
386
+ chat: chatFn,
387
+ } as unknown as RunContext;
388
+ return {
389
+ globals: createWorkflowContext({
390
+ ctx,
391
+ state,
392
+ journal,
393
+ emit: (ev) => events.push(ev),
394
+ args: {},
395
+ workflowName: 'demo',
396
+ session: 's1',
397
+ task: state.taskId,
398
+ }),
399
+ journal,
400
+ events,
401
+ };
402
+ };
403
+
404
+ // 让完成顺序与发起顺序相反 —— 序号若按「谁先返回」分配,第二次必然错位
405
+ const slowChat = (texts: string[]) => {
406
+ let n = 0;
407
+ return async function* () {
408
+ const i = n++;
409
+ await new Promise((r) => setTimeout(r, (texts.length - i) * 3));
410
+ yield { type: 'text_delta' as const, delta: texts[i] ?? 'x' };
411
+ };
412
+ };
413
+ const texts = Array.from({ length: N }, (_, i) => `{"items":[${i}]}`);
414
+
415
+ const run1 = await build(slowChat(texts));
416
+ const out1 = await run1.globals.flow.pipeline(
417
+ Array.from({ length: N }, (_, i) => i),
418
+ { stages: [(_p: unknown, item: number) =>
419
+ run1.globals.model.generate(`处理 ${item}`, { schema: NUMS, isolated: true })] },
420
+ );
421
+ expect(out1.results).toHaveLength(N);
422
+
423
+ // 第二次:任何一次模型调用都算失败
424
+ const run2 = await build(async function* () {
425
+ throw new Error('第二次不该调模型');
426
+ // biome-ignore lint/correctness/noUnreachable: deliberate throwing stub
427
+ yield { type: 'text_delta' as const, delta: '' };
428
+ });
429
+ const out2 = await run2.globals.flow.pipeline(
430
+ Array.from({ length: N }, (_, i) => i),
431
+ { stages: [(_p: unknown, item: number) =>
432
+ run2.globals.model.generate(`处理 ${item}`, { schema: NUMS, isolated: true })] },
433
+ );
434
+
435
+ expect(out2.results).toEqual(out1.results);
436
+ expect(run2.journal.replayedCount).toBe(N);
437
+ // 车道并发告警不该出现 —— pipeline 给的是各自独立的车道
438
+ expect(
439
+ run2.events.some(
440
+ (e) =>
441
+ e.type === 'warning' &&
442
+ String(e.message).includes('断点续跑随之失效'),
443
+ ),
444
+ ).toBe(false);
445
+ });
446
+ });
447
+
448
+ describe('③ model 覆盖', () => {
449
+ test('schema repairs share the logical-call deadline rather than resetting it', async () => {
450
+ const signals: AbortSignal[] = [];
451
+ const chat = async function* (a: { signal: AbortSignal }) {
452
+ signals.push(a.signal);
453
+ if (signals.length === 1) {
454
+ yield { type: 'text_delta' as const, delta: 'invalid JSON' };
455
+ } else {
456
+ await new Promise<void>((resolve) => {
457
+ if (a.signal.aborted) resolve();
458
+ else
459
+ a.signal.addEventListener('abort', () => resolve(), { once: true });
460
+ });
461
+ }
462
+ };
463
+ const h = await harness(chat);
464
+ await expect(
465
+ h.globals.model.generate('repair', { schema: NUMS, timeoutMs: 500, isolated: true }),
466
+ ).rejects.toThrow('步骤时限');
467
+ expect(signals).toHaveLength(2);
468
+ expect(signals[0]).toBe(signals[1]);
469
+ });
470
+
471
+ test('queued calls expire without contacting provider and do not leak gate slots', async () => {
472
+ let started = 0;
473
+ const release: (() => void)[] = [];
474
+ const chat = async function* () {
475
+ started++;
476
+ if (started <= 8)
477
+ await new Promise<void>((resolve) => release.push(resolve));
478
+ yield { type: 'text_delta' as const, delta: 'ok' };
479
+ };
480
+ const h = await harness(chat);
481
+ const running = h.globals.flow.pipeline(
482
+ Array.from({ length: 8 }, (_, i) => i),
483
+ { concurrency: 8, stages: [(i: number) => h.globals.model.generate(`hold ${i}`, { isolated: true })] },
484
+ );
485
+ while (started < 8) await new Promise((resolve) => setTimeout(resolve, 1));
486
+ try {
487
+ await expect(h.globals.model.generate('queued', { timeoutMs: 20 })).rejects.toThrow(
488
+ '步骤时限',
489
+ );
490
+ expect(started).toBe(8);
491
+ } finally {
492
+ for (const resolve of release) resolve();
493
+ await running;
494
+ }
495
+ expect(await h.globals.model.generate('after', { timeoutMs: 200 })).toBe('ok');
496
+ });
497
+
498
+ test('未映射工作流角色时用基座模型做能力预检', async () => {
499
+ const chat = scriptedChat(['unused']);
500
+ const h = await harness(chat.fn);
501
+ const capability = (await h.globals.model.require(
502
+ 'capability_probe_unmapped',
503
+ {
504
+ vision: true,
505
+ },
506
+ )) as {
507
+ role: string;
508
+ model: string;
509
+ supportsVision: boolean;
510
+ supportsJsonSchema: boolean;
511
+ };
512
+ expect(capability).toEqual({
513
+ role: 'capability_probe_unmapped',
514
+ model: 'base-model',
515
+ supportsVision: true,
516
+ supportsJsonSchema: false,
517
+ });
518
+ });
519
+
520
+ test('单步可以限制输出 token,且不超过 provider 自身上限', async () => {
521
+ const chat = scriptedChat(['ok']);
522
+ const h = await harness(chat.fn);
523
+ await h.globals.model.generate('短回答', { maxTokens: 1234 });
524
+ expect(chat.calls[0]?.maxTokens).toBe(1234);
525
+ });
526
+
527
+ test('单步超过总时限会中止,而不是无限等待流式推理', async () => {
528
+ const chat = async function* (a: { signal?: AbortSignal }) {
529
+ await new Promise<void>((resolve) => {
530
+ if (a.signal?.aborted) resolve();
531
+ else
532
+ a.signal?.addEventListener('abort', () => resolve(), { once: true });
533
+ });
534
+ if (!a.signal?.aborted) {
535
+ yield { type: 'text_delta' as const, delta: '不应到达' };
536
+ }
537
+ };
538
+ const h = await harness(chat);
539
+ await expect(
540
+ h.globals.model.generate('模拟持续推理', { timeoutMs: 10 }),
541
+ ).rejects.toThrow(/超过工作流步骤时限 10ms/);
542
+ });
543
+
544
+ test('★ 解析不到 → 回落基座 + 响亮告警,绝不静默换模型', async () => {
545
+ const chat = scriptedChat(['ok']);
546
+ const h = await harness(chat.fn);
547
+ await h.globals.model.generate('x', { model: '不存在的模型' });
548
+
549
+ expect(chat.calls[0].model).toBe('base-model'); // 真的回落了
550
+ expect(
551
+ h.events.some(
552
+ (e) =>
553
+ e.type === 'warning' &&
554
+ String(e.message).includes('找不到模型「不存在的模型」'),
555
+ ),
556
+ ).toBe(true);
557
+ });
558
+
559
+ test('同一个坏 id 只告警一次(循环里不刷屏)', async () => {
560
+ const h = await harness(scriptedChat(['ok']).fn);
561
+ await h.globals.model.generate('x', { model: 'bad' });
562
+ await h.globals.model.generate('y', { model: 'bad' });
563
+ const n = h.events.filter(
564
+ (e) => e.type === 'warning' && String(e.message).includes('找不到模型'),
565
+ ).length;
566
+ expect(n).toBe(1);
567
+ });
568
+ });
569
+
570
+ describe('④ isolated 隔离执行', () => {
571
+ test('agent schema repairs remain isolated as well as the initial call', async () => {
572
+ const chat = scriptedChat(['invalid', '{"items":[1]}']);
573
+ const h = await harness(chat.fn);
574
+ expect(
575
+ await h.globals.model.agent('x', {
576
+ isolated: true,
577
+ allowedTools: [],
578
+ schema: NUMS,
579
+ }),
580
+ ).toEqual({ items: [1] });
581
+ expect(h.history).toHaveLength(1);
582
+ expect(
583
+ h.events.some(
584
+ (e) =>
585
+ e.type === 'assistant_message' || e.type === 'user_message_committed',
586
+ ),
587
+ ).toBe(false);
588
+ });
589
+ test('★ 主线 history 一个字节都不变长', async () => {
590
+ const chat = scriptedChat(['隔离产出']);
591
+ const h = await harness(chat.fn);
592
+ const before = h.history.length;
593
+ const r = await h.globals.model.generate('打个分', { isolated: true });
594
+ expect(r).toBe('隔离产出');
595
+ expect(h.history.length).toBe(before);
596
+ });
597
+
598
+ test('★ 隔离调用看不到主线积累的上下文(只带系统提示)', async () => {
599
+ const chat = scriptedChat(['一', '二']);
600
+ const h = await harness(chat.fn);
601
+ await h.globals.model.generate('第一次'); // 非隔离:会把 user+assistant 追加进主线
602
+ const sharedCount = chat.calls[0].messageCount;
603
+ await h.globals.model.generate('第二次', { isolated: true });
604
+ // 非隔离的第二次会比第一次多;隔离的必须回到与第一次同量级
605
+ expect(chat.calls[1].messageCount).toBeLessThanOrEqual(sharedCount);
606
+ });
607
+
608
+ test('默认(不传 isolated)仍走单一上下文 —— 主线会变长', async () => {
609
+ const h = await harness(scriptedChat(['a', 'b']).fn);
610
+ const before = h.history.length;
611
+ await h.globals.model.generate('x');
612
+ expect(h.history.length).toBeGreaterThan(before);
613
+ });
614
+ });