minimal-agent 0.13.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 (470) hide show
  1. package/README.md +6 -5
  2. package/package.json +60 -4
  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 +197 -206
  16. package/plugins/workflow-runner/src/index.ts +248 -249
  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 +400 -489
  28. package/plugins/workflow-runner/src/runner.ts +488 -560
  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 +151 -307
  54. package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
  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 -211
  58. package/plugins/workflow-runner/src/types.ts +154 -623
  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 +474 -802
  68. package/plugins/workflow-runner/src/workflowState.ts +575 -970
  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/ui-design/SKILL.md +5 -2
  92. package/skills/ui-design/references/layout-composition.md +1 -1
  93. package/skills/workflow-creator/SKILL.md +344 -0
  94. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  95. package/skills/workflow-creator/references/api.md +216 -0
  96. package/skills/workflow-creator/references/migration.md +103 -0
  97. package/skills/workflow-creator/references/patterns.md +779 -0
  98. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  99. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  100. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  101. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  102. package/src/advisor/advisorCoordinator.js +503 -0
  103. package/src/advisor/advisorCoordinator.ts +657 -0
  104. package/src/cli/configMenu.tsx +0 -1
  105. package/src/cli/print.js +27 -6
  106. package/src/cli/print.ts +48 -4
  107. package/src/cli/streamJson.js +5 -6
  108. package/src/cli/streamJson.ts +8 -4
  109. package/src/cli/wizards/advisorWizard.js +4 -2
  110. package/src/cli/wizards/advisorWizard.tsx +8 -2
  111. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  112. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  113. package/src/collaboration/taskFlow.js +115 -0
  114. package/src/collaboration/taskFlow.ts +169 -0
  115. package/src/config/configDefaults.js +41 -1
  116. package/src/config/configDefaults.ts +43 -1
  117. package/src/config/configDiagnostics.ts +2 -2
  118. package/src/config/configFile.js +40 -9
  119. package/src/config/configFile.ts +60 -11
  120. package/src/config/configService.js +419 -74
  121. package/src/config/configService.ts +703 -145
  122. package/src/config/numericLimits.js +22 -0
  123. package/src/config/numericLimits.ts +24 -0
  124. package/src/config.js +1 -0
  125. package/src/config.ts +3 -2
  126. package/src/context/budgetPlan.js +1 -1
  127. package/src/context/budgetPlan.ts +1 -1
  128. package/src/context/compact.js +7 -2
  129. package/src/context/compact.ts +9 -2
  130. package/src/context/microCompactLite.js +212 -13
  131. package/src/context/microCompactLite.ts +261 -12
  132. package/src/context/sessionRegistry.js +8 -1
  133. package/src/context/sessionRegistry.ts +18 -5
  134. package/src/context/toolResultArchive.js +72 -0
  135. package/src/context/toolResultArchive.ts +95 -0
  136. package/src/goal/errors.js +8 -0
  137. package/src/goal/errors.ts +18 -0
  138. package/src/goal/goalRuntime.js +344 -0
  139. package/src/goal/goalRuntime.ts +466 -0
  140. package/src/goal/goalStore.js +200 -0
  141. package/src/goal/goalStore.ts +219 -0
  142. package/src/goal/index.js +4 -0
  143. package/src/goal/index.ts +4 -0
  144. package/src/goal/statusMapping.js +71 -0
  145. package/src/goal/statusMapping.ts +95 -0
  146. package/src/goal/types.js +7 -0
  147. package/src/goal/types.ts +179 -0
  148. package/src/goal/workflowController.js +306 -0
  149. package/src/goal/workflowController.ts +360 -0
  150. package/src/harness/evidenceLedger.js +2 -0
  151. package/src/harness/evidenceLedger.ts +1 -0
  152. package/src/harness/stallGuard.js +9 -3
  153. package/src/harness/stallGuard.ts +7 -3
  154. package/src/images/imageResolver.js +161 -0
  155. package/src/images/imageResolver.ts +189 -0
  156. package/src/infra/verification.js +34 -5
  157. package/src/infra/verification.ts +39 -4
  158. package/src/llm/client.js +79 -21
  159. package/src/llm/client.ts +182 -35
  160. package/src/llm/gatewayClient.js +201 -0
  161. package/src/llm/gatewayClient.ts +299 -0
  162. package/src/llm/messageProvenance.js +46 -0
  163. package/src/llm/messageProvenance.ts +58 -0
  164. package/src/llm/modelProvider.js +1 -0
  165. package/src/llm/modelProvider.ts +22 -0
  166. package/src/llm/providers/anthropicFormat.js +108 -1
  167. package/src/llm/providers/anthropicFormat.ts +115 -4
  168. package/src/llm/providers/openaiFormat.js +9 -0
  169. package/src/llm/providers/openaiFormat.ts +12 -3
  170. package/src/loop.js +319 -54
  171. package/src/loop.ts +373 -57
  172. package/src/main.js +4 -1
  173. package/src/main.tsx +4 -1
  174. package/src/managedExecution/index.js +2 -0
  175. package/src/managedExecution/index.ts +2 -0
  176. package/src/managedExecution/registry.js +15 -0
  177. package/src/managedExecution/registry.ts +20 -0
  178. package/src/managedExecution/types.js +1 -0
  179. package/src/managedExecution/types.ts +86 -0
  180. package/src/modelRef.js +9 -0
  181. package/src/modelRef.ts +31 -0
  182. package/src/multimodal.js +14 -28
  183. package/src/multimodal.ts +17 -33
  184. package/src/net/lanDiscovery.js +65 -0
  185. package/src/net/lanDiscovery.ts +82 -0
  186. package/src/plugin-sdk.js +28 -3
  187. package/src/plugin-sdk.ts +39 -6
  188. package/src/plugins/pluginApi.ts +16 -1
  189. package/src/plugins/pluginLoader.js +4 -0
  190. package/src/plugins/pluginLoader.ts +6 -0
  191. package/src/plugins/pluginRunner.js +139 -7
  192. package/src/plugins/pluginRunner.ts +173 -9
  193. package/src/plugins/slashFallback.js +113 -0
  194. package/src/plugins/slashFallback.ts +137 -0
  195. package/src/prompts/policyRegistry.js +12 -32
  196. package/src/prompts/policyRegistry.ts +15 -32
  197. package/src/prompts/promptAssembly.js +103 -12
  198. package/src/prompts/promptAssembly.ts +114 -12
  199. package/src/prompts/skillList.js +21 -3
  200. package/src/prompts/skillList.ts +27 -2
  201. package/src/prompts/stableSystemPrompt.js +18 -0
  202. package/src/prompts/stableSystemPrompt.ts +33 -0
  203. package/src/runtime/agentRuntimeContext.js +8 -2
  204. package/src/runtime/agentRuntimeContext.ts +17 -4
  205. package/src/session/runAgentTurn.js +78 -12
  206. package/src/session/runAgentTurn.ts +105 -23
  207. package/src/session/runTurn.js +10 -9
  208. package/src/session/runTurn.ts +17 -8
  209. package/src/session/sessionState.ts +2 -0
  210. package/src/team/auth.js +186 -0
  211. package/src/team/auth.ts +298 -0
  212. package/src/team/cli.js +105 -0
  213. package/src/team/cli.ts +122 -0
  214. package/src/team/onlineToolContract.js +64 -0
  215. package/src/team/onlineToolContract.ts +109 -0
  216. package/src/team/toolPolicy.js +36 -0
  217. package/src/team/toolPolicy.ts +75 -0
  218. package/src/tools/advisor/advisor.js +90 -46
  219. package/src/tools/advisor/advisor.ts +100 -47
  220. package/src/tools/advisor/advisorDecision.js +71 -0
  221. package/src/tools/advisor/advisorDecision.ts +85 -0
  222. package/src/tools/agent/agent.js +18 -5
  223. package/src/tools/agent/agent.ts +21 -7
  224. package/src/tools/agent/agentControl.js +246 -0
  225. package/src/tools/agent/agentControl.ts +284 -0
  226. package/src/tools/agent/agentTaskService.js +40 -1
  227. package/src/tools/agent/agentTaskService.ts +44 -1
  228. package/src/tools/agent/agentTypes.js +14 -2
  229. package/src/tools/agent/agentTypes.ts +15 -2
  230. package/src/tools/applypatch/applyPatch.js +362 -0
  231. package/src/tools/applypatch/applyPatch.ts +428 -0
  232. package/src/tools/bash/bash.js +22 -15
  233. package/src/tools/bash/bash.ts +23 -17
  234. package/src/tools/capabilities.js +10 -5
  235. package/src/tools/capabilities.ts +10 -5
  236. package/src/tools/computeruse/computerIntent.js +32 -0
  237. package/src/tools/computeruse/computerIntent.ts +46 -0
  238. package/src/tools/computeruse/telemetryLog.js +12 -0
  239. package/src/tools/computeruse/telemetryLog.ts +16 -0
  240. package/src/tools/computeruse/vision/telemetry.js +1 -0
  241. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  242. package/src/tools/effectiveToolSet.js +4 -0
  243. package/src/tools/effectiveToolSet.ts +4 -0
  244. package/src/tools/index.js +21 -2
  245. package/src/tools/index.ts +33 -2
  246. package/src/tools/presentresult/presentresult.js +6 -15
  247. package/src/tools/presentresult/presentresult.ts +6 -15
  248. package/src/tools/read/read.js +62 -9
  249. package/src/tools/read/read.ts +63 -9
  250. package/src/tools/shared/fileUtils.js +1 -2
  251. package/src/tools/shared/fileUtils.ts +1 -2
  252. package/src/tools/shared/toolOutputStore.js +23 -1
  253. package/src/tools/shared/toolOutputStore.ts +22 -1
  254. package/src/tools/skill/skill.js +9 -3
  255. package/src/tools/skill/skill.ts +8 -4
  256. package/src/tools/toolPromptGuides.js +117 -0
  257. package/src/tools/toolPromptGuides.ts +137 -0
  258. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  259. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  260. package/src/tools/webbrowser/browser.js +27 -11
  261. package/src/tools/webbrowser/browser.ts +68 -24
  262. package/src/tools/webbrowser/browserAutomation.js +329 -0
  263. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  264. package/src/tools/webbrowser/browserAxState.js +97 -0
  265. package/src/tools/webbrowser/browserAxState.ts +142 -0
  266. package/src/tools/webbrowser/browserBatch.js +1578 -0
  267. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  268. package/src/tools/webbrowser/browserCdp.js +208 -0
  269. package/src/tools/webbrowser/browserCdp.ts +255 -0
  270. package/src/tools/webbrowser/browserConditions.js +24 -0
  271. package/src/tools/webbrowser/browserConditions.ts +45 -0
  272. package/src/tools/webbrowser/browserContent.js +281 -0
  273. package/src/tools/webbrowser/browserContent.ts +438 -0
  274. package/src/tools/webbrowser/browserControl.js +131 -34
  275. package/src/tools/webbrowser/browserControl.ts +210 -56
  276. package/src/tools/webbrowser/browserDaemon.js +22 -14
  277. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  278. package/src/tools/webbrowser/browserFrames.js +47 -0
  279. package/src/tools/webbrowser/browserFrames.ts +54 -0
  280. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  281. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  282. package/src/tools/webbrowser/browserLocator.js +140 -0
  283. package/src/tools/webbrowser/browserLocator.ts +194 -0
  284. package/src/tools/webbrowser/browserOutcome.js +10 -17
  285. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  286. package/src/tools/webbrowser/browserRecovery.js +58 -0
  287. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  288. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  289. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  290. package/src/tools/webbrowser/browserService.js +28 -2
  291. package/src/tools/webbrowser/browserService.ts +49 -9
  292. package/src/tools/webbrowser/browserStream.js +132 -33
  293. package/src/tools/webbrowser/browserStream.ts +224 -50
  294. package/src/tools/webbrowser/browserTabs.js +48 -15
  295. package/src/tools/webbrowser/browserTabs.ts +178 -96
  296. package/src/tools/webbrowser/webbrowser.js +679 -144
  297. package/src/tools/webbrowser/webbrowser.ts +975 -236
  298. package/src/tools/webfetch/preapproved.js +0 -2
  299. package/src/tools/webfetch/preapproved.ts +0 -2
  300. package/src/tools/websearch/websearch.js +129 -8
  301. package/src/tools/websearch/websearch.ts +138 -6
  302. package/src/types.ts +440 -38
  303. package/src/ui/App.js +377 -58
  304. package/src/ui/App.tsx +576 -111
  305. package/src/ui/LiveArea.js +14 -8
  306. package/src/ui/LiveArea.tsx +14 -10
  307. package/src/ui/MessageList.js +63 -20
  308. package/src/ui/MessageList.tsx +101 -16
  309. package/src/ui/ModelPicker.js +7 -4
  310. package/src/ui/ModelPicker.tsx +55 -8
  311. package/src/ui/hooks/useChat.js +152 -163
  312. package/src/ui/hooks/useChat.ts +396 -334
  313. package/src/ui/loginCommand.js +56 -0
  314. package/src/ui/loginCommand.ts +63 -0
  315. package/src/ui/toolProgress.js +41 -0
  316. package/src/ui/toolProgress.ts +44 -0
  317. package/src/web/browserBridge.js +20 -2
  318. package/src/web/browserBridge.ts +20 -2
  319. package/src/web/protocol.ts +1897 -194
  320. package/workflows/adaptive-research/workflow.mjs +86 -0
  321. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  322. package/workflows/e2e-llm/workflow.mjs +52 -0
  323. package/workflows/e2e-smoke/workflow.mjs +59 -0
  324. package/workflows/e2e-vision-schema/README.md +48 -0
  325. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  326. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  327. package/workflows/file-census/README.md +14 -0
  328. package/workflows/file-census/scripts/.gitkeep +2 -0
  329. package/workflows/file-census/workflow.mjs +110 -0
  330. package/workflows/goal/README.md +166 -0
  331. package/workflows/goal/prompts/audit.md +57 -0
  332. package/workflows/goal/prompts/contract.md +56 -0
  333. package/workflows/goal/prompts/iterate.md +37 -0
  334. package/workflows/goal/workflow.mjs +501 -0
  335. package/workflows/illustrate/README.md +90 -0
  336. package/workflows/illustrate/assets/engines.json +20 -0
  337. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  338. package/workflows/illustrate/assets/extract-bing.js +33 -0
  339. package/workflows/illustrate/assets/extract-google.js +31 -0
  340. package/workflows/illustrate/workflow.mjs +301 -0
  341. package/workflows/plan/workflow.mjs +389 -0
  342. package/workflows/question-crops/README.md +35 -0
  343. package/workflows/question-crops/prompts/inventory.md +23 -0
  344. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  345. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  346. package/workflows/question-crops/workflow.mjs +172 -0
  347. package/workflows/ralph-loop/workflow.mjs +162 -0
  348. package/workflows/research/README.md +74 -0
  349. package/workflows/research/prompts/s0-goal.md +43 -0
  350. package/workflows/research/prompts/s1-early-exit.md +22 -0
  351. package/workflows/research/prompts/s1-map.md +58 -0
  352. package/workflows/research/prompts/s2-options.md +53 -0
  353. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  354. package/workflows/research/prompts/s4-verify.md +74 -0
  355. package/workflows/research/prompts/s5-synthesis.md +81 -0
  356. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  357. package/workflows/research/prompts/s6-package.md +32 -0
  358. package/workflows/research/scripts/build-report.mjs +98 -0
  359. package/workflows/research/workflow.mjs +288 -0
  360. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  361. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  362. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  363. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  364. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  365. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  366. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  367. package/workflows/wrong-question-docx/README.md +55 -0
  368. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  369. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  370. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  371. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  372. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  373. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  374. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  375. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  376. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  377. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  378. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  379. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  380. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  381. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  382. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  383. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  384. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  385. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  386. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  387. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  388. package/plugins/workflow-runner/src/expressions.js +0 -639
  389. package/plugins/workflow-runner/src/expressions.ts +0 -640
  390. package/plugins/workflow-runner/src/loader.js +0 -573
  391. package/plugins/workflow-runner/src/loader.ts +0 -721
  392. package/plugins/workflow-runner/src/multimodal.js +0 -38
  393. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  394. package/plugins/workflow-runner/src/recovery.js +0 -126
  395. package/plugins/workflow-runner/src/recovery.ts +0 -156
  396. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  397. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  398. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -42
  399. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
  400. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  401. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  402. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  403. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  404. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  405. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  406. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  407. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  408. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -227
  409. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
  410. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
  411. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
  412. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  413. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  414. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  415. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  416. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  417. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  418. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -167
  419. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  420. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  421. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +0 -314
  422. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
  423. package/plugins/workflow-runner/test/expressions.test.ts +0 -439
  424. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  425. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  426. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  427. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  428. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  429. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  430. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  431. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  432. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  433. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  434. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -106
  435. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  436. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  437. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -221
  438. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  439. package/plugins/workflow-runner/test/research.load.test.ts +0 -328
  440. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  441. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
  442. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  443. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
  444. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  445. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
  446. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
  447. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  448. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  449. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  450. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  451. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  452. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  453. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  454. package/src/advisorState.js +0 -22
  455. package/src/cli/configWizard.js +0 -293
  456. package/src/tools/toolProgressBridge.js +0 -48
  457. package/workflows/adaptive-research.yaml +0 -50
  458. package/workflows/book-review-short.yaml +0 -125
  459. package/workflows/e2e-write-greet.yaml +0 -31
  460. package/workflows/illustrate.yaml +0 -227
  461. package/workflows/plan.yaml +0 -470
  462. package/workflows/ppt.yaml +0 -1670
  463. package/workflows/ralph-loop.yaml +0 -201
  464. package/workflows/research.yaml +0 -873
  465. package/workflows/schema.json +0 -176
  466. package/workflows/webppt.yaml +0 -496
  467. package/workflows/wechat-reading-shots.yaml +0 -315
  468. package/workflows/word.yaml +0 -720
  469. package/workflows/youtube-shorts.yaml +0 -202
  470. /package/workflows/{research.md → research/DESIGN.md} +0 -0
@@ -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
+ });