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
@@ -1,237 +1,346 @@
1
1
  /**
2
- * ============================================================
3
- * test/taskIdentity.test.ts —— 任务身份判定单元测试
4
- * ------------------------------------------------------------
5
- * 覆盖 taskIdentity.ts 的所有核心功能:
6
- * - computeTaskId() hash 计算
7
- * - 时间窗口判定(30分钟内视为同任务)
8
- * - 任务身份匹配逻辑(hash_match / time_window / expired / different_workflow)
9
- * - normalizeGoal() 归一化规则
10
- * - extractGoal() 提取策略
2
+ * plugins/workflow-runner/test/taskIdentity.test.ts —— 任务身份与状态目录布局
11
3
  *
12
- * 设计原则:
13
- * - 不依赖文件系统(computeTaskId 在无 workingDir 时返回 no_previous)
14
- * - 需要文件系统时用 tmpdir + 手写 current.json
15
- * - 纯函数逻辑用大量边界用例覆盖
16
- * ============================================================
4
+ * 这组测试的核心是**四级布局的三个并存需求**(见 taskIdentity.ts 头注):
5
+ * 同 session 同 goal → 同目录(能 resume)
6
+ * 同 session 换主题 → 不同目录(不覆盖)
7
+ * 不同 session → 不同目录(能并行)
8
+ *
9
+ * legacy 曾把这三条压进两级布局,反复顾此失彼:Phase 2 用 taskId 层解决了「不覆盖」,
10
+ * Phase 3 把那一层**换成** sessionId 换来了「并行」,「不覆盖」就丢了;补偿方案
11
+ * (宿主没给 session 就自动生成带时间戳的)又让「能 resume」也丢了 —— 时间每次都变,
12
+ * 于是每次运行都是新 session。下面「三者并存」那一组就是钉这段历史的。
17
13
  */
18
14
 
19
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
20
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
21
- import { tmpdir } from 'node:os';
22
- import { join } from 'node:path';
15
+ import { afterEach, describe, expect, test } from 'bun:test';
16
+
17
+ import { buildGoalAnchor } from '../src/goalAnchor.js';
18
+ import {
19
+ DEFAULT_SESSION_ID,
20
+ WF_STATE_DIR_ENV,
21
+ buildTaskArbitrator,
22
+ computeTaskId,
23
+ extractGoal,
24
+ isContinuationPhrase,
25
+ resolveStateRoot,
26
+ sanitizeWorkflowDirName,
27
+ sessionStateDir,
28
+ taskInstanceDir,
29
+ } from '../src/taskIdentity.js';
23
30
 
24
- import { computeTaskId } from '../src/taskIdentity.js';
31
+ const WD = process.platform === 'win32' ? 'D:\\work' : '/work';
25
32
 
26
- describe('taskIdentity::normalizeGoal (隐式测试 via computeTaskId)', () => {
27
- it('相同内容产生相同 hash(确定性)', () => {
28
- const r1 = computeTaskId('test-wf', { goal: 'hello world' });
29
- const r2 = computeTaskId('test-wf', { goal: 'hello world' });
30
- expect(r1.hash).toBe(r2.hash);
31
- expect(r1.hash.length).toBe(16);
33
+ describe('状态目录布局', () => {
34
+ const savedEnv = process.env[WF_STATE_DIR_ENV];
35
+ afterEach(() => {
36
+ if (savedEnv === undefined) delete process.env[WF_STATE_DIR_ENV];
37
+ else process.env[WF_STATE_DIR_ENV] = savedEnv;
32
38
  });
33
39
 
34
- it('不同内容产生不同 hash(碰撞敏感度)', () => {
35
- const r1 = computeTaskId('test-wf', { goal: '目标A' });
36
- const r2 = computeTaskId('test-wf', { goal: '目标B' });
37
- expect(r1.hash).not.toBe(r2.hash);
40
+ test('默认落在 cwd 下的 .minimal-agent-workflow/', () => {
41
+ delete process.env[WF_STATE_DIR_ENV];
42
+ expect(resolveStateRoot(WD)).toContain('.minimal-agent-workflow');
38
43
  });
39
44
 
40
- it('大小写不敏感(归一化转小写)', () => {
41
- const r1 = computeTaskId('test-wf', { goal: 'Hello World' });
42
- const r2 = computeTaskId('test-wf', { goal: 'hello world' });
43
- expect(r1.hash).toBe(r2.hash);
45
+ test('环境变量指定的全局目录优先', () => {
46
+ process.env[WF_STATE_DIR_ENV] = WD;
47
+ expect(resolveStateRoot('/somewhere/else')).toBe(WD);
44
48
  });
45
49
 
46
- it('标点符号被去除', () => {
47
- const r1 = computeTaskId('test-wf', { goal: 'Hello, World!' });
48
- const r2 = computeTaskId('test-wf', { goal: 'hello world' });
49
- expect(r1.hash).toBe(r2.hash);
50
+ test('目录名清洗:非 [字母数字._-] 一律换成 -', () => {
51
+ expect(sanitizeWorkflowDirName('my flow/v2:x')).toBe('my-flow-v2-x');
50
52
  });
51
53
 
52
- it('多余空格被压缩', () => {
53
- const r1 = computeTaskId('test-wf', { goal: 'hello world' });
54
- const r2 = computeTaskId('test-wf', { goal: 'hello world' });
55
- expect(r1.hash).toBe(r2.hash);
54
+ test('实例目录是四级:workflow / session / taskId', () => {
55
+ const dir = taskInstanceDir(WD, 'ppt', 'sess-a', 'abc123');
56
+ expect(dir).toContain('ppt');
57
+ expect(dir).toContain('sess-a');
58
+ expect(dir.endsWith('abc123')).toBe(true);
59
+ // taskId 目录必须在 session 目录之内,不是它的兄弟
60
+ expect(dir.startsWith(sessionStateDir(WD, 'ppt', 'sess-a'))).toBe(true);
56
61
  });
62
+ });
63
+
64
+ describe('★ 三个需求必须并存(四级布局的存在理由)', () => {
65
+ const dirFor = (session: string, goal: string): string =>
66
+ taskInstanceDir(
67
+ WD,
68
+ 'research',
69
+ session,
70
+ computeTaskId('research', { goal }),
71
+ );
57
72
 
58
- it('中文保留正常', () => {
59
- const r = computeTaskId('test-wf', { goal: '实现用户认证模块' });
60
- expect(r.hash.length).toBe(16);
61
- // 中文不应被去除
62
- expect(r.hash).toMatch(/^[0-9a-f]{16}$/);
73
+ test('同 session + 同 goal → 同一个目录(resume 才够得着)', () => {
74
+ expect(dirFor('s1', '调研 A 主题')).toBe(dirFor('s1', '调研 A 主题'));
63
75
  });
64
76
 
65
- it('超长输入被截断到 200 字符', () => {
66
- const longGoal = 'a'.repeat(300);
67
- const r1 = computeTaskId('test-wf', { goal: longGoal });
68
- const r2 = computeTaskId('test-wf', { goal: 'a'.repeat(200) });
69
- expect(r1.hash).toBe(r2.hash);
77
+ test('★ 同 session + 换主题 → 不同目录(连续研究不同内容不互相覆盖)', () => {
78
+ expect(dirFor('s1', '调研 A 主题')).not.toBe(dirFor('s1', '调研 B 主题'));
70
79
  });
71
80
 
72
- it('首尾空格被 trim', () => {
73
- const r1 = computeTaskId('test-wf', { goal: ' hello world ' });
74
- const r2 = computeTaskId('test-wf', { goal: 'hello world' });
75
- expect(r1.hash).toBe(r2.hash);
81
+ test('不同 session + 同 goal → 不同目录(并行隔离)', () => {
82
+ expect(dirFor('s1', '调研 A 主题')).not.toBe(dirFor('s2', '调研 A 主题'));
83
+ });
84
+
85
+ test('★ default session 也照常工作,不被替换成带时间戳的名字', () => {
86
+ // legacy 在这里自动生成 `YYYYMMDD-HHmmss-xxxxxx`,导致每次运行都是新 session、
87
+ // resume/completed 永远够不着。四级布局下没有这个必要,也绝不能加回来。
88
+ const a = dirFor(DEFAULT_SESSION_ID, '同一个目标');
89
+ const b = dirFor(DEFAULT_SESSION_ID, '同一个目标');
90
+ expect(a).toBe(b);
91
+ expect(a).toContain(DEFAULT_SESSION_ID);
76
92
  });
77
93
  });
78
94
 
79
- describe('taskIdentity::extractGoal 策略', () => {
80
- it('优先级1:直接使用 inputs.goal', () => {
81
- const r1 = computeTaskId('wf', { goal: '我的目标', other: '忽略这个' });
82
- const r2 = computeTaskId('wf', { goal: '我的目标', other: '不同的值' });
83
- expect(r1.hash).toBe(r2.hash); // 只看 goal
95
+ describe('computeTaskId', () => {
96
+ test('纯函数:同输入恒等,不读盘不看时间', () => {
97
+ const a = computeTaskId('w', { goal: 'x' });
98
+ const b = computeTaskId('w', { goal: 'x' });
99
+ expect(a).toBe(b);
100
+ expect(a).toHaveLength(16);
101
+ });
102
+
103
+ test('工作流名参与哈希 —— 不同工作流的同一目标不是同一个任务', () => {
104
+ expect(computeTaskId('a', { goal: 'x' })).not.toBe(
105
+ computeTaskId('b', { goal: 'x' }),
106
+ );
84
107
  });
85
108
 
86
- it('优先级2:无 goal 时取第一个 length > 2 的 string 值', () => {
87
- const r1 = computeTaskId('wf', { short: 'ab', description: '这是一个较长的描述' });
88
- // 应该取 description(length > 2),而不是 short
89
- const r2 = computeTaskId('wf', { description: '这是一个较长的描述' });
90
- expect(r1.hash).toBe(r2.hash);
109
+ test.each([
110
+ ['大小写', '调研 Foo', '调研 foo'],
111
+ ['尾部标点', '调研 foo!!!', '调研 foo'],
112
+ ['连续空格', '调研 foo', '调研 foo'],
113
+ ['首尾空白', ' 调研 foo ', '调研 foo'],
114
+ ])('归一化消除无意义差异:%s', (_label, a, b) => {
115
+ expect(computeTaskId('w', { goal: a })).toBe(
116
+ computeTaskId('w', { goal: b }),
117
+ );
91
118
  });
92
119
 
93
- it('优先级3:所有 string 都太短时 JSON 序列化整个 inputs', () => {
94
- const r1 = computeTaskId('wf', { a: 'x', b: 'y' });
95
- const r2 = computeTaskId('wf', { a: 'x', b: 'y' });
96
- expect(r1.hash).toBe(r2.hash); // 确定性
97
- expect(r1.hash.length).toBe(16);
120
+ test('标点是被【删除】而不是换成空格 —— 记录真实边界,别想当然', () => {
121
+ // `调研:foo` 归一化成 `调研foo`(不含空格),因此它与 `调研 foo` **不**同任务。
122
+ // 对中日韩文本这样处理没问题(本就不用空格分词),但拉丁文本里
123
+ // `foo,bar` 会并成 `foobar`。这是自 legacy 原样移植的行为,不在本次改动范围内;
124
+ // 真要改,得先想清楚它会让所有既有 taskId 全部变化。
125
+ expect(computeTaskId('w', { goal: '调研:foo' })).toBe(
126
+ computeTaskId('w', { goal: '调研foo' }),
127
+ );
128
+ expect(computeTaskId('w', { goal: '调研:foo' })).not.toBe(
129
+ computeTaskId('w', { goal: '调研 foo' }),
130
+ );
98
131
  });
99
132
 
100
- it('空 inputs 也能处理', () => {
101
- const r = computeTaskId('wf', {});
102
- expect(r.hash.length).toBe(16);
133
+ test('★ 语义不同就是不同任务 —— 时间窗口不得覆盖内容差异', () => {
134
+ // legacy 早期「30 分钟内视为同一任务」是 bug:半小时内换个完全不同的目标
135
+ // 重跑,会 resume 旧任务并把旧变量混进新目标。已移除,别加回来。
136
+ expect(computeTaskId('w', { goal: '写一份 PPT' })).not.toBe(
137
+ computeTaskId('w', { goal: '修一个 bug' }),
138
+ );
103
139
  });
104
140
 
105
- it('非 string 值被跳过(number/boolean/null)', () => {
106
- const r1 = computeTaskId('wf', { count: 42, flag: true });
107
- const r2 = computeTaskId('wf', {});
108
- // 没有可用的 string → 都走 JSON.stringify
109
- // 但序列化结果不同,所以 hash 不同是正常的
110
- expect(r1.hash.length).toBe(16);
111
- expect(r2.hash.length).toBe(16);
141
+ test('★ 同一句请求配不同附件内容必须是不同任务', () => {
142
+ const defs = [
143
+ { name: 'source', type: 'resource' as const, required: true },
144
+ ];
145
+ const base = {
146
+ source: '/runtime/paper.jpg',
147
+ requestText: '帮我整理成 Word',
148
+ };
149
+ expect(
150
+ computeTaskId(
151
+ 'wrong-question-docx',
152
+ { ...base, __resource_fingerprint: 'source:sha-a' },
153
+ defs,
154
+ ),
155
+ ).not.toBe(
156
+ computeTaskId(
157
+ 'wrong-question-docx',
158
+ { ...base, __resource_fingerprint: 'source:sha-b' },
159
+ defs,
160
+ ),
161
+ );
162
+ expect(
163
+ buildGoalAnchor(
164
+ { ...base, __resource_fingerprint: 'source:sha-a' },
165
+ defs,
166
+ ),
167
+ ).toContain('帮我整理成 Word');
112
168
  });
113
- });
114
169
 
115
- describe('taskIdentity::无 workingDir 基础行为', () => {
116
- it('无 workingDir → isSameTask=false, reason=no_previous', () => {
117
- const r = computeTaskId('test-wf', { goal: 'hello' });
118
- expect(r.isSameTask).toBe(false);
119
- expect(r.reason).toBe('no_previous');
170
+ test('超长 goal 截断到 200 字 —— 靠后的细微差异不该分裂同一件事', () => {
171
+ const base = '主'.repeat(200);
172
+ expect(computeTaskId('w', { goal: `${base}甲` })).toBe(
173
+ computeTaskId('w', { goal: `${base}乙` }),
174
+ );
120
175
  });
121
176
 
122
- it('不同 workflow 名产生不同 hash', () => {
123
- const r1 = computeTaskId('wf-a', { goal: 'same goal' });
124
- const r2 = computeTaskId('wf-b', { goal: 'same goal' });
125
- expect(r1.hash).not.toBe(r2.hash); // workflow 名参与 hash
177
+ test('资源工作流相同输入不同处理参数不能命中旧completed', () => {
178
+ const defs = [
179
+ { name: 'source', type: 'resource' as const, required: true },
180
+ {
181
+ name: 'format',
182
+ type: 'enum' as const,
183
+ values: ['pdf', 'docx'],
184
+ default: 'pdf',
185
+ },
186
+ { name: 'range' },
187
+ ];
188
+ const base = {
189
+ source: '/runtime/image.png',
190
+ requestText: '转换文件',
191
+ __resource_fingerprint: 'same-bytes',
192
+ format: 'pdf',
193
+ };
194
+ expect(computeTaskId('converter', { ...base, range: '1' }, defs)).not.toBe(
195
+ computeTaskId('converter', { ...base, range: '2' }, defs),
196
+ );
197
+ expect(computeTaskId('converter', base, defs)).not.toBe(
198
+ computeTaskId('converter', { ...base, format: 'docx' }, defs),
199
+ );
200
+ expect(computeTaskId('converter', base, defs)).toBe(
201
+ computeTaskId(
202
+ 'converter',
203
+ { ...base, source: '/another/staged.png' },
204
+ [...defs].reverse(),
205
+ ),
206
+ );
126
207
  });
127
208
  });
128
209
 
129
- describe('taskIdentity::文件系统交互(时间窗口 + hash 匹配)', () => {
130
- let tmp: string;
210
+ describe('extractGoal', () => {
211
+ test('显式 goal 优先', () => {
212
+ expect(extractGoal({ topic: '别的', goal: '真目标' })).toBe('真目标');
213
+ });
131
214
 
132
- beforeEach(() => {
133
- tmp = mkdtempSync(join(tmpdir(), 'task-id-'));
215
+ test('无 goal 时优先取作者标了 required 的输入', () => {
216
+ // 作者用 required 表达了「这是主要输入」,比「第一个碰到的字符串」可靠得多
217
+ expect(
218
+ extractGoal({ lang: 'zh', topic: '调研主题' }, [
219
+ { name: 'lang' },
220
+ { name: 'topic', required: true },
221
+ ]),
222
+ ).toBe('调研主题');
134
223
  });
135
224
 
136
- afterEach(() => {
137
- rmSync(tmp, { recursive: true, force: true });
138
- });
139
-
140
- function writeCurrentState(data: {
141
- taskId?: string;
142
- name?: string;
143
- startedAt?: string;
144
- status?: string;
145
- }): void {
146
- // E6 后状态目录按 workflow 名隔离:computeTaskId('test-wf', …) 读 test-wf/ 子目录
147
- const stateDir = join(tmp, '.minimal-agent-workflow', 'test-wf');
148
- mkdirSync(stateDir, { recursive: true });
149
- writeFileSync(
150
- join(stateDir, 'current.json'),
151
- JSON.stringify(data, null, 2),
152
- 'utf8',
153
- );
154
- }
225
+ test('没有 required 声明时退到第一个非空字符串', () => {
226
+ expect(extractGoal({ n: 5, topic: '主题' })).toBe('主题');
227
+ });
155
228
 
156
- it('无 current.json → no_previous', () => {
157
- const r = computeTaskId('test-wf', { goal: 'hello' }, tmp);
158
- expect(r.isSameTask).toBe(false);
159
- expect(r.reason).toBe('no_previous');
229
+ test('★ 两个字的中文目标必须取到 —— 曾被 >2 的长度阈值跳过', () => {
230
+ // 那条阈值是照拉丁文本想的(跳过 ab、语言代码),对中文直接失效:
231
+ // `世界`、`调研` 这种目标会被跳过去落到 JSON 兜底,于是 current.json 里
232
+ // 记的目标变成一坨 JSON、仲裁时喂给 LLM 的也是那坨 JSON。端到端实跑复现过。
233
+ expect(extractGoal({ name: '世界' })).toBe('世界');
234
+ expect(extractGoal({ name: '世界', times: 3 })).not.toContain('{');
160
235
  });
161
236
 
162
- it('不同 workflow 名 → different_workflow', () => {
163
- writeCurrentState({
164
- name: 'other-wf',
165
- taskId: 'abc123',
166
- startedAt: new Date().toISOString(),
167
- });
168
- const r = computeTaskId('test-wf', { goal: 'hello' }, tmp);
169
- expect(r.isSameTask).toBe(false);
170
- expect(r.reason).toBe('different_workflow');
237
+ test('全是非字符串 → 回退整体 JSON(保证恒有确定值)', () => {
238
+ expect(extractGoal({ pages: 12 })).toBe('{"pages":12}');
171
239
  });
172
240
 
173
- it('时间窗口内但 goal 不同 → 仍判不同任务(hash 权威,recency 不覆盖)', () => {
174
- // 刚刚启动的任务,但这次换了完全不同的 goal
175
- writeCurrentState({
176
- name: 'test-wf',
177
- taskId: 'old-hash',
178
- startedAt: new Date().toISOString(),
179
- });
180
- const r = computeTaskId('test-wf', { goal: '完全不同的新目标' }, tmp);
181
- // 30 分钟内换不同 goal 必须当成全新任务(需求 3.2「不相关新任务自动清理重开」),
182
- // 而非旧版「时间窗口覆盖 hash」错误 resume 旧任务把旧变量混进新目标。
183
- expect(r.isSameTask).toBe(false);
184
- expect(r.reason).toBe('hash_diff');
185
- expect(r.hash).not.toBe('old-hash');
186
- });
187
-
188
- it('时间窗口过期 + hash 匹配 → hash_match', () => {
189
- // 31 分钟前启动的任务(超出 30 分钟窗口)
190
- const pastDate = new Date(Date.now() - 31 * 60 * 1000);
191
- const expectedHash = computeTaskId('test-wf', { goal: 'same goal' }).hash;
192
-
193
- writeCurrentState({
194
- name: 'test-wf',
195
- taskId: expectedHash,
196
- startedAt: pastDate.toISOString(),
241
+ test('★ 与 buildGoalAnchor 共用同一份规则(两处分家会让身份与锚指向不同目标)', () => {
242
+ const inputs = { lang: 'zh', topic: '调研主题' };
243
+ const defs = [{ name: 'lang' }, { name: 'topic', required: true }];
244
+ expect(buildGoalAnchor(inputs, defs)).toContain(extractGoal(inputs, defs));
245
+ });
246
+ });
247
+
248
+ describe('isContinuationPhrase', () => {
249
+ test.each(['继续', '继续吧', '接着做', 'continue', 'RESUME', '继续 执行'])(
250
+ '「%s」是延续指令',
251
+ (s) => {
252
+ expect(isContinuationPhrase(s)).toBe(true);
253
+ },
254
+ );
255
+
256
+ test.each(['写一份关于继续教育的报告', '调研 A 主题', ''])(
257
+ '「%s」不是延续指令',
258
+ (s) => {
259
+ expect(isContinuationPhrase(s)).toBe(false);
260
+ },
261
+ );
262
+
263
+ test('★ 长句里出现「继续」二字不算延续指令(否则会误 resume 掉真正的新任务)', () => {
264
+ expect(isContinuationPhrase('继续教育政策的调研报告怎么写')).toBe(false);
265
+ });
266
+ });
267
+
268
+ describe('任务仲裁链', () => {
269
+ const actx = {
270
+ workflowName: 'research',
271
+ prevGoal: '调研 A 主题',
272
+ newGoal: '继续',
273
+ completedCalls: 7,
274
+ artifacts: ['out/a.md'],
275
+ };
276
+ const provider = { name: 'test' } as never;
277
+
278
+ /** 造一个只吐固定文本的 chat stub(DI,不用 mock.module)。 */
279
+ const chatStub = (text: string) =>
280
+ async function* () {
281
+ yield { type: 'text_delta' as const, delta: text };
282
+ };
283
+
284
+ test('a. 延续短语走快路径,不花 LLM', async () => {
285
+ let llmCalled = false;
286
+ const arb = buildTaskArbitrator({
287
+ provider,
288
+ chat: (() => {
289
+ llmCalled = true;
290
+ return chatStub('{"verdict":"new"}')();
291
+ }) as never,
197
292
  });
198
- const r = computeTaskId('test-wf', { goal: 'same goal' }, tmp);
199
- expect(r.isSameTask).toBe(true);
200
- expect(r.reason).toBe('hash_match');
293
+ expect(await arb(actx)).toBe('resume');
294
+ expect(llmCalled).toBe(false);
201
295
  });
202
296
 
203
- it('hash 不匹配 → 不同任务(hash_diff,与时间无关)', () => {
204
- const pastDate = new Date(Date.now() - 31 * 60 * 1000);
205
- writeCurrentState({
206
- name: 'test-wf',
207
- taskId: 'some-old-hash',
208
- startedAt: pastDate.toISOString(),
297
+ test('b. LLM 给出明确判定就采纳', async () => {
298
+ const arb = buildTaskArbitrator({
299
+ provider,
300
+ chat: chatStub('{"verdict":"new","reason":"无关"}') as never,
209
301
  });
210
- const r = computeTaskId('test-wf', { goal: 'completely new target' }, tmp);
211
- expect(r.isSameTask).toBe(false);
212
- expect(r.reason).toBe('hash_diff');
302
+ expect(await arb({ ...actx, newGoal: '换个完全不同的题目' })).toBe('new');
213
303
  });
214
304
 
215
- it('损坏的 current.json → 容错返回 no_previous', () => {
216
- const stateDir = join(tmp, '.minimal-agent-workflow', 'test-wf');
217
- mkdirSync(stateDir, { recursive: true });
218
- writeFileSync(join(stateDir, 'current.json'), '{invalid json', 'utf8');
305
+ test('LLM 输出被 markdown 代码块包着也能解析', async () => {
306
+ const arb = buildTaskArbitrator({
307
+ provider,
308
+ chat: chatStub('```json\n{"verdict":"resume"}\n```') as never,
309
+ });
310
+ expect(await arb({ ...actx, newGoal: '把它做完,另外改成 12 页' })).toBe(
311
+ 'resume',
312
+ );
313
+ });
219
314
 
220
- const r = computeTaskId('test-wf', { goal: 'hello' }, tmp);
221
- expect(r.isSameTask).toBe(false);
222
- expect(r.reason).toBe('no_previous');
315
+ test('c. LLM 说 unsure → 降级问用户', async () => {
316
+ const arb = buildTaskArbitrator({
317
+ provider,
318
+ chat: chatStub('{"verdict":"unsure"}') as never,
319
+ executeTool: (async () => ({
320
+ ok: true,
321
+ content: 'A1: 继续上次任务',
322
+ })) as never,
323
+ });
324
+ expect(await arb({ ...actx, newGoal: '别的' })).toBe('resume');
223
325
  });
224
326
 
225
- it('current.json 缺少 startedAt 字段 → 降级为 hash 比较', () => {
226
- const expectedHash = computeTaskId('test-wf', { goal: 'target' }).hash;
227
- writeCurrentState({
228
- name: 'test-wf',
229
- taskId: expectedHash,
230
- // 没有 startedAt
327
+ test('★ d. 全都不可用 → 兜底 new,绝不 halt 工作流', async () => {
328
+ // 仲裁只是个辅助判断。让它有能力中断整条工作流是本末倒置。
329
+ const boom = () => {
330
+ throw new Error('不可用');
331
+ };
332
+ const arb = buildTaskArbitrator({
333
+ provider,
334
+ chat: boom as never,
335
+ executeTool: boom as never,
231
336
  });
232
- const r = computeTaskId('test-wf', { goal: 'target' }, tmp);
233
- // 无 startedAt 时时间窗口检查不通过,走 hash 匹配
234
- expect(r.isSameTask).toBe(true);
235
- expect(r.reason).toBe('hash_match');
337
+ expect(await arb({ ...actx, newGoal: '别的' })).toBe('new');
338
+ });
339
+
340
+ test('仲裁过程留下人话记录(用户要看得见为什么这么判)', async () => {
341
+ const notes: string[] = [];
342
+ const arb = buildTaskArbitrator({ provider, notes });
343
+ await arb(actx);
344
+ expect(notes.join('\n')).toContain('延续短语');
236
345
  });
237
346
  });
@@ -0,0 +1,106 @@
1
+ import { expect, test } from 'bun:test';
2
+ import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { workflowFingerprint } from '../src/workflowFingerprint.js';
6
+ import { WorkflowState } from '../src/workflowState.js';
7
+
8
+ test('bundle changes invalidate resume AND completed; output changes do not', async () => {
9
+ const dir = await mkdtemp(join(tmpdir(), 'wf-fingerprint-'));
10
+ try {
11
+ await mkdir(join(dir, 'scripts'));
12
+ await mkdir(join(dir, 'prompts'));
13
+ await writeFile(join(dir, 'scripts', 'helper.mjs'), 'v1');
14
+ const source = 'echo("ok")';
15
+ const before = await workflowFingerprint(source, dir);
16
+ const state = new WorkflowState(dir, 'test', 'session');
17
+ await state.init({ goal: 'same' }, before, []);
18
+ await state.markDone([]);
19
+ await state.archive();
20
+ expect(
21
+ (
22
+ await new WorkflowState(dir, 'test', 'session').init(
23
+ { goal: 'same' },
24
+ before,
25
+ [],
26
+ )
27
+ ).mode,
28
+ ).toBe('completed');
29
+ await writeFile(join(dir, 'scripts', 'helper.mjs'), 'v2');
30
+ const after = await workflowFingerprint(source, dir);
31
+ expect(after).not.toBe(before);
32
+ const changed = await new WorkflowState(dir, 'test', 'session').init(
33
+ { goal: 'same' },
34
+ after,
35
+ [],
36
+ );
37
+ expect(changed.scriptChanged).toBe(true);
38
+ await writeFile(join(dir, 'README.md'), 'not an executable dependency');
39
+ expect(await workflowFingerprint(source, dir)).toBe(after);
40
+ await writeFile(join(dir, 'prompts', 'qa.md'), 'new acceptance contract');
41
+ expect(await workflowFingerprint(source, dir)).not.toBe(after);
42
+ expect(await workflowFingerprint('new source', dir)).not.toBe(
43
+ await workflowFingerprint(source, dir),
44
+ );
45
+ } finally {
46
+ await rm(dir, { recursive: true, force: true });
47
+ }
48
+ });
49
+
50
+ test('bundle symlinks fail visibly instead of reusing untracked external dependencies', async () => {
51
+ const dir = await mkdtemp(join(tmpdir(), 'wf-link-'));
52
+ try {
53
+ await mkdir(join(dir, 'scripts'));
54
+ await writeFile(join(dir, 'external.mjs'), 'v1');
55
+ await symlink(
56
+ join(dir, 'external.mjs'),
57
+ join(dir, 'scripts', 'linked.mjs'),
58
+ );
59
+ await expect(workflowFingerprint('source', dir)).rejects.toThrow(
60
+ '符号链接',
61
+ );
62
+ } finally {
63
+ await rm(dir, { recursive: true, force: true });
64
+ }
65
+ });
66
+
67
+ test('import of the host package itself must be rejected — bypasses wf entirely, declaring it does not help', async () => {
68
+ // package.json::exports 里的 "./src/*"/"./plugins/*" 通配符 + Node/Bun 包
69
+ // 自引用会让 `minimal-agent/src/xxx` 解析到真实内核文件,完全绕开 wf 分组
70
+ // 能力对象对调度/记账/心跳/预算的管控——这是 scriptGuard.mjs 同名检查的
71
+ // 第二道闸,验证即使调用方跳过了 discovery.ts 的静态校验,这里仍然拦得住。
72
+ const dir = await mkdtemp(join(import.meta.dir, 'wf-selfref-'));
73
+ try {
74
+ const source = "import { runQuery } from 'minimal-agent/src/loop.js';\n";
75
+ await expect(workflowFingerprint(source, dir)).rejects.toThrow('宿主引擎自身');
76
+ // 声明进 meta.packages 也无法绕过——这不是"没声明"的问题。
77
+ await expect(
78
+ workflowFingerprint(source, dir, ['minimal-agent']),
79
+ ).rejects.toThrow('宿主引擎自身');
80
+ } finally {
81
+ await rm(dir, { recursive: true, force: true });
82
+ }
83
+ });
84
+
85
+ test('bare packages imported by relative helpers must be declared and versioned', async () => {
86
+ // ⚠️ 必须落在仓库树内(而不是 OS 系统临时目录):`workflowFingerprint` 校验
87
+ // `meta.packages` 时用 `createRequire(entry).resolve(name)`,从 fixture 目录
88
+ // 向上找 node_modules——系统临时目录(如 Windows 的 %TEMP%)没有任何
89
+ // node_modules 祖先,会让这里恒定解析失败,与"包名没声明"这个真正要测的
90
+ // 场景无关。落在 `test/` 下能向上找到仓库根的 node_modules(含 yaml)。
91
+ const dir = await mkdtemp(join(import.meta.dir, 'wf-package-'));
92
+ try {
93
+ await mkdir(join(dir, 'scripts'));
94
+ await writeFile(
95
+ join(dir, 'scripts', 'helper.mjs'),
96
+ "import YAML from 'yaml'; export const parse = YAML.parse;\n",
97
+ );
98
+ const source = "import './scripts/helper.mjs';\n";
99
+ await expect(workflowFingerprint(source, dir)).rejects.toThrow(
100
+ '必须声明在 meta.packages',
101
+ );
102
+ expect(await workflowFingerprint(source, dir, ['yaml'])).toHaveLength(16);
103
+ } finally {
104
+ await rm(dir, { recursive: true, force: true });
105
+ }
106
+ });