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,104 @@
1
+ /**
2
+ * plugins/workflow-runner/test/apiConcurrencyGate.test.ts —— llm() 与 agent()
3
+ * 共用进程级模型调度器
4
+ *
5
+ * `generate()`、`agent()`、`skill()` 都必须进入同一个进程级 model pool;测试从
6
+ * 统一配置读取边界,不能再复制一个常量,否则环境配置和测试会悄悄分叉。
7
+ */
8
+
9
+ import { afterEach, describe, expect, test } from 'bun:test';
10
+ import { mkdtempSync, rmSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ import type { LoopEvent } from '../../../src/plugin-sdk.js';
15
+ import { createWorkflowContext } from '../src/runtime/context.js';
16
+ import { Journal } from '../src/journal.js';
17
+ import { readWorkflowRuntimeConfig } from '../src/runtime/config.js';
18
+ import type { RunContext } from '../src/types.js';
19
+ import { WorkflowState } from '../src/workflowState.js';
20
+
21
+ const dirs: string[] = [];
22
+ const MODEL_LIMIT = readWorkflowRuntimeConfig({} as NodeJS.ProcessEnv)
23
+ .modelConcurrency;
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
+ async function harness() {
36
+ const wd = mkdtempSync(join(tmpdir(), 'wf-gate-'));
37
+ dirs.push(wd);
38
+ const state = new WorkflowState(wd, 'demo', 's1');
39
+ await state.init({ goal: '测试目标' }, 'hash-v1', []);
40
+ const journal = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
41
+ await journal.load();
42
+
43
+ let active = 0;
44
+ let peak = 0;
45
+ const chat: NonNullable<RunContext['chat']> = async function* () {
46
+ active++;
47
+ peak = Math.max(peak, active);
48
+ try {
49
+ // 强制重叠:没有这个延迟,调用可能快到彼此从不同时在场,测不出并发峰值。
50
+ await new Promise((r) => setTimeout(r, 15));
51
+ yield { type: 'text_delta' as const, delta: 'ok' };
52
+ } finally {
53
+ active--;
54
+ }
55
+ };
56
+
57
+ const events: LoopEvent[] = [];
58
+ const ctx = {
59
+ provider: { name: 'test' },
60
+ history: [{ role: 'system', content: 'sys' }],
61
+ cwd: wd,
62
+ tools: [],
63
+ chat,
64
+ } as unknown as RunContext;
65
+
66
+ const globals = createWorkflowContext({
67
+ ctx,
68
+ state,
69
+ journal,
70
+ emit: (ev) => events.push(ev),
71
+ args: {},
72
+ workflowName: 'demo',
73
+ session: 's1',
74
+ task: state.taskId,
75
+ });
76
+
77
+ return { globals, peakConcurrency: () => peak };
78
+ }
79
+
80
+ describe('generate()/agent() 共用进程级模型调度器', () => {
81
+ test(`★ 混合扇出 ${MODEL_LIMIT * 2} 个 generate()+agent() 调用,实际并发峰值不超过 ${MODEL_LIMIT}`, async () => {
82
+ const h = await harness();
83
+ const N = MODEL_LIMIT * 2;
84
+ await Promise.all(
85
+ Array.from({ length: N }, (_, i) =>
86
+ i % 2 === 0 ? h.globals.model.generate(`任务 ${i}`) : h.globals.model.agent(`任务 ${i}`),
87
+ ),
88
+ );
89
+ expect(h.peakConcurrency()).toBeLessThanOrEqual(MODEL_LIMIT);
90
+ });
91
+
92
+ test('★ 纯 llm() 扇出同样受同一把闸约束(不是只有 agent() 受限)', async () => {
93
+ const h = await harness();
94
+ const N = MODEL_LIMIT * 2;
95
+ await Promise.all(
96
+ Array.from({ length: N }, (_, i) => h.globals.model.generate(`任务 ${i}`)),
97
+ );
98
+ expect(h.peakConcurrency()).toBeLessThanOrEqual(MODEL_LIMIT);
99
+ // 扇出规模显著大于闸门上限时,峰值应当明显叠起来、接近上限(否则上面的
100
+ // 断言可能只是因为并发压根没叠起来,同样是假绿)。账本落盘等异步开销会让
101
+ // 各调用起跑时间略有错开,所以不强求刚好等于上限,只要求足够接近。
102
+ expect(h.peakConcurrency()).toBeGreaterThanOrEqual(MODEL_LIMIT - 2);
103
+ });
104
+ });
@@ -0,0 +1,210 @@
1
+ /**
2
+ * plugins/workflow-runner/test/apiEcho.test.ts —— echo() 必须真正进对话历史
3
+ *
4
+ * 这是实测出来的行为缺陷,不是理论风险:
5
+ *
6
+ * 宿主把 `text` 事件当**流式预览**——TUI 累进 LiveArea、Desktop 累进
7
+ * `streamingText`——而预览在 `turn_done` 时被 `clearLive()` 清掉。只有
8
+ * `assistant_message` 才会落进 `history` / `setMessages`。
9
+ *
10
+ * 于是一条纯确定性工作流(只有 echo、没有 llm/agent)跑完之后,对话历史里只剩
11
+ * 用户那句 `/workflow xxx`,助手侧一片空白 —— 用户完全看不出发生过什么。
12
+ * 有 llm 的工作流更糟:中间 agent 的碎话留下了,`echo()` 写的最终结论反而没了。
13
+ *
14
+ * 更早的 `echo` 注释白纸黑字写着「进 MessageList」,而实现只发 `text` ——
15
+ * 注释宣称了、实现没做到,正是最容易再次退化的那类。这个文件把它钉死。
16
+ */
17
+
18
+ import { afterEach, describe, expect, test } from 'bun:test';
19
+ import { mkdtempSync, rmSync } from 'node:fs';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+
23
+ import type { LoopEvent, Message } from '../../../src/plugin-sdk.js';
24
+ import { createWorkflowContext } from '../src/runtime/context.js';
25
+ import { Journal } from '../src/journal.js';
26
+ import type { RunContext } from '../src/types.js';
27
+ import { WorkflowState } from '../src/workflowState.js';
28
+
29
+ const dirs: string[] = [];
30
+ afterEach(() => {
31
+ for (const d of dirs.splice(0)) {
32
+ try {
33
+ rmSync(d, { recursive: true, force: true, maxRetries: 3 });
34
+ } catch {
35
+ /* Windows 偶发 EBUSY;清理失败不影响断言 */
36
+ }
37
+ }
38
+ });
39
+
40
+ async function harness() {
41
+ const wd = mkdtempSync(join(tmpdir(), 'wf-echo-'));
42
+ dirs.push(wd);
43
+ const state = new WorkflowState(wd, 'demo', 's1');
44
+ await state.init({ goal: '测试目标' }, 'hash-v1', []);
45
+ const journal = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
46
+ await journal.load();
47
+
48
+ const events: LoopEvent[] = [];
49
+ const history: Message[] = [{ role: 'system', content: 'sys' }];
50
+ const ctx = {
51
+ provider: { name: 'test' },
52
+ history,
53
+ cwd: wd,
54
+ tools: [],
55
+ } as unknown as RunContext;
56
+
57
+ const globals = createWorkflowContext({
58
+ ctx,
59
+ state,
60
+ journal,
61
+ emit: (ev) => events.push(ev),
62
+ args: {},
63
+ workflowName: 'demo',
64
+ session: 's1',
65
+ task: state.taskId,
66
+ });
67
+
68
+ return { globals, events, history };
69
+ }
70
+
71
+ describe('echo() 进历史', () => {
72
+ test('★ 发 assistant_message,而不只是流式 text', async () => {
73
+ const h = await harness();
74
+ h.globals.output.message('✅ 冒烟通过:2 行');
75
+
76
+ const committed = h.events.filter((e) => e.type === 'assistant_message');
77
+ expect(committed).toHaveLength(1);
78
+ expect(
79
+ (committed[0] as Extract<LoopEvent, { type: 'assistant_message' }>).message
80
+ .content,
81
+ ).toBe('✅ 冒烟通过:2 行');
82
+ });
83
+
84
+ test('★ push 进 ctx.history —— 这才是跨会话不丢的原因', async () => {
85
+ // Desktop 重开会话是从 history 加载的;只 emit 事件、不进 history 的话
86
+ // 当场看得见,一刷新就没了。
87
+ const h = await harness();
88
+ h.globals.output.message('结论 A');
89
+ h.globals.output.message('结论 B');
90
+
91
+ const assistant = h.history.filter((m) => m.role === 'assistant');
92
+ expect(assistant.map((m) => m.content)).toEqual(['结论 A', '结论 B']);
93
+ });
94
+
95
+ test('每条消息都有 id(宿主拿它做 React key / 去重)', async () => {
96
+ const h = await harness();
97
+ h.globals.output.message('一');
98
+ h.globals.output.message('二');
99
+
100
+ const ids = h.history
101
+ .filter((m) => m.role === 'assistant')
102
+ .map((m) => (m as { id?: string }).id);
103
+ expect(ids.every((id) => typeof id === 'string' && id.length > 0)).toBe(true);
104
+ expect(new Set(ids).size).toBe(2); // 不能是同一个 id
105
+ });
106
+
107
+ test('★ text 事件照发不误 —— -p 的 result 累的是它', async () => {
108
+ // print.ts 的 result 累 text delta;assistant_message 只计 num_turns、
109
+ // 不进 result buffer。两者并存不会重复,去掉 text 反而会让 -p 拿到空 result。
110
+ const h = await harness();
111
+ h.globals.output.message('给宿主的结论');
112
+
113
+ const texts = h.events
114
+ .filter((e): e is Extract<LoopEvent, { type: 'text' }> => e.type === 'text')
115
+ .map((e) => e.delta)
116
+ .join('');
117
+ expect(texts).toContain('给宿主的结论');
118
+ });
119
+
120
+ test('log() 不进历史 —— 它是状态行旁白,不是给用户的结论', async () => {
121
+ const h = await harness();
122
+ h.globals.output.progress({
123
+ phase: 'test',
124
+ status: 'update',
125
+ message: '正在扫描目录…',
126
+ });
127
+
128
+ expect(h.history.filter((m) => m.role === 'assistant')).toHaveLength(0);
129
+ expect(h.events.some((e) => e.type === 'assistant_message')).toBe(false);
130
+ expect(h.events.some((e) => e.type === 'workflow_progress')).toBe(true);
131
+ });
132
+ });
133
+
134
+ describe('断点续跑:快进段不得重复入历史', () => {
135
+ test('★ 重放前缀里的 echo 只发 text,不再 push(history 已从会话文件带着它)', async () => {
136
+ const wd = mkdtempSync(join(tmpdir(), 'wf-echo-resume-'));
137
+ dirs.push(wd);
138
+ const state = new WorkflowState(wd, 'demo', 's1');
139
+ await state.init({ goal: '测试目标' }, 'hash-v1', []);
140
+
141
+ // 第一轮:跑两个 journaled 调用,账本落两条
142
+ const j1 = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
143
+ await j1.load();
144
+ await j1.step('tool', { a: 1 }, async () => 'r1');
145
+ await j1.step('tool', { a: 2 }, async () => 'r2');
146
+
147
+ // 第二轮 resume:重新载入账本,此刻处在重放前缀
148
+ const j2 = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
149
+ await j2.load();
150
+
151
+ const events: LoopEvent[] = [];
152
+ // resume 的 history 里已经有上一轮那条 echo(会话文件带过来的)
153
+ const history: Message[] = [
154
+ { role: 'system', content: 'sys' },
155
+ { role: 'assistant', content: '上一轮的结论' },
156
+ ];
157
+ const globals = createWorkflowContext({
158
+ ctx: { provider: { name: 'test' }, history, cwd: wd, tools: [] } as unknown as RunContext,
159
+ state,
160
+ journal: j2,
161
+ emit: (ev) => events.push(ev),
162
+ args: {},
163
+ workflowName: 'demo',
164
+ session: 's1',
165
+ task: state.taskId,
166
+ });
167
+
168
+ expect(j2.replaying).toBe(true);
169
+ globals.output.message('上一轮的结论'); // 脚本重跑到同一行
170
+
171
+ // 文本照发(用户看得到叙述),但不再重复 push
172
+ expect(events.some((e) => e.type === 'text')).toBe(true);
173
+ expect(events.some((e) => e.type === 'assistant_message')).toBe(false);
174
+ expect(history.filter((m) => m.role === 'assistant')).toHaveLength(1);
175
+ });
176
+
177
+ test('★ 分叉之后(实跑段)恢复 push', async () => {
178
+ const wd = mkdtempSync(join(tmpdir(), 'wf-echo-diverge-'));
179
+ dirs.push(wd);
180
+ const state = new WorkflowState(wd, 'demo', 's1');
181
+ await state.init({ goal: '测试目标' }, 'hash-v1', []);
182
+
183
+ const j1 = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
184
+ await j1.load();
185
+ await j1.step('tool', { a: 1 }, async () => 'r1');
186
+
187
+ const j2 = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
188
+ await j2.load();
189
+ // 指纹失配 → 分叉
190
+ await j2.step('tool', { a: 999 }, async () => 'other');
191
+ expect(j2.replaying).toBe(false);
192
+
193
+ const events: LoopEvent[] = [];
194
+ const history: Message[] = [{ role: 'system', content: 'sys' }];
195
+ const globals = createWorkflowContext({
196
+ ctx: { provider: { name: 'test' }, history, cwd: wd, tools: [] } as unknown as RunContext,
197
+ state,
198
+ journal: j2,
199
+ emit: (ev) => events.push(ev),
200
+ args: {},
201
+ workflowName: 'demo',
202
+ session: 's1',
203
+ task: state.taskId,
204
+ });
205
+
206
+ globals.output.message('新的结论');
207
+ expect(events.some((e) => e.type === 'assistant_message')).toBe(true);
208
+ expect(history.filter((m) => m.role === 'assistant')).toHaveLength(1);
209
+ });
210
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * plugins/workflow-runner/test/apiReadText.test.ts —— readText / readJson
3
+ *
4
+ * 钉死一个只有端到端才暴露的 bug,根因比表面深两层:
5
+ *
6
+ * 表面:`readJson()` 对一个**完全合法**的 JSON 文件返回 `null`,
7
+ * 下游报「JSON 格式错误或数组为空」,而手工 `cat` + `python -m json.tool` 都没问题。
8
+ *
9
+ * 第一层:`Read` 工具的输出是**给模型看**的,每行带 `<行号><TAB>` 前缀 ——
10
+ * `JSON.parse` 必然失败。(剥离正则一度还写成了箭头 `→` 而不是 TAB。)
11
+ *
12
+ * 第二层(真根因):`Read` 在**重复读同一文件**时,会在开头插一句
13
+ * 「⚠️ 重复读取空转:该区间未变化,前一次 Read 结果仍然有效。」
14
+ * 这条只在**第二次**出现,于是同一段代码时对时错。真实触发路径:
15
+ * research 阶段2 的 agent 写完 branches.json 后自己 Read 校验了一遍,
16
+ * 脚本随后 readJson() 就命中提示 → 解析失败 → null。
17
+ *
18
+ * 修法不是去剥这些前缀(那是在追着「给模型看的格式」跑),而是让脚本的
19
+ * 确定性读取**根本不走那条通道** —— 直接读文件。下面第一组就是钉这条的。
20
+ */
21
+
22
+ import { afterEach, describe, expect, test } from 'bun:test';
23
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
24
+ import { tmpdir } from 'node:os';
25
+ import { join } from 'node:path';
26
+
27
+ import { createWorkflowContext } from '../src/runtime/context.js';
28
+ import { Journal } from '../src/journal.js';
29
+ import type { RunContext } from '../src/types.js';
30
+ import type { WorkflowState } from '../src/workflowState.js';
31
+ import type { WorkflowContext } from '../src/workflowContext.js';
32
+
33
+ const dirs: string[] = [];
34
+ afterEach(() => {
35
+ for (const d of dirs.splice(0)) {
36
+ try {
37
+ rmSync(d, { recursive: true, force: true, maxRetries: 3 });
38
+ } catch {
39
+ /* Windows 偶发 EBUSY,清理失败不影响断言 */
40
+ }
41
+ }
42
+ });
43
+
44
+ function workdir(): string {
45
+ const d = mkdtempSync(join(tmpdir(), 'wf-readtext-'));
46
+ dirs.push(d);
47
+ return d;
48
+ }
49
+
50
+ /** 造一份最小 globals,cwd 指向临时目录。 */
51
+ function makeGlobals(cwd: string, workflowDir?: string) {
52
+ const ctx = {
53
+ provider: { name: 'test' },
54
+ cwd,
55
+ history: [],
56
+ tools: [],
57
+ } as unknown as RunContext;
58
+
59
+ return createWorkflowContext({
60
+ ctx,
61
+ state: {
62
+ appendProgress: async () => undefined,
63
+ } as unknown as WorkflowState,
64
+ journal: new Journal(join(cwd, 'journal.jsonl'), { bundleHash: 'test', rootRunId: 'test-root' }),
65
+ emit: () => undefined,
66
+ args: {},
67
+ workflowName: 'demo',
68
+ session: 's',
69
+ task: 't',
70
+ workflowDir,
71
+ });
72
+ }
73
+
74
+ const readTextOf = (g: WorkflowContext) =>
75
+ g.fs.readText as (p: string) => Promise<string | null>;
76
+ const readJsonOf = (g: WorkflowContext) =>
77
+ g.fs.readJson as (p: string) => Promise<unknown>;
78
+
79
+ describe('readText', () => {
80
+ test('返回**纯**文件内容 —— 没有行号、没有任何提示前缀', async () => {
81
+ const wd = workdir();
82
+ writeFileSync(join(wd, 'a.txt'), 'hello\nworld\n', 'utf8');
83
+ expect(await readTextOf(makeGlobals(wd))('a.txt')).toBe('hello\nworld\n');
84
+ });
85
+
86
+ test('★ 连读三次内容完全一致 —— 「重复读取」提示绝不能混进来', async () => {
87
+ // 这是真根因那一条。走 Read 工具的话,第二次开头会多一句
88
+ // 「⚠️ 重复读取空转…」,于是第一次能 parse、第二次就 null。
89
+ const wd = workdir();
90
+ writeFileSync(join(wd, 'x.json'), '{"a":1}', 'utf8');
91
+ const readText = readTextOf(makeGlobals(wd));
92
+ const a = await readText('x.json');
93
+ const b = await readText('x.json');
94
+ const c = await readText('x.json');
95
+ expect(a).toBe('{"a":1}');
96
+ expect(b).toBe(a);
97
+ expect(c).toBe(a);
98
+ });
99
+
100
+ test('相对路径基于工作流 cwd 解析', async () => {
101
+ const wd = workdir();
102
+ mkdirSync(join(wd, 'sub'), { recursive: true });
103
+ writeFileSync(join(wd, 'sub', 'p.md'), '内容', 'utf8');
104
+ expect(await readTextOf(makeGlobals(wd))('sub/p.md')).toBe('内容');
105
+ });
106
+
107
+ test('绝对路径原样使用', async () => {
108
+ const wd = workdir();
109
+ const abs = join(wd, 'abs.txt');
110
+ writeFileSync(abs, 'ok', 'utf8');
111
+ expect(await readTextOf(makeGlobals(workdir()))(abs)).toBe('ok');
112
+ });
113
+
114
+ test('Desktop 工作目录不在包内时仍能读取工作流随附资源', async () => {
115
+ const cwd = workdir();
116
+ const packageRoot = workdir();
117
+ const workflowDir = join(packageRoot, 'workflows', 'demo');
118
+ mkdirSync(join(workflowDir, 'prompts'), { recursive: true });
119
+ writeFileSync(join(workflowDir, 'prompts', 'step.md'), 'bundled prompt', 'utf8');
120
+ const readText = readTextOf(makeGlobals(cwd, workflowDir));
121
+ expect(await readText('workflows/demo/prompts/step.md')).toBe('bundled prompt');
122
+ });
123
+
124
+ test('读不到返回 null,不抛', async () => {
125
+ expect(await readTextOf(makeGlobals(workdir()))('nope.txt')).toBeNull();
126
+ });
127
+ });
128
+
129
+ describe('readJson', () => {
130
+ test('合法 JSON 解析成功', async () => {
131
+ const wd = workdir();
132
+ writeFileSync(join(wd, 'b.json'), '[{"a":1},{"b":2}]', 'utf8');
133
+ expect(await readJsonOf(makeGlobals(wd))('b.json')).toEqual([
134
+ { a: 1 },
135
+ { b: 2 },
136
+ ]);
137
+ });
138
+
139
+ test('★ 连读三次都解析成功 —— 端到端炸在这里', async () => {
140
+ // research 的真实失败:阶段2 的 agent 自己 Read 过 branches.json,
141
+ // 脚本再 readJson 就成了「第二次」,静默返回 null。
142
+ const wd = workdir();
143
+ writeFileSync(
144
+ join(wd, 'branches.json'),
145
+ '[{"name":"甲"},{"name":"乙"}]',
146
+ 'utf8',
147
+ );
148
+ const readJson = readJsonOf(makeGlobals(wd));
149
+ for (let i = 1; i <= 3; i++) {
150
+ const v = (await readJson('branches.json')) as unknown[];
151
+ expect(Array.isArray(v)).toBe(true);
152
+ expect(v).toHaveLength(2);
153
+ }
154
+ });
155
+
156
+ test('多行带缩进的 JSON(真实文件的样子)', async () => {
157
+ const wd = workdir();
158
+ writeFileSync(
159
+ join(wd, 'c.json'),
160
+ '[\n {\n "name": "js-yaml(默认推荐)",\n "safe_name": "js-yaml"\n }\n]\n',
161
+ 'utf8',
162
+ );
163
+ const v = (await readJsonOf(makeGlobals(wd))('c.json')) as {
164
+ name: string;
165
+ }[];
166
+ expect(v[0].name).toBe('js-yaml(默认推荐)');
167
+ });
168
+
169
+ test('内容不是 JSON → null(不抛,让脚本自己决定怎么办)', async () => {
170
+ const wd = workdir();
171
+ writeFileSync(join(wd, 'd.json'), '这不是 JSON', 'utf8');
172
+ expect(await readJsonOf(makeGlobals(wd))('d.json')).toBeNull();
173
+ });
174
+
175
+ test('读不到 → null', async () => {
176
+ expect(await readJsonOf(makeGlobals(workdir()))('nope.json')).toBeNull();
177
+ });
178
+ });
@@ -0,0 +1,184 @@
1
+ /**
2
+ * plugins/workflow-runner/test/apiResourceLedger.test.ts —— 资源账本与重放安全
3
+ *
4
+ * 守两件事:
5
+ * 1. 每次 `agent()` / `llm()` 的用量真的记进 `ctx.resourceLedger`(预算闸的刻度)。
6
+ * 2. **断点续跑重放时不重复记账** —— 这是最容易写错、也最难发现的一条:
7
+ * 记账代码若挪到 `journaled()` 回调外面,每 resume 一次就把历史用量再加一遍,
8
+ * 跑几次之后预算凭空耗尽,而现场看起来一切正常。
9
+ *
10
+ * 用 DI 注入 chat stub(本仓库纪律:不用 mock.module,它会全局泄漏)。
11
+ */
12
+
13
+ import { afterEach, describe, expect, test } from 'bun:test';
14
+ import { mkdtempSync, rmSync } from 'node:fs';
15
+ import { tmpdir } from 'node:os';
16
+ import { join } from 'node:path';
17
+
18
+ import type { LoopEvent } from '../../../src/plugin-sdk.js';
19
+ import { createWorkflowContext } from '../src/runtime/context.js';
20
+ import { Journal } from '../src/journal.js';
21
+ import type { RunContext } from '../src/types.js';
22
+ import { WorkflowState } from '../src/workflowState.js';
23
+
24
+ const dirs: string[] = [];
25
+ afterEach(() => {
26
+ for (const d of dirs.splice(0)) {
27
+ try {
28
+ rmSync(d, { recursive: true, force: true, maxRetries: 3 });
29
+ } catch {
30
+ /* Windows 偶发 EBUSY */
31
+ }
32
+ }
33
+ });
34
+
35
+ /** 每次调用都回报固定用量的 chat stub。 */
36
+ function chatReporting(inputTokens: number, outputTokens: number) {
37
+ return async function* () {
38
+ yield { type: 'text_delta' as const, delta: '做完了' };
39
+ yield { type: 'done' as const, stopReason: 'stop' as const, promptTokens: inputTokens, outputTokens };
40
+ };
41
+ }
42
+
43
+ /** 完全不回报 usage 的 chat stub(很多兼容 provider 就是这样)。 */
44
+ function chatSilent() {
45
+ return async function* () {
46
+ yield { type: 'text_delta' as const, delta: '做完了' };
47
+ yield { type: 'done' as const, stopReason: 'stop' as const };
48
+ };
49
+ }
50
+
51
+ async function harness(chat: RunContext['chat'], opts: { workDir?: string } = {}) {
52
+ const wd = opts.workDir ?? mkdtempSync(join(tmpdir(), 'wf-ledger-'));
53
+ if (!opts.workDir) dirs.push(wd);
54
+ const state = new WorkflowState(wd, 'demo', 's1');
55
+ await state.init({ goal: '账本测试' }, 'hash-v1', []);
56
+ const journal = new Journal(state.journalPath, { bundleHash: 'test', rootRunId: 'test-root' });
57
+ await journal.load();
58
+
59
+ const events: LoopEvent[] = [];
60
+ const ctx = {
61
+ provider: { name: 'test' },
62
+ history: [{ role: 'system', content: 'sys' }],
63
+ cwd: wd,
64
+ tools: [],
65
+ chat,
66
+ } as unknown as RunContext;
67
+
68
+ const globals = createWorkflowContext({
69
+ ctx, state, journal,
70
+ emit: (ev) => events.push(ev),
71
+ args: {}, workflowName: 'demo', session: 's1', task: state.taskId,
72
+ });
73
+
74
+ return { globals, ctx, events, workDir: wd };
75
+ }
76
+
77
+ describe('资源账本', () => {
78
+ test('★ agent() 的用量记进 ctx.resourceLedger', async () => {
79
+ const h = await harness(chatReporting(500, 60));
80
+ await h.globals.model.agent('干点什么');
81
+
82
+ expect(h.ctx.resourceLedger?.inputTokens).toBe(500);
83
+ expect(h.ctx.resourceLedger?.outputTokens).toBe(60);
84
+ expect(h.ctx.resourceLedger?.trustworthy).toBe(true);
85
+ });
86
+
87
+ test('★ 多次调用累计', async () => {
88
+ const h = await harness(chatReporting(100, 10));
89
+ await h.globals.model.agent('第一件事');
90
+ await h.globals.model.agent('第二件事');
91
+ await h.globals.model.generate('顺便总结一下');
92
+
93
+ expect(h.ctx.resourceLedger?.inputTokens).toBe(300);
94
+ expect(h.ctx.resourceLedger?.outputTokens).toBe(30);
95
+ });
96
+
97
+ test('★ provider 不报 usage → trustworthy 拉黑,而不是假装花了 0', async () => {
98
+ const h = await harness(chatSilent());
99
+ await h.globals.model.agent('干点什么');
100
+
101
+ // 记 0 是事实(不知道),但必须让消费方知道这份账偏低 ——
102
+ // 否则预算闸会拿着一份永远为 0 的账本一路放行。
103
+ expect(h.ctx.resourceLedger?.trustworthy).toBe(false);
104
+ });
105
+
106
+ test('★★ 重放不重复记账 —— 记账必须待在 journaled 回调内部', async () => {
107
+ // 第一次跑:真实执行,写账本 + 记用量。
108
+ const first = await harness(chatReporting(1_000, 100));
109
+ await first.globals.model.agent('唯一的一次调用');
110
+ expect(first.ctx.resourceLedger?.inputTokens).toBe(1_000);
111
+
112
+ // 第二次跑:**同一个工作目录**,账本命中重放。
113
+ // journal.step 命中时直接 return prev.result,根本不调 run() ——
114
+ // 所以只要记账在回调内部,重放就天然不会重复计。
115
+ const second = await harness(chatReporting(1_000, 100), { workDir: first.workDir });
116
+ await second.globals.model.agent('唯一的一次调用');
117
+
118
+ // 重放这一次没有真的发请求,也就不该记账。
119
+ // 若把 recordCallUsage 挪到 journaled 外面,这里会变成 1_000(重复计一遍)。
120
+ expect(second.ctx.resourceLedger?.inputTokens ?? 0).toBe(0);
121
+ });
122
+
123
+ test('重放之后新增的调用照常记账 —— 不能因为重放过就整体哑火', async () => {
124
+ const first = await harness(chatReporting(200, 20));
125
+ await first.globals.model.agent('第一步');
126
+
127
+ const second = await harness(chatReporting(200, 20), { workDir: first.workDir });
128
+ await second.globals.model.agent('第一步'); // 命中重放,不记
129
+ await second.globals.model.agent('第二步'); // 新调用,真跑,要记
130
+
131
+ expect(second.ctx.resourceLedger?.inputTokens).toBe(200);
132
+ });
133
+ });
134
+
135
+ /**
136
+ * ── 预算闸的语义边界(用户决策的硬约束)───────────────────────────────
137
+ *
138
+ * 产品决策原话:「一轮对话一旦开始,过程中即使到额度了只要 TAOR 循环不结束
139
+ * 也不停止不检查预算…比起严格限制预算,我更重视让 agent 把一次对话完成不打断,
140
+ * 至少做完到一个节点。」
141
+ *
142
+ * 所以这里既要证明 `budget()` 能报数,也要**反证**它不会打断执行 ——
143
+ * 后者是容易在后续重构中被「顺手加个检查」破坏的那一半。
144
+ */
145
+ describe('budget() 的语义边界', () => {
146
+ test('没设上限时 remaining 是 Infinity、exhausted 为 false', async () => {
147
+ const h = await harness(chatReporting(10_000, 1_000));
148
+ await h.globals.model.agent('花点钱');
149
+ const b = (await h.globals.state.budget()) as Record<string, unknown>;
150
+
151
+ // ⚠️ 用 0 表示「无限」会让没设预算的运行被判成「一开始就超了」,一轮都跑不了。
152
+ expect(b.limit).toBeNull();
153
+ expect(b.remaining).toBe(Number.POSITIVE_INFINITY);
154
+ expect(b.exhausted).toBe(false);
155
+ expect(b.used).toBe(11_000);
156
+ });
157
+
158
+ test('★ 反证:超预算**不会**中断进行中的 TAOR 循环', async () => {
159
+ // 造一个「一轮里连调两次」的场景。即使第一次就把预算花超,
160
+ // 第二次调用照样必须完成 —— 引擎内核不做任何预算检查,
161
+ // 停不停由脚本在**下一轮开头**自己决定。
162
+ const h = await harness(chatReporting(1_000_000, 1_000_000));
163
+ h.ctx.resourceLedger = {
164
+ inputTokens: 0, outputTokens: 0, limit: 1, trustworthy: true,
165
+ };
166
+
167
+ await h.globals.model.agent('第一次调用,一次就爆预算');
168
+ const mid = (await h.globals.state.budget()) as Record<string, unknown>;
169
+ expect(mid.exhausted).toBe(true);
170
+
171
+ // 关键断言:已经 exhausted 了,agent() 仍然正常执行并返回,不抛不挂。
172
+ const out = await h.globals.model.agent('第二次调用,预算已爆但必须跑完');
173
+ expect(typeof out).toBe('string');
174
+ expect(out).toContain('做完了');
175
+ });
176
+
177
+ test('trustworthy 会跟着账本一起透出,供脚本如实告知用户', async () => {
178
+ const h = await harness(chatSilent());
179
+ await h.globals.model.agent('provider 不报 usage');
180
+ const b = (await h.globals.state.budget()) as Record<string, unknown>;
181
+
182
+ expect(b.trustworthy).toBe(false);
183
+ });
184
+ });