minimal-agent 0.10.0 → 0.12.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 (573) hide show
  1. package/README.md +11 -3
  2. package/package.json +52 -5
  3. package/plugins/computer_use/.claude-plugin/plugin.json +6 -0
  4. package/plugins/computer_use/README.md +94 -0
  5. package/plugins/computer_use/app/_template.md +142 -0
  6. package/plugins/computer_use/app/chrome.md +165 -0
  7. package/plugins/computer_use/app/finder.md +76 -0
  8. package/plugins/computer_use/app/macos-system-settings.md +81 -0
  9. package/plugins/computer_use/app/microsoft-excel.md +80 -0
  10. package/plugins/computer_use/app/microsoft-powerpoint.md +80 -0
  11. package/plugins/computer_use/app/microsoft-word.md +79 -0
  12. package/plugins/computer_use/app/wechat.md +413 -0
  13. package/plugins/computer_use/app/weread.md +319 -0
  14. package/plugins/computer_use/commands/computeruse.md +25 -0
  15. package/plugins/computer_use/commands/cu.md +9 -0
  16. package/plugins/computer_use/plugin.js +26 -0
  17. package/plugins/computer_use/plugin.ts +30 -0
  18. package/plugins/computer_use/src/capture.js +90 -0
  19. package/plugins/computer_use/src/capture.ts +104 -0
  20. package/plugins/computer_use/src/commands.js +91 -0
  21. package/plugins/computer_use/src/commands.ts +103 -0
  22. package/plugins/computer_use/src/daemon.js +134 -0
  23. package/plugins/computer_use/src/daemon.ts +161 -0
  24. package/plugins/computer_use/src/manuals.js +135 -0
  25. package/plugins/computer_use/src/manuals.ts +159 -0
  26. package/plugins/computer_use/src/verify.js +168 -0
  27. package/plugins/computer_use/src/verify.ts +219 -0
  28. package/plugins/computer_use/test/commands.test.ts +73 -0
  29. package/plugins/cron-runner/plugin.ts +38 -0
  30. package/plugins/cron-runner/src/commands.ts +373 -0
  31. package/plugins/cron-runner/src/cronTime.ts +155 -0
  32. package/plugins/cron-runner/src/lock.ts +79 -0
  33. package/plugins/cron-runner/src/runs.ts +99 -0
  34. package/plugins/cron-runner/src/scheduleEngine.ts +76 -0
  35. package/plugins/cron-runner/src/scheduler.ts +376 -0
  36. package/plugins/cron-runner/src/schema.ts +139 -0
  37. package/plugins/cron-runner/src/service.ts +193 -0
  38. package/plugins/cron-runner/src/store.ts +333 -0
  39. package/plugins/cron-runner/src/types.ts +141 -0
  40. package/plugins/cron-runner/test/cronTime.test.ts +41 -0
  41. package/plugins/cron-runner/test/plugin-integration.test.ts +47 -0
  42. package/plugins/cron-runner/test/scheduleEngine.test.ts +41 -0
  43. package/plugins/cron-runner/test/service.test.ts +76 -0
  44. package/plugins/cron-runner/test/store-and-scheduler.test.ts +501 -0
  45. package/plugins/workflow-runner/plugin.js +56 -2
  46. package/plugins/workflow-runner/plugin.ts +113 -0
  47. package/plugins/workflow-runner/src/agentCommon.js +43 -4
  48. package/plugins/workflow-runner/src/agentCommon.ts +435 -0
  49. package/plugins/workflow-runner/src/controlFlow/branch.js +40 -0
  50. package/plugins/workflow-runner/src/controlFlow/branch.ts +46 -0
  51. package/plugins/workflow-runner/src/controlFlow/loop.js +211 -0
  52. package/plugins/workflow-runner/src/controlFlow/loop.ts +248 -0
  53. package/plugins/workflow-runner/src/controlFlow/pause.js +129 -0
  54. package/plugins/workflow-runner/src/controlFlow/pause.ts +141 -0
  55. package/plugins/workflow-runner/src/controlFlow/signal.js +21 -0
  56. package/plugins/workflow-runner/src/controlFlow/signal.ts +29 -0
  57. package/plugins/workflow-runner/src/controlFlow/types.js +18 -0
  58. package/plugins/workflow-runner/src/controlFlow/types.ts +63 -0
  59. package/plugins/workflow-runner/src/dslManifest.ts +127 -0
  60. package/plugins/workflow-runner/src/expressions.ts +622 -0
  61. package/plugins/workflow-runner/src/index.js +13 -4
  62. package/plugins/workflow-runner/src/index.ts +338 -0
  63. package/plugins/workflow-runner/src/jsonPayload.ts +84 -0
  64. package/plugins/workflow-runner/src/loader.js +23 -0
  65. package/plugins/workflow-runner/src/loader.ts +721 -0
  66. package/plugins/workflow-runner/src/multimodal.js +2 -1
  67. package/plugins/workflow-runner/src/multimodal.ts +64 -0
  68. package/plugins/workflow-runner/src/recovery.ts +149 -0
  69. package/plugins/workflow-runner/src/runner.js +75 -659
  70. package/plugins/workflow-runner/src/runner.ts +604 -0
  71. package/plugins/workflow-runner/src/stepExecutors/agent.ts +234 -0
  72. package/plugins/workflow-runner/src/stepExecutors/assert.ts +50 -0
  73. package/plugins/workflow-runner/src/stepExecutors/echo.js +31 -0
  74. package/plugins/workflow-runner/src/stepExecutors/echo.ts +38 -0
  75. package/plugins/workflow-runner/src/stepExecutors/llm.ts +133 -0
  76. package/plugins/workflow-runner/src/stepExecutors/set.js +49 -0
  77. package/plugins/workflow-runner/src/stepExecutors/set.ts +56 -0
  78. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  79. package/plugins/workflow-runner/src/stepExecutors/skill.ts +140 -0
  80. package/plugins/workflow-runner/src/stepExecutors/tool.js +80 -8
  81. package/plugins/workflow-runner/src/stepExecutors/tool.ts +256 -0
  82. package/plugins/workflow-runner/src/stepExecutors/verify.ts +68 -0
  83. package/plugins/workflow-runner/src/stepPipeline.js +277 -0
  84. package/plugins/workflow-runner/src/stepPipeline.ts +366 -0
  85. package/plugins/workflow-runner/src/taskIdentity.ts +405 -0
  86. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  87. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  88. package/plugins/workflow-runner/src/types.js +37 -1
  89. package/plugins/workflow-runner/src/types.ts +602 -0
  90. package/plugins/workflow-runner/src/workflowState.ts +733 -0
  91. package/plugins/workflow-runner/test/agent.test.ts +587 -0
  92. package/plugins/workflow-runner/test/agentLlmError.test.ts +373 -0
  93. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +65 -0
  94. package/plugins/workflow-runner/test/captureMisbind.test.ts +99 -0
  95. package/plugins/workflow-runner/test/cli.e2e.test.ts +147 -0
  96. package/plugins/workflow-runner/test/context-files.test.ts +320 -0
  97. package/plugins/workflow-runner/test/e2e.test.ts +370 -0
  98. package/plugins/workflow-runner/test/engineRobustness.test.ts +226 -0
  99. package/plugins/workflow-runner/test/expressions.test.ts +397 -0
  100. package/plugins/workflow-runner/test/foundation.test.ts +126 -0
  101. package/plugins/workflow-runner/test/graceful.test.ts +139 -0
  102. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +47 -0
  103. package/plugins/workflow-runner/test/hostCapability.test.ts +108 -0
  104. package/plugins/workflow-runner/test/illustrate.load.test.ts +183 -0
  105. package/plugins/workflow-runner/test/jsonPayload.test.ts +83 -0
  106. package/plugins/workflow-runner/test/loader.test.ts +617 -0
  107. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +33 -0
  108. package/plugins/workflow-runner/test/multimodal.test.ts +342 -0
  109. package/plugins/workflow-runner/test/output-schema.test.ts +213 -0
  110. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +87 -0
  111. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +269 -0
  112. package/plugins/workflow-runner/test/ppt.load.test.ts +1220 -0
  113. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +220 -0
  114. package/plugins/workflow-runner/test/recovery.test.ts +265 -0
  115. package/plugins/workflow-runner/test/runner.test.ts +911 -0
  116. package/plugins/workflow-runner/test/runnerPreflight.test.ts +379 -0
  117. package/plugins/workflow-runner/test/skillTurnDone.test.ts +71 -0
  118. package/plugins/workflow-runner/test/stateLifecycle.test.ts +150 -0
  119. package/plugins/workflow-runner/test/stepPipeline.test.ts +114 -0
  120. package/plugins/workflow-runner/test/taskArbitration.test.ts +338 -0
  121. package/plugins/workflow-runner/test/taskIdentity.test.ts +237 -0
  122. package/plugins/workflow-runner/test/taskLifecycle.test.ts +270 -0
  123. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +221 -0
  124. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +29 -0
  125. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  126. package/plugins/workflow-runner/test/webppt.load.test.ts +250 -0
  127. package/plugins/workflow-runner/test/word.load.test.ts +208 -0
  128. package/plugins/workflow-runner/test/workflows.smoke.test.ts +76 -0
  129. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +207 -0
  130. package/skills/browser-automation/SKILL.md +9 -19
  131. package/skills/web-crawler/SKILL.md +9 -11
  132. package/src/askBridge.ts +249 -0
  133. package/src/bootstrap/cwdArg.js +10 -3
  134. package/src/bootstrap/cwdArg.ts +31 -0
  135. package/src/bootstrap/workingDir.ts +48 -0
  136. package/src/cli/args.js +70 -7
  137. package/src/cli/args.ts +146 -0
  138. package/src/cli/computerUse.js +185 -0
  139. package/src/cli/computerUse.ts +221 -0
  140. package/src/cli/configMenu.js +274 -202
  141. package/src/cli/configMenu.tsx +521 -0
  142. package/src/cli/memoryAccess.js +45 -0
  143. package/src/cli/memoryAccess.ts +53 -0
  144. package/src/cli/print.js +119 -5
  145. package/src/cli/print.ts +597 -0
  146. package/src/cli/quickModelSetup.js +60 -0
  147. package/src/cli/quickModelSetup.tsx +96 -0
  148. package/src/cli/streamJson.js +80 -4
  149. package/src/cli/streamJson.ts +433 -0
  150. package/src/cli/wizards/advisorWizard.js +1 -0
  151. package/src/cli/wizards/advisorWizard.tsx +195 -0
  152. package/src/cli/wizards/agentRuntimeWizard.js +84 -0
  153. package/src/cli/wizards/agentRuntimeWizard.tsx +134 -0
  154. package/src/cli/wizards/browserWizard.js +55 -0
  155. package/src/cli/wizards/browserWizard.tsx +77 -0
  156. package/src/cli/wizards/contextAdvisorWizard.js +84 -0
  157. package/src/cli/wizards/contextAdvisorWizard.tsx +124 -0
  158. package/src/cli/wizards/fieldForm.js +7 -4
  159. package/src/cli/wizards/fieldForm.tsx +250 -0
  160. package/src/cli/wizards/imageWizard.js +4 -5
  161. package/src/cli/wizards/imageWizard.tsx +96 -0
  162. package/src/cli/wizards/openRouterWizard.tsx +48 -0
  163. package/src/cli/wizards/providerPresets.js +1 -8
  164. package/src/cli/wizards/providerPresets.ts +106 -0
  165. package/src/cli/wizards/providerWizard.js +152 -205
  166. package/src/cli/wizards/providerWizard.tsx +266 -0
  167. package/src/cli/wizards/searchWizard.tsx +47 -0
  168. package/src/cli/wizards/ttsWizard.js +4 -3
  169. package/src/cli/wizards/ttsWizard.tsx +61 -0
  170. package/src/config/configDefaults.js +25 -0
  171. package/src/config/configDefaults.ts +33 -0
  172. package/src/config/configDiagnostics.js +117 -0
  173. package/src/config/configDiagnostics.ts +166 -0
  174. package/src/config/configFile.js +52 -32
  175. package/src/config/configFile.ts +633 -0
  176. package/src/config/configManagement.js +126 -0
  177. package/src/config/configManagement.ts +159 -0
  178. package/src/config/configService.js +66 -11
  179. package/src/config/configService.ts +554 -0
  180. package/src/config/numericLimits.js +61 -0
  181. package/src/config/numericLimits.ts +80 -0
  182. package/src/config.ts +109 -0
  183. package/src/context/agentHome.ts +11 -0
  184. package/src/context/archive.ts +144 -0
  185. package/src/context/budgetPlan.js +54 -0
  186. package/src/context/budgetPlan.ts +88 -0
  187. package/src/context/compact.js +179 -106
  188. package/src/context/compact.ts +584 -0
  189. package/src/context/contextBundle.js +119 -0
  190. package/src/context/contextBundle.ts +176 -0
  191. package/src/context/contextLock.ts +84 -0
  192. package/src/context/microCompactLite.ts +218 -0
  193. package/src/context/persistContext.ts +177 -0
  194. package/src/context/reactiveCompact.js +19 -4
  195. package/src/context/reactiveCompact.ts +240 -0
  196. package/src/context/recentDirs.ts +42 -0
  197. package/src/context/sessionPath.ts +206 -0
  198. package/src/context/sessionRegistry.js +8 -2
  199. package/src/context/sessionRegistry.ts +414 -0
  200. package/src/context/snipCompact.ts +163 -0
  201. package/src/context/tokenCounter.ts +106 -0
  202. package/src/context/workspaceIndex.js +2 -2
  203. package/src/context/workspaceIndex.ts +751 -0
  204. package/src/entry.compiled.tsx +20 -0
  205. package/src/harness/claimValidation.js +9 -0
  206. package/src/harness/claimValidation.ts +22 -0
  207. package/src/harness/evidenceLedger.js +92 -0
  208. package/src/harness/evidenceLedger.ts +96 -0
  209. package/src/harness/stallGuard.js +60 -0
  210. package/src/harness/stallGuard.ts +81 -0
  211. package/src/humanIntervention.js +129 -0
  212. package/src/humanIntervention.ts +216 -0
  213. package/src/infra/verification.ts +295 -0
  214. package/src/irreversible/rules.js +216 -0
  215. package/src/irreversible/rules.ts +261 -0
  216. package/src/llm/client.js +40 -3
  217. package/src/llm/client.ts +621 -0
  218. package/src/llm/promptCache.js +27 -0
  219. package/src/llm/promptCache.ts +41 -0
  220. package/src/llm/providers/anthropicFormat.js +41 -5
  221. package/src/llm/providers/anthropicFormat.ts +290 -0
  222. package/src/llm/providers/openaiFormat.js +186 -8
  223. package/src/llm/providers/openaiFormat.ts +428 -0
  224. package/src/loop.js +614 -63
  225. package/src/loop.ts +1454 -0
  226. package/src/main.js +67 -10
  227. package/src/main.tsx +344 -0
  228. package/src/memory/codec.js +204 -0
  229. package/src/memory/codec.ts +249 -0
  230. package/src/memory/constants.js +34 -0
  231. package/src/memory/constants.ts +50 -0
  232. package/src/memory/episodicMemory.js +684 -0
  233. package/src/memory/episodicMemory.ts +815 -0
  234. package/src/memory/fileStore.js +148 -0
  235. package/src/memory/fileStore.ts +184 -0
  236. package/src/memory/memoryService.js +570 -0
  237. package/src/memory/memoryService.ts +859 -0
  238. package/src/memory/migration.js +128 -0
  239. package/src/memory/migration.ts +160 -0
  240. package/src/memory/repository.js +789 -0
  241. package/src/memory/repository.ts +893 -0
  242. package/src/memory/retrieval.js +108 -0
  243. package/src/memory/retrieval.ts +145 -0
  244. package/src/memory/searchIndex.js +107 -0
  245. package/src/memory/searchIndex.ts +153 -0
  246. package/src/memory/topicView.js +82 -0
  247. package/src/memory/topicView.ts +95 -0
  248. package/src/memory/turnJournal.js +95 -0
  249. package/src/memory/turnJournal.ts +120 -0
  250. package/src/memory/types.js +2 -0
  251. package/src/memory/types.ts +186 -0
  252. package/src/messageQueue.ts +123 -0
  253. package/src/multimodal.js +128 -46
  254. package/src/multimodal.ts +349 -0
  255. package/src/plugin-sdk.js +21 -1
  256. package/src/plugin-sdk.ts +134 -0
  257. package/src/plugins/builtinPluginIds.js +47 -0
  258. package/src/plugins/builtinPluginIds.ts +51 -0
  259. package/src/plugins/builtinRegistry.ts +43 -0
  260. package/src/plugins/commandRouter.js +40 -26
  261. package/src/plugins/commandRouter.ts +261 -0
  262. package/src/plugins/hookEngine.ts +341 -0
  263. package/src/plugins/pluginApi.ts +133 -0
  264. package/src/plugins/pluginLifecycle.ts +34 -0
  265. package/src/plugins/pluginLoader.ts +147 -0
  266. package/src/plugins/pluginManifest.js +127 -0
  267. package/src/plugins/pluginManifest.ts +152 -0
  268. package/src/plugins/pluginRunner.js +47 -2
  269. package/src/plugins/pluginRunner.ts +207 -0
  270. package/src/plugins/transcript.ts +208 -0
  271. package/src/prompts/lint.js +169 -0
  272. package/src/prompts/lint.ts +220 -0
  273. package/src/prompts/pluginList.js +32 -16
  274. package/src/prompts/pluginList.ts +86 -0
  275. package/src/prompts/policyRegistry.js +318 -0
  276. package/src/prompts/policyRegistry.ts +358 -0
  277. package/src/prompts/projectInstructions.js +24 -0
  278. package/src/prompts/projectInstructions.ts +81 -0
  279. package/src/prompts/promptAssembly.js +311 -202
  280. package/src/prompts/promptAssembly.ts +510 -0
  281. package/src/prompts/skillList.js +106 -45
  282. package/src/prompts/skillList.ts +198 -0
  283. package/src/prompts/system.js +31 -6
  284. package/src/prompts/system.ts +81 -0
  285. package/src/runtime/agentRuntimeContext.js +53 -0
  286. package/src/runtime/agentRuntimeContext.ts +112 -0
  287. package/src/session/runAgentTurn.js +71 -12
  288. package/src/session/runAgentTurn.ts +181 -0
  289. package/src/session/runTurn.js +29 -0
  290. package/src/session/runTurn.ts +160 -0
  291. package/src/session/runtimeStore.js +149 -0
  292. package/src/session/runtimeStore.ts +197 -0
  293. package/src/session/sessionState.js +79 -3
  294. package/src/session/sessionState.ts +134 -0
  295. package/src/toolGate.ts +50 -0
  296. package/src/tools/advisor/advisor.js +76 -15
  297. package/src/tools/advisor/advisor.ts +512 -0
  298. package/src/tools/agent/agent.js +245 -29
  299. package/src/tools/agent/agent.ts +837 -0
  300. package/src/tools/agent/agentTaskService.js +60 -0
  301. package/src/tools/agent/agentTaskService.ts +113 -0
  302. package/src/tools/agent/agentTypes.js +20 -8
  303. package/src/tools/agent/agentTypes.ts +94 -0
  304. package/src/tools/agent/coordinator.ts +111 -0
  305. package/src/tools/agent/worktree.ts +296 -0
  306. package/src/tools/ask/ask.js +17 -0
  307. package/src/tools/ask/ask.ts +122 -0
  308. package/src/tools/bash/attachImage.js +223 -0
  309. package/src/tools/bash/attachImage.ts +281 -0
  310. package/src/tools/bash/bash.js +132 -14
  311. package/src/tools/bash/bash.ts +642 -0
  312. package/src/tools/bash/semantics.ts +103 -0
  313. package/src/tools/bash/warnings.js +17 -11
  314. package/src/tools/bash/warnings.ts +117 -0
  315. package/src/tools/capabilities.js +42 -0
  316. package/src/tools/capabilities.ts +69 -0
  317. package/src/tools/computeruse/accessibilityPermission.js +261 -0
  318. package/src/tools/computeruse/accessibilityPermission.ts +294 -0
  319. package/src/tools/computeruse/axSearch.js +96 -0
  320. package/src/tools/computeruse/axSearch.ts +120 -0
  321. package/src/tools/computeruse/axSnap.js +272 -0
  322. package/src/tools/computeruse/axSnap.ts +349 -0
  323. package/src/tools/computeruse/capabilityLabels.js +95 -0
  324. package/src/tools/computeruse/capabilityLabels.ts +115 -0
  325. package/src/tools/computeruse/computerUseService.js +2235 -0
  326. package/src/tools/computeruse/computerUseService.ts +2686 -0
  327. package/src/tools/computeruse/computeruse.js +2742 -0
  328. package/src/tools/computeruse/computeruse.ts +3265 -0
  329. package/src/tools/computeruse/diagnosticTrace.js +154 -0
  330. package/src/tools/computeruse/diagnosticTrace.ts +195 -0
  331. package/src/tools/computeruse/diagnostics.js +369 -0
  332. package/src/tools/computeruse/diagnostics.ts +453 -0
  333. package/src/tools/computeruse/grants.js +53 -0
  334. package/src/tools/computeruse/grants.ts +70 -0
  335. package/src/tools/computeruse/humanBoundary.js +159 -0
  336. package/src/tools/computeruse/humanBoundary.ts +199 -0
  337. package/src/tools/computeruse/intervention.js +77 -0
  338. package/src/tools/computeruse/intervention.ts +110 -0
  339. package/src/tools/computeruse/manualSchema.js +709 -0
  340. package/src/tools/computeruse/manualSchema.ts +916 -0
  341. package/src/tools/computeruse/observationImages.js +705 -0
  342. package/src/tools/computeruse/observationImages.ts +889 -0
  343. package/src/tools/computeruse/protocol.js +60 -0
  344. package/src/tools/computeruse/protocol.ts +515 -0
  345. package/src/tools/computeruse/telemetryLog.js +57 -0
  346. package/src/tools/computeruse/telemetryLog.ts +67 -0
  347. package/src/tools/computeruse/vision/appGuide.js +325 -0
  348. package/src/tools/computeruse/vision/appGuide.ts +482 -0
  349. package/src/tools/computeruse/vision/focus.js +291 -0
  350. package/src/tools/computeruse/vision/focus.ts +382 -0
  351. package/src/tools/computeruse/vision/frames.js +248 -0
  352. package/src/tools/computeruse/vision/frames.ts +329 -0
  353. package/src/tools/computeruse/vision/geometry.js +131 -0
  354. package/src/tools/computeruse/vision/geometry.ts +163 -0
  355. package/src/tools/computeruse/vision/imaging.js +201 -0
  356. package/src/tools/computeruse/vision/imaging.ts +281 -0
  357. package/src/tools/computeruse/vision/png.js +274 -0
  358. package/src/tools/computeruse/vision/png.ts +317 -0
  359. package/src/tools/computeruse/vision/raster.js +320 -0
  360. package/src/tools/computeruse/vision/raster.ts +408 -0
  361. package/src/tools/computeruse/vision/settle.js +131 -0
  362. package/src/tools/computeruse/vision/settle.ts +220 -0
  363. package/src/tools/computeruse/vision/telemetry.js +196 -0
  364. package/src/tools/computeruse/vision/telemetry.ts +241 -0
  365. package/src/tools/computeruse/visionOcr.js +115 -0
  366. package/src/tools/computeruse/visionOcr.ts +128 -0
  367. package/src/tools/computeruse/waitFor.js +237 -0
  368. package/src/tools/computeruse/waitFor.ts +291 -0
  369. package/src/tools/download/download.js +10 -6
  370. package/src/tools/download/download.ts +187 -0
  371. package/src/tools/edit/edit.js +30 -18
  372. package/src/tools/edit/edit.ts +349 -0
  373. package/src/tools/edit/multi-edit.js +25 -14
  374. package/src/tools/edit/multi-edit.ts +223 -0
  375. package/src/tools/effectiveToolSet.js +73 -0
  376. package/src/tools/effectiveToolSet.ts +91 -0
  377. package/src/tools/genimage/genimage.ts +257 -0
  378. package/src/tools/genspeech/genspeech.ts +83 -0
  379. package/src/tools/glob/glob.js +176 -29
  380. package/src/tools/glob/glob.ts +283 -0
  381. package/src/tools/grep/grep.js +61 -19
  382. package/src/tools/grep/grep.ts +239 -0
  383. package/src/tools/grep/rgPath.ts +206 -0
  384. package/src/tools/index.js +99 -5
  385. package/src/tools/index.ts +293 -0
  386. package/src/tools/lsp/config.js +113 -0
  387. package/src/tools/lsp/config.ts +113 -0
  388. package/src/tools/lsp/lsp.js +256 -0
  389. package/src/tools/lsp/lsp.ts +274 -0
  390. package/src/tools/lsp/service.js +303 -0
  391. package/src/tools/lsp/service.ts +296 -0
  392. package/src/tools/memory/memory.js +520 -0
  393. package/src/tools/memory/memory.ts +625 -0
  394. package/src/tools/memory/policy.js +53 -0
  395. package/src/tools/memory/policy.ts +72 -0
  396. package/src/tools/notebook/notebookEdit.js +114 -0
  397. package/src/tools/notebook/notebookEdit.ts +126 -0
  398. package/src/tools/powershell/powershell.js +210 -0
  399. package/src/tools/powershell/powershell.ts +239 -0
  400. package/src/tools/presentresult/presentresult.js +145 -0
  401. package/src/tools/presentresult/presentresult.ts +156 -0
  402. package/src/tools/read/read.js +371 -163
  403. package/src/tools/read/read.ts +558 -0
  404. package/src/tools/shared/diffSummary.js +44 -0
  405. package/src/tools/shared/diffSummary.ts +63 -0
  406. package/src/tools/shared/fileActivity.js +51 -0
  407. package/src/tools/shared/fileActivity.ts +66 -0
  408. package/src/tools/shared/fileMutationBus.js +13 -0
  409. package/src/tools/shared/fileMutationBus.ts +14 -0
  410. package/src/tools/shared/fileState.js +73 -65
  411. package/src/tools/shared/fileState.ts +145 -0
  412. package/src/tools/shared/fileUtils.js +81 -4
  413. package/src/tools/shared/fileUtils.ts +504 -0
  414. package/src/tools/shared/killTree.ts +50 -0
  415. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  416. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  417. package/src/tools/shared/schemas.ts +20 -0
  418. package/src/tools/shared/toolOutputStore.js +97 -0
  419. package/src/tools/shared/toolOutputStore.ts +110 -0
  420. package/src/tools/skill/skill.js +86 -0
  421. package/src/tools/skill/skill.ts +94 -0
  422. package/src/tools/snip/snip.js +67 -0
  423. package/src/tools/snip/snip.ts +80 -0
  424. package/src/tools/tasks/backgroundTaskService.js +275 -0
  425. package/src/tools/tasks/backgroundTaskService.ts +369 -0
  426. package/src/tools/tasks/processEvidence.js +24 -0
  427. package/src/tools/tasks/processEvidence.ts +34 -0
  428. package/src/tools/tasks/taskManagement.js +163 -0
  429. package/src/tools/tasks/taskManagement.ts +180 -0
  430. package/src/tools/tasks/taskOutput.js +113 -0
  431. package/src/tools/tasks/taskOutput.ts +145 -0
  432. package/src/tools/tasks/taskStop.js +74 -0
  433. package/src/tools/tasks/taskStop.ts +99 -0
  434. package/src/tools/tasks/taskStore.js +46 -0
  435. package/src/tools/tasks/taskStore.ts +77 -0
  436. package/src/tools/terminal/terminalCapture.js +167 -0
  437. package/src/tools/terminal/terminalCapture.ts +179 -0
  438. package/src/tools/trash/trash.js +233 -0
  439. package/src/tools/trash/trash.ts +283 -0
  440. package/src/tools/types.js +1 -1
  441. package/src/tools/types.ts +24 -0
  442. package/src/tools/webbrowser/agentBrowserPath.ts +286 -0
  443. package/src/tools/webbrowser/browser.js +214 -11
  444. package/src/tools/webbrowser/browser.ts +293 -0
  445. package/src/tools/webbrowser/browserControl.js +153 -48
  446. package/src/tools/webbrowser/browserControl.ts +262 -0
  447. package/src/tools/webbrowser/browserDaemon.js +147 -39
  448. package/src/tools/webbrowser/browserDaemon.ts +270 -0
  449. package/src/tools/webbrowser/browserOutcome.js +273 -0
  450. package/src/tools/webbrowser/browserOutcome.ts +306 -0
  451. package/src/tools/webbrowser/browserRunRegistry.js +150 -0
  452. package/src/tools/webbrowser/browserRunRegistry.ts +179 -0
  453. package/src/tools/webbrowser/browserService.js +20 -6
  454. package/src/tools/webbrowser/browserService.ts +56 -0
  455. package/src/tools/webbrowser/browserStream.js +30 -1
  456. package/src/tools/webbrowser/browserStream.ts +216 -0
  457. package/src/tools/webbrowser/browserTabs.js +143 -0
  458. package/src/tools/webbrowser/browserTabs.ts +210 -0
  459. package/src/tools/webbrowser/webbrowser.js +831 -150
  460. package/src/tools/webbrowser/webbrowser.ts +1537 -0
  461. package/src/tools/webfetch/preapproved.ts +274 -0
  462. package/src/tools/webfetch/webfetch.js +24 -23
  463. package/src/tools/webfetch/webfetch.ts +383 -0
  464. package/src/tools/websearch/websearch.js +12 -11
  465. package/src/tools/websearch/websearch.ts +292 -0
  466. package/src/tools/worktree/sessionWorktree.js +128 -0
  467. package/src/tools/worktree/sessionWorktree.ts +158 -0
  468. package/src/tools/write/write.js +27 -44
  469. package/src/tools/write/write.ts +136 -0
  470. package/src/types.ts +960 -0
  471. package/src/ui/App.js +216 -12
  472. package/src/ui/App.tsx +879 -0
  473. package/src/ui/AskQuestion.tsx +300 -0
  474. package/src/ui/InputBox.js +77 -1
  475. package/src/ui/InputBox.tsx +567 -0
  476. package/src/ui/LiveArea.tsx +344 -0
  477. package/src/ui/MessageList.js +27 -6
  478. package/src/ui/MessageList.tsx +559 -0
  479. package/src/ui/ModelPicker.tsx +107 -0
  480. package/src/ui/QueuedMessagesBanner.tsx +51 -0
  481. package/src/ui/Root.js +6 -8
  482. package/src/ui/Root.tsx +100 -0
  483. package/src/ui/SessionPicker.tsx +169 -0
  484. package/src/ui/StatusLine.js +22 -1
  485. package/src/ui/StatusLine.tsx +145 -0
  486. package/src/ui/WorkspacePicker.tsx +84 -0
  487. package/src/ui/advisorCommand.js +54 -0
  488. package/src/ui/advisorCommand.ts +63 -0
  489. package/src/ui/computerCommand.js +19 -0
  490. package/src/ui/computerCommand.ts +35 -0
  491. package/src/ui/figures.ts +45 -0
  492. package/src/ui/hooks/askSlot.ts +112 -0
  493. package/src/ui/hooks/useChat.js +271 -7
  494. package/src/ui/hooks/useChat.ts +1578 -0
  495. package/src/ui/hooks/useInputHistory.ts +235 -0
  496. package/src/ui/hooks/usePasteHandler.ts +220 -0
  497. package/src/ui/hooks/useTerminalSize.ts +41 -0
  498. package/src/ui/hooks/useTextBuffer.ts +108 -0
  499. package/src/ui/hooks/useTokenUsage.ts +44 -0
  500. package/src/ui/liveViewport.ts +189 -0
  501. package/src/ui/markdown.ts +230 -0
  502. package/src/ui/textBuffer.ts +239 -0
  503. package/src/ui/theme.ts +35 -0
  504. package/src/utils/clipboardImage.js +358 -0
  505. package/src/utils/clipboardImage.ts +420 -0
  506. package/src/utils/debugLog.ts +50 -0
  507. package/src/utils/greenRoot.ts +35 -0
  508. package/src/utils/packageRoot.ts +40 -0
  509. package/src/utils/pathContains.js +28 -0
  510. package/src/utils/pathContains.ts +31 -0
  511. package/src/utils/resourcePaths.ts +71 -0
  512. package/src/utils/stripThink.ts +49 -0
  513. package/src/utils/zodToJson.ts +73 -0
  514. package/src/web/browserBridge.js +365 -66
  515. package/src/web/browserBridge.ts +672 -0
  516. package/src/web/protocol.ts +1804 -0
  517. package/vendor/computer-use/arm64-darwin/cu-helperd +0 -0
  518. package/workflows/ppt.yaml +13 -2
  519. package/workflows/wechat-reading-shots.yaml +315 -0
  520. package/workflows/word.yaml +11 -1
  521. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  522. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  523. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  524. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  525. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  526. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  527. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  528. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  529. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  530. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  531. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  532. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  533. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  534. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  535. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  536. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  537. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  538. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  539. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  540. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  541. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  542. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  543. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  544. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  545. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  546. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  547. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  548. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  549. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  550. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  551. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  552. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  553. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  554. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  555. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  556. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  557. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  558. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  559. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  560. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  561. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  562. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  563. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  564. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  565. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  566. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  567. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  568. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  569. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  570. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  571. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  572. /package/skills/ai-image/scripts/{generate_test.mjs → generate.selftest.mjs} +0 -0
  573. /package/skills/ai-image/scripts/{generate_from_plan_test.mjs → generate_from_plan.selftest.mjs} +0 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * ============================================================
3
+ * test/context-files.test.ts —— ADR-05 阶段 4 miniReAct 验证
4
+ * ------------------------------------------------------------
5
+ * 覆盖 execLlmStep + miniReAct 子循环(context_files 路径):
6
+ * case 1: 基本 Read 引用 — 第一轮 tool_call,第二轮纯文本收敛
7
+ * case 2: maxTurns 上限 — 永不收敛 → 抛错
8
+ * case 3: output_schema + tools 组合 — 走 [json+tools] 前缀
9
+ *
10
+ * 策略:
11
+ * - mock '../../../src/llm/client.js' → 按调用次数依次返回不同流
12
+ * - mock '../../../src/tools/index.js' → executeTool 返回固定内容;
13
+ * 同时透传 ALL_TOOLS / getToolByName(llm.ts 还要靠 ALL_TOOLS 过滤白名单)
14
+ * - 不真跑 Read,避免文件系统耦合
15
+ * ============================================================
16
+ */
17
+
18
+ import {
19
+ afterAll,
20
+ beforeAll,
21
+ beforeEach,
22
+ describe,
23
+ expect,
24
+ it,
25
+ mock,
26
+ } from 'bun:test';
27
+
28
+ import type { Provider, Tool } from '../../../src/types.js';
29
+ import { execLlmStep } from '../src/stepExecutors/llm.js';
30
+ import { VarStack } from '../src/types.js';
31
+ import type { RunContext, StepDef } from '../src/types.js';
32
+
33
+ const fakeProvider: Provider = {
34
+ name: 'test',
35
+ baseURL: '',
36
+ apiKey: '',
37
+ model: 'm',
38
+ contextWindow: 128000,
39
+ };
40
+
41
+ /** execLlmStep 现为 AsyncGenerator:drain 取最终 return 值(yield 的事件直接丢弃)。 */
42
+ async function drainLlm<R>(gen: AsyncGenerator<unknown, R, void>): Promise<R> {
43
+ let r = await gen.next();
44
+ while (!r.done) r = await gen.next();
45
+ return r.value;
46
+ }
47
+
48
+ // ---------------- mock 基础设施 ----------------
49
+
50
+ interface ChatCallRecord {
51
+ messages: unknown[];
52
+ tools: unknown[];
53
+ responseFormat?: unknown;
54
+ }
55
+
56
+ /** 每次 chat() 调用记录的参数 */
57
+ const capturedChatArgs: ChatCallRecord[] = [];
58
+ /** mock 流脚本:每轮一个;用尽后报错(避免无限循环) */
59
+ type MockTurn =
60
+ | { kind: 'text'; text: string }
61
+ | {
62
+ kind: 'tool_call';
63
+ callId: string;
64
+ name: string;
65
+ argsJson: string;
66
+ /** 该轮 assistant 同时附带的可选文本 */
67
+ text?: string;
68
+ };
69
+ let mockTurns: MockTurn[] = [];
70
+ let mockTurnIdx = 0;
71
+
72
+ interface ExecuteToolCall {
73
+ name: string;
74
+ rawArguments: string;
75
+ }
76
+ const capturedExecuteToolCalls: ExecuteToolCall[] = [];
77
+ /** mock executeTool 返回值脚本(按调用顺序);用尽则回退到 { ok:true, content:'OK' } */
78
+ let mockToolResults: Array<
79
+ { ok: true; content: string } | { ok: false; error: string }
80
+ > = [];
81
+
82
+ import { chat as realChatImport } from '../../../src/llm/client.js';
83
+ // 复用真实的 ALL_TOOLS / getToolByName(llm.ts 的白名单过滤要用),
84
+ // 只把 executeTool 替换成 mock 版本。
85
+ //
86
+ // 注:bun 的 mock.module 是 process-global 且无内建 restore。即便其它文件的
87
+ // 测试 case 没被本文件触发,只要 top-level 执行了 mock.module,整个进程的
88
+ // 模块解析就被替换了。所以放在 beforeAll/afterAll 内成对挂钩,确保最小作用域。
89
+ import {
90
+ ALL_TOOLS as REAL_ALL_TOOLS,
91
+ executeTool as realExecuteTool,
92
+ getToolByName as realGetToolByName,
93
+ } from '../../../src/tools/index.js';
94
+
95
+ // 在 mock 安装前把真实 chat 函数引用拷到值变量,避免 ESM live binding
96
+ // 在 mock.module 后跟着变成 mock 版本(afterAll 复原才能拿到真函数)
97
+ const ORIGINAL_CHAT = realChatImport;
98
+ const ORIGINAL_EXECUTE_TOOL = realExecuteTool;
99
+
100
+ beforeAll(() => {
101
+ installChatMock();
102
+ mock.module('../../../src/tools/index.js', () => ({
103
+ ALL_TOOLS: REAL_ALL_TOOLS,
104
+ getToolByName: realGetToolByName,
105
+ executeTool: async (name: string, rawArguments: string) => {
106
+ capturedExecuteToolCalls.push({ name, rawArguments });
107
+ const next = mockToolResults.shift();
108
+ return next ?? { ok: true, content: 'OK' };
109
+ },
110
+ }));
111
+ });
112
+
113
+ afterAll(() => {
114
+ // 复原 chat 为真实实现,避免污染下游 e2e / runner 等不 mock chat 的测试文件
115
+ // 必须用 ORIGINAL_CHAT(mock 前抓到的值快照),不能用 realChatImport(已被 mock 替换的 live binding)
116
+ mock.module('../../../src/llm/client.js', () => ({ chat: ORIGINAL_CHAT }));
117
+ // 复原 executeTool 为真实实现
118
+ mock.module('../../../src/tools/index.js', () => ({
119
+ ALL_TOOLS: REAL_ALL_TOOLS,
120
+ getToolByName: realGetToolByName,
121
+ executeTool: ORIGINAL_EXECUTE_TOOL,
122
+ }));
123
+ });
124
+
125
+ // chat mock 也放进 beforeAll,避免 top-level 副作用污染其它文件
126
+ function installChatMock(): void {
127
+ mock.module('../../../src/llm/client.js', () => ({
128
+ chat: async function* (args: ChatCallRecord) {
129
+ capturedChatArgs.push({
130
+ messages: args.messages,
131
+ tools: args.tools,
132
+ responseFormat: args.responseFormat,
133
+ });
134
+ const turn = mockTurns[mockTurnIdx];
135
+ mockTurnIdx++;
136
+ if (!turn) {
137
+ throw new Error(
138
+ `mock chat: 用尽脚本(共 ${mockTurns.length} 轮,调用第 ${mockTurnIdx} 次)`,
139
+ );
140
+ }
141
+ if (turn.kind === 'text') {
142
+ yield { type: 'text_delta', delta: turn.text };
143
+ } else {
144
+ if (turn.text) yield { type: 'text_delta', delta: turn.text };
145
+ yield {
146
+ type: 'tool_call_delta',
147
+ index: 0,
148
+ id: turn.callId,
149
+ name: turn.name,
150
+ argumentsDelta: turn.argsJson,
151
+ };
152
+ }
153
+ yield {
154
+ type: 'done',
155
+ stopReason: turn.kind === 'tool_call' ? 'tool_calls' : 'stop',
156
+ };
157
+ },
158
+ }));
159
+ }
160
+
161
+ // ---------------- 测试 ----------------
162
+
163
+ describe('workflow-runner context_files (ADR-05 阶段 4)', () => {
164
+ let ctx: RunContext;
165
+
166
+ beforeEach(() => {
167
+ capturedChatArgs.length = 0;
168
+ capturedExecuteToolCalls.length = 0;
169
+ mockTurns = [];
170
+ mockTurnIdx = 0;
171
+ mockToolResults = [];
172
+ ctx = {
173
+ provider: fakeProvider,
174
+ history: [],
175
+ cwd: process.cwd(),
176
+ tools: [],
177
+ };
178
+ });
179
+
180
+ it('pure llm 不下发工具,避免单轮设计类步骤误调工具后撞 maxTurns=1', async () => {
181
+ mockTurns = [{ kind: 'text', text: 'design doc' }];
182
+
183
+ const step: StepDef = {
184
+ id: 'pure-design',
185
+ llm: 'please design',
186
+ };
187
+
188
+ const result = await drainLlm(execLlmStep(step, new VarStack(), ctx));
189
+
190
+ expect(result.raw).toEqual({ text: 'design doc', result: 'design doc' });
191
+ expect(capturedChatArgs.length).toBe(1);
192
+ expect(capturedChatArgs[0].tools).toEqual([]);
193
+ });
194
+
195
+ it('case 1: 基本 Read 引用 — tool_call 一轮 + 纯文本一轮', async () => {
196
+ ctx.tools = [{ name: 'Read' } as Tool];
197
+ mockTurns = [
198
+ {
199
+ kind: 'tool_call',
200
+ callId: 'call_1',
201
+ name: 'Read',
202
+ argsJson: '{"file_path":"/tmp/x.txt"}',
203
+ },
204
+ { kind: 'text', text: 'got it' },
205
+ ];
206
+ mockToolResults = [{ ok: true, content: 'file content here' }];
207
+
208
+ const step: StepDef = {
209
+ id: 'read-and-answer',
210
+ llm: 'please summarize',
211
+ context_files: [{ path: '/tmp/x.txt', hint: 'source data' }],
212
+ // context_files 经 runQuery(TAOR) 执行;步骤白名单与宿主有效工具集取交集。
213
+ allowed_tools: ['Read'],
214
+ };
215
+
216
+ const result = await drainLlm(execLlmStep(step, new VarStack(), ctx));
217
+
218
+ // 返回值断言
219
+ expect(result.raw).toEqual({ text: 'got it', result: 'got it' });
220
+ expect(result.preview).toBe('got it');
221
+
222
+ // chat 被调了 2 次
223
+ expect(capturedChatArgs.length).toBe(2);
224
+ // executeTool 被调了 1 次,参数透传正确
225
+ expect(capturedExecuteToolCalls.length).toBe(1);
226
+ expect(capturedExecuteToolCalls[0].name).toBe('Read');
227
+ const parsedArgs = JSON.parse(capturedExecuteToolCalls[0].rawArguments) as {
228
+ file_path: string;
229
+ };
230
+ expect(parsedArgs.file_path).toBe('/tmp/x.txt');
231
+
232
+ // 第一轮 user message 包含路径和 hint
233
+ const firstUserMsg = (
234
+ capturedChatArgs[0].messages as Array<{ role: string; content: string }>
235
+ ).find((m) => m.role === 'user');
236
+ expect(firstUserMsg).toBeDefined();
237
+ expect(firstUserMsg!.content).toContain('/tmp/x.txt');
238
+ expect(firstUserMsg!.content).toContain('source data');
239
+
240
+ // tools 数组只含 Read(白名单生效)
241
+ const tools0 = capturedChatArgs[0].tools as Array<{ name: string }>;
242
+ expect(tools0.length).toBe(1);
243
+ expect(tools0[0].name).toBe('Read');
244
+ });
245
+
246
+ it('case 2: 永不收敛(每轮都调工具)→ 被 maxTurns 严格兜住,不无限循环', async () => {
247
+ ctx.tools = [{ name: 'Read' } as Tool];
248
+ // v0.9 重构移除了 miniReAct 的"重复工具调用早停",改由主 TAOR 引擎(runQuery)的
249
+ // maxTurns 硬上限兜底。本用例验证安全属性:模型每轮都调工具、永不收敛时,chat 调用
250
+ // 次数被 maxTurns 严格限制(不会无限循环),不再依赖已移除的重复检测。
251
+ mockTurns = [
252
+ {
253
+ kind: 'tool_call',
254
+ callId: 'c1',
255
+ name: 'Read',
256
+ argsJson: '{"file_path":"/a"}',
257
+ },
258
+ {
259
+ kind: 'tool_call',
260
+ callId: 'c2',
261
+ name: 'Read',
262
+ argsJson: '{"file_path":"/a"}',
263
+ },
264
+ ];
265
+ mockToolResults = [
266
+ { ok: true, content: 'A' },
267
+ { ok: true, content: 'A' },
268
+ ];
269
+
270
+ const step: StepDef = {
271
+ id: 'never-converge',
272
+ llm: 'go',
273
+ context_files: [{ path: '/a' }],
274
+ maxTurns: 2,
275
+ };
276
+
277
+ // 不无限循环:chat 调用次数被 maxTurns(=2) 严格兜住。
278
+ // 撞顶后的半截产出不可信,execLlmStep 必须抛错触发 runner retry/halt。
279
+ await expect(
280
+ drainLlm(execLlmStep(step, new VarStack(), ctx)),
281
+ ).rejects.toThrow('达到最大轮数 2 被截断');
282
+ expect(capturedChatArgs.length).toBe(2);
283
+ });
284
+
285
+ it('case 3: output_schema + tools — 走 [json+tools] 前缀', async () => {
286
+ ctx.tools = [{ name: 'Read' } as Tool];
287
+ mockTurns = [
288
+ {
289
+ kind: 'tool_call',
290
+ callId: 'call_1',
291
+ name: 'Read',
292
+ argsJson: '{"file_path":"/tmp/y.txt"}',
293
+ },
294
+ { kind: 'text', text: '{"findings":["a","b"]}' },
295
+ ];
296
+ mockToolResults = [{ ok: true, content: 'raw content' }];
297
+
298
+ const step: StepDef = {
299
+ id: 'structured-with-tools',
300
+ llm: 'find',
301
+ context_files: [{ path: '/tmp/y.txt' }],
302
+ output_schema: {
303
+ type: 'object',
304
+ properties: { findings: { type: 'array', items: { type: 'string' } } },
305
+ required: ['findings'],
306
+ },
307
+ };
308
+
309
+ const result = await drainLlm(execLlmStep(step, new VarStack(), ctx));
310
+
311
+ // raw 是 parsed JSON
312
+ expect(result.raw).toEqual({ findings: ['a', 'b'] });
313
+ // v0.9:output_schema 走 prompt 内嵌 + extractJsonPayload 解析,preview 统一 [json] 前缀
314
+ // (不再有独立的 [json+tools] 路径,也不再走 provider-native responseFormat)。
315
+ expect(result.preview.startsWith('[json] ')).toBe(true);
316
+
317
+ // chat 被调两轮(读文件一轮 + 出 JSON 一轮)
318
+ expect(capturedChatArgs.length).toBe(2);
319
+ });
320
+ });
@@ -0,0 +1,370 @@
1
+ /**
2
+ * ============================================================
3
+ * test/workflows/e2e.test.ts —— /workflow 端到端测试
4
+ * ------------------------------------------------------------
5
+ * 从用户敲 `/workflow hello-workflow --input ...` 这一最外层入口开始,
6
+ * 穿过:
7
+ *
8
+ * runWithPlugins
9
+ * → discoverPlugins (扫到真实 workflow-runner 插件)
10
+ * → resolveCommand (识别 /workflow)
11
+ * → cmd.executionMode === 'workflow' 路径
12
+ * → lazy import workflows/index.ts
13
+ * → parseWorkflowArgs + coerceInputs
14
+ * → findWorkflowByName (从 tmp/workflows/ 加载 fixture)
15
+ * → runWorkflow → step executors (真实 executeTool('Write'))
16
+ *
17
+ * 断言:
18
+ * - 整个事件序列形态合理(workflow_start → step_start/end ×N → workflow_done → turn_done)
19
+ * 注:经命令路径(runWithPlugins→runWorkflowFromCommand)成功后补发通用 turn_done 收口,
20
+ * 故终结事件是 turn_done(workflow_done 在其前一条);直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾
21
+ * - 文件真的落盘到正确路径,内容正确
22
+ * - 缺必填 input 时不写任何文件
23
+ * - assert 失败时整个 workflow halt
24
+ *
25
+ * 不 mock chat / runQuery —— fixture 不依赖 LLM。
26
+ * 只 mock workingDir.ts,因为其它测试文件留下的全局 mock 会污染。
27
+ * ============================================================
28
+ */
29
+
30
+ import {
31
+ afterEach,
32
+ beforeEach,
33
+ describe,
34
+ expect,
35
+ it,
36
+ mock,
37
+ } from 'bun:test';
38
+ import {
39
+ copyFileSync,
40
+ existsSync,
41
+ mkdirSync,
42
+ mkdtempSync,
43
+ readFileSync,
44
+ rmSync,
45
+ } from 'node:fs';
46
+ import { tmpdir } from 'node:os';
47
+ import { join } from 'node:path';
48
+
49
+ import { _resetWorkingDir } from '../../../src/bootstrap/workingDir.js';
50
+ import { _resetPluginCache } from '../../../src/plugins/commandRouter.js';
51
+ import type { LoopEvent, Provider } from '../../../src/types.js';
52
+ import type { PluginEvent } from '../../../src/plugins/pluginApi.js';
53
+ import type { WorkflowEvent } from '../src/types.js';
54
+
55
+ type WfOrLoop = LoopEvent | PluginEvent | WorkflowEvent;
56
+
57
+ const FIXTURE_SRC = join(import.meta.dir, 'fixtures', 'hello-workflow.yaml');
58
+
59
+ const provider: Provider = {
60
+ name: 'test',
61
+ baseURL: '',
62
+ apiKey: '',
63
+ model: 'm',
64
+ contextWindow: 128000,
65
+ };
66
+
67
+ async function collect(
68
+ gen: AsyncGenerator<WfOrLoop, void, void>,
69
+ ): Promise<WfOrLoop[]> {
70
+ const out: WfOrLoop[] = [];
71
+ for await (const ev of gen) out.push(ev as WfOrLoop);
72
+ return out;
73
+ }
74
+
75
+ describe('workflow E2E(/workflow → 路由 → runner → 文件落盘)', () => {
76
+ let tmp: string;
77
+
78
+ beforeEach(() => {
79
+ tmp = mkdtempSync(join(tmpdir(), 'wf-e2e-'));
80
+ mkdirSync(join(tmp, 'workflows'), { recursive: true });
81
+ copyFileSync(FIXTURE_SRC, join(tmp, 'workflows', 'hello-workflow.yaml'));
82
+
83
+ process.env.MINIMAL_AGENT_CWD = tmp;
84
+ _resetWorkingDir();
85
+ _resetPluginCache();
86
+
87
+ // 抵抗其它测试文件留下的 workingDir.ts 全局 mock 污染。
88
+ // 注:这一行让 getWorkingDir 跟着 tmp 走,相对路径锚点 + workflows/ 双源扫描都受益。
89
+ mock.module('../../../src/bootstrap/workingDir.js', () => ({
90
+ getWorkingDir: () => tmp,
91
+ initWorkingDir: () => tmp,
92
+ _resetWorkingDir: () => {},
93
+ }));
94
+ });
95
+
96
+ afterEach(() => {
97
+ delete process.env.MINIMAL_AGENT_CWD;
98
+ _resetWorkingDir();
99
+ _resetPluginCache();
100
+ rmSync(tmp, { recursive: true, force: true });
101
+ });
102
+
103
+ async function runCmd(input: string): Promise<WfOrLoop[]> {
104
+ const { runWithPlugins } = await import('../../../src/plugins/pluginRunner.js');
105
+ return collect(
106
+ runWithPlugins(input, { provider, history: [] }) as AsyncGenerator<WfOrLoop, void, void>,
107
+ );
108
+ }
109
+
110
+ it('golden path:/workflow hello-workflow --input name=alice --input lang=zh 跑完且文件落盘', async () => {
111
+ const events = await runCmd('/workflow hello-workflow --input name=alice --input lang=zh');
112
+
113
+ // 整体形态
114
+ expect(events[0]?.type).toBe('workflow_start');
115
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
116
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
117
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
118
+ expect(events[events.length - 1]?.type).toBe('turn_done');
119
+ expect(events.some((e) => e.type === 'error')).toBe(false);
120
+
121
+ // 每个 step 都跑完且 ok=true
122
+ const ends = events.filter(
123
+ (e): e is Extract<WorkflowEvent, { type: 'workflow_step_end' }> => e.type === 'workflow_step_end',
124
+ );
125
+ expect(ends.length).toBe(4);
126
+ expect(ends.every((e) => e.ok)).toBe(true);
127
+ expect(ends.map((e) => e.id)).toEqual([
128
+ 'write_greeting',
129
+ 'write_meta',
130
+ 'verify_files',
131
+ 'verify_capture',
132
+ ]);
133
+
134
+ // 文件实际写盘
135
+ const helloPath = join(tmp, 'greetings', 'alice', 'hello.txt');
136
+ const metaPath = join(tmp, 'greetings', 'alice', 'meta.json');
137
+ expect(existsSync(helloPath)).toBe(true);
138
+ expect(existsSync(metaPath)).toBe(true);
139
+
140
+ // hello.txt 内容验证(${var} 插值确实工作了)
141
+ expect(readFileSync(helloPath, 'utf8')).toBe('Hello, alice! (lang=zh)');
142
+
143
+ // meta.json 是合法 JSON,字段对得上
144
+ const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
145
+ expect(meta.name).toBe('alice');
146
+ expect(meta.lang).toBe('zh');
147
+ // length() 内置函数 + capture 链路:greeting_text="Hello, alice! (lang=zh)" → 长度 23
148
+ expect(meta.greeting_length).toBe('Hello, alice! (lang=zh)'.length);
149
+ });
150
+
151
+ it('lang 使用默认值 en(input enum default 路径)', async () => {
152
+ const events = await runCmd('/workflow hello-workflow --input name=bob');
153
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
154
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
155
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
156
+ expect(events[events.length - 1]?.type).toBe('turn_done');
157
+ expect(readFileSync(join(tmp, 'greetings', 'bob', 'hello.txt'), 'utf8'))
158
+ .toBe('Hello, bob! (lang=en)');
159
+ });
160
+
161
+ it('workflow_step_start 事件 index/total 单调且与 yaml 一致', async () => {
162
+ const events = await runCmd('/workflow hello-workflow --input name=carol');
163
+ const starts = events.filter(
164
+ (e): e is Extract<WorkflowEvent, { type: 'workflow_step_start' }> => e.type === 'workflow_step_start',
165
+ );
166
+ expect(starts.length).toBe(4);
167
+ expect(starts.map((s) => s.index)).toEqual([0, 1, 2, 3]);
168
+ expect(starts.every((s) => s.total === 4)).toBe(true);
169
+ expect(starts.map((s) => s.kind)).toEqual(['tool', 'tool', 'assert', 'assert']);
170
+ });
171
+
172
+ it('缺必填 input name → yield error,且没写任何文件', async () => {
173
+ const events = await runCmd('/workflow hello-workflow');
174
+ expect(events.some((e) => e.type === 'error')).toBe(true);
175
+ expect(events.some((e) => e.type === 'workflow_start')).toBe(false);
176
+ // tmp/greetings/ 不应被创建
177
+ expect(existsSync(join(tmp, 'greetings'))).toBe(false);
178
+ });
179
+
180
+ it('lang 传非法值 → yield enum 校验 error,且不进入 runWorkflow', async () => {
181
+ const events = await runCmd('/workflow hello-workflow --input name=dave --input lang=fr');
182
+ const err = events.find(
183
+ (e): e is Extract<LoopEvent, { type: 'error' }> => e.type === 'error',
184
+ );
185
+ expect(err).toBeDefined();
186
+ expect(err!.error).toContain('lang');
187
+ expect(events.some((e) => e.type === 'workflow_start')).toBe(false);
188
+ });
189
+
190
+ it('未知 workflow 名 → yield error,不污染文件系统', async () => {
191
+ const events = await runCmd('/workflow definitely-not-exists --input name=eve');
192
+ expect(events.some((e) => e.type === 'error')).toBe(true);
193
+ expect(events.some((e) => e.type === 'workflow_start')).toBe(false);
194
+ expect(existsSync(join(tmp, 'greetings'))).toBe(false);
195
+ });
196
+
197
+ it('L2:位置参数等价 --input —— /workflow hello-workflow alice 同效', async () => {
198
+ const events = await runCmd('/workflow hello-workflow alice');
199
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
200
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
201
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
202
+ expect(events[events.length - 1]?.type).toBe('turn_done');
203
+ // lang 走 default=en
204
+ expect(readFileSync(join(tmp, 'greetings', 'alice', 'hello.txt'), 'utf8'))
205
+ .toBe('Hello, alice! (lang=en)');
206
+ });
207
+
208
+ it('L2:多个位置参数按 inputs 声明顺序映射 —— alice zh → name=alice, lang=zh', async () => {
209
+ const events = await runCmd('/workflow hello-workflow alice zh');
210
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
211
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
212
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
213
+ expect(events[events.length - 1]?.type).toBe('turn_done');
214
+ expect(readFileSync(join(tmp, 'greetings', 'alice', 'hello.txt'), 'utf8'))
215
+ .toBe('Hello, alice! (lang=zh)');
216
+ });
217
+
218
+ it('L2:.yaml / .yml 后缀自动剥离 —— hello-workflow.yaml alice 与 hello-workflow alice 等价', async () => {
219
+ const events = await runCmd('/workflow hello-workflow.yaml alice');
220
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
221
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
222
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
223
+ expect(events[events.length - 1]?.type).toBe('turn_done');
224
+ expect(existsSync(join(tmp, 'greetings', 'alice', 'hello.txt'))).toBe(true);
225
+ });
226
+
227
+ it('L2:--input 优先级高于位置参数 —— 同槽位的位置参数被丢弃但会 yield warning', async () => {
228
+ // rawInputs={name:'bob'} (来自 --input);positional=['alice'] 对应 inputs[0]=name (已占) → 丢弃 + warn
229
+ // 最终 name=bob, lang=default(en)
230
+ const events = await runCmd('/workflow hello-workflow alice --input name=bob');
231
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
232
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
233
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
234
+ expect(events[events.length - 1]?.type).toBe('turn_done');
235
+ expect(existsSync(join(tmp, 'greetings', 'bob'))).toBe(true);
236
+ expect(existsSync(join(tmp, 'greetings', 'alice'))).toBe(false);
237
+ expect(readFileSync(join(tmp, 'greetings', 'bob', 'hello.txt'), 'utf8'))
238
+ .toBe('Hello, bob! (lang=en)');
239
+
240
+ // 必须有一条 warning 提示位置参数被覆盖
241
+ const warn = events.find(
242
+ (e): e is { type: 'warning'; message: string } => e.type === 'warning',
243
+ );
244
+ expect(warn).toBeDefined();
245
+ expect(warn!.message).toContain('被覆盖');
246
+ expect(warn!.message).toContain('alice');
247
+ expect(warn!.message).toContain('name');
248
+ });
249
+
250
+ it('L2:位置 + --input 混用 —— 位置填 name,--input 填 lang', async () => {
251
+ // 位置 alice 填 name;--input lang=zh 填 lang(不冲突)
252
+ const events = await runCmd('/workflow hello-workflow alice --input lang=zh');
253
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
254
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
255
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
256
+ expect(events[events.length - 1]?.type).toBe('turn_done');
257
+ expect(readFileSync(join(tmp, 'greetings', 'alice', 'hello.txt'), 'utf8'))
258
+ .toBe('Hello, alice! (lang=zh)');
259
+ });
260
+
261
+ it('L2:多余的位置参数被丢弃但会 yield warning(不影响执行)', async () => {
262
+ // 第 3、4 个位置参数 extra1/extra2 没有 input 槽位 → 丢弃 + warn
263
+ const events = await runCmd('/workflow hello-workflow alice zh extra1 extra2');
264
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
265
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
266
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
267
+ expect(events[events.length - 1]?.type).toBe('turn_done');
268
+ expect(readFileSync(join(tmp, 'greetings', 'alice', 'hello.txt'), 'utf8'))
269
+ .toBe('Hello, alice! (lang=zh)');
270
+
271
+ // 必须有一条 warning 提示多余参数被忽略,且列出具体值
272
+ const warn = events.find(
273
+ (e): e is { type: 'warning'; message: string } => e.type === 'warning',
274
+ );
275
+ expect(warn).toBeDefined();
276
+ expect(warn!.message).toContain('忽略了 2 个');
277
+ expect(warn!.message).toContain('extra1');
278
+ expect(warn!.message).toContain('extra2');
279
+ });
280
+
281
+ it('L2:位置参数刚好等于 inputs 数量 —— 无 warning', async () => {
282
+ // alice zh 正好填满 name + lang,无超量、无覆盖 → 不应有 warning
283
+ const events = await runCmd('/workflow hello-workflow alice zh');
284
+ expect(events.some((e) => e.type === 'warning')).toBe(false);
285
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(true);
286
+ // 命令路径(runWorkflowFromCommand)成功后补发通用 turn_done 作终结事件(对外协议收口),
287
+ // 故 turn_done 是最后一条、workflow_done 在其前一条。直测 runWorkflow 的 runner.test.ts 仍以 workflow_done 收尾。
288
+ expect(events[events.length - 1]?.type).toBe('turn_done');
289
+ });
290
+
291
+ it('/workflows 列表能看到 hello-workflow(cwd 优先双源加载)', async () => {
292
+ const events = await runCmd('/workflows');
293
+ const text = events
294
+ .filter((e): e is Extract<LoopEvent, { type: 'text' }> => e.type === 'text')
295
+ .map((e) => e.delta)
296
+ .join('');
297
+ expect(text).toContain('hello-workflow');
298
+ });
299
+ });
300
+
301
+ describe('workflow E2E:assert 失败 → halt(用变体 fixture)', () => {
302
+ let tmp: string;
303
+
304
+ beforeEach(() => {
305
+ tmp = mkdtempSync(join(tmpdir(), 'wf-e2e-fail-'));
306
+ mkdirSync(join(tmp, 'workflows'), { recursive: true });
307
+ // 写一个故意 assert 不通过的 yaml
308
+ const failYaml = `name: assert-fail-fixture
309
+ description: assert 必失败的端到端 fixture
310
+ steps:
311
+ - id: write_a
312
+ tool: Write
313
+ args:
314
+ file_path: "a.txt"
315
+ content: "hi"
316
+ - id: bad_assert
317
+ type: assert
318
+ condition: '1 == 2'
319
+ onFail: "故意失败"
320
+ - id: should_not_run
321
+ tool: Write
322
+ args:
323
+ file_path: "b.txt"
324
+ content: "should not exist"
325
+ `;
326
+ require('node:fs').writeFileSync(
327
+ join(tmp, 'workflows', 'assert-fail-fixture.yaml'),
328
+ failYaml,
329
+ 'utf8',
330
+ );
331
+
332
+ process.env.MINIMAL_AGENT_CWD = tmp;
333
+ _resetWorkingDir();
334
+ _resetPluginCache();
335
+ mock.module('../../../src/bootstrap/workingDir.js', () => ({
336
+ getWorkingDir: () => tmp,
337
+ initWorkingDir: () => tmp,
338
+ _resetWorkingDir: () => {},
339
+ }));
340
+ });
341
+
342
+ afterEach(() => {
343
+ delete process.env.MINIMAL_AGENT_CWD;
344
+ _resetWorkingDir();
345
+ _resetPluginCache();
346
+ rmSync(tmp, { recursive: true, force: true });
347
+ });
348
+
349
+ it('assert 失败 → 后续 step 不执行 + yield error + 没 workflow_done', async () => {
350
+ const { runWithPlugins } = await import('../../../src/plugins/pluginRunner.js');
351
+ const events = await collect(
352
+ runWithPlugins('/workflow assert-fail-fixture', { provider, history: [] }),
353
+ );
354
+
355
+ expect(events.some((e) => e.type === 'workflow_done')).toBe(false);
356
+ expect(events.some((e) => e.type === 'error')).toBe(true);
357
+
358
+ // a.txt 在 assert 前就写盘,应存在
359
+ expect(existsSync(join(tmp, 'a.txt'))).toBe(true);
360
+ // b.txt 在 assert 后,不应被写
361
+ expect(existsSync(join(tmp, 'b.txt'))).toBe(false);
362
+
363
+ // 错误信息含 onFail 自定义文案
364
+ const errs = events
365
+ .filter((e): e is Extract<LoopEvent, { type: 'error' }> => e.type === 'error')
366
+ .map((e) => e.error)
367
+ .join('\n');
368
+ expect(errs).toContain('故意失败');
369
+ });
370
+ });