minimal-agent 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +6 -5
  2. package/package.json +62 -5
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +204 -185
  16. package/plugins/workflow-runner/src/index.ts +249 -224
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +404 -467
  28. package/plugins/workflow-runner/src/runner.ts +491 -535
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +172 -152
  54. package/plugins/workflow-runner/src/taskIdentity.ts +233 -200
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -105
  58. package/plugins/workflow-runner/src/types.ts +155 -520
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +542 -469
  68. package/plugins/workflow-runner/src/workflowState.ts +639 -589
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/skill-creator/scripts/run_eval.py +3 -2
  92. package/skills/ui-design/SKILL.md +5 -2
  93. package/skills/ui-design/references/layout-composition.md +1 -1
  94. package/skills/workflow-creator/SKILL.md +344 -0
  95. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  96. package/skills/workflow-creator/references/api.md +216 -0
  97. package/skills/workflow-creator/references/migration.md +103 -0
  98. package/skills/workflow-creator/references/patterns.md +779 -0
  99. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  100. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  101. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  102. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  103. package/src/advisor/advisorCoordinator.js +503 -0
  104. package/src/advisor/advisorCoordinator.ts +657 -0
  105. package/src/cli/configMenu.tsx +0 -1
  106. package/src/cli/print.js +44 -6
  107. package/src/cli/print.ts +73 -4
  108. package/src/cli/streamJson.js +5 -6
  109. package/src/cli/streamJson.ts +8 -4
  110. package/src/cli/wizards/advisorWizard.js +4 -2
  111. package/src/cli/wizards/advisorWizard.tsx +8 -2
  112. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  113. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  114. package/src/collaboration/taskFlow.js +115 -0
  115. package/src/collaboration/taskFlow.ts +169 -0
  116. package/src/config/configDefaults.js +41 -1
  117. package/src/config/configDefaults.ts +43 -1
  118. package/src/config/configDiagnostics.ts +2 -2
  119. package/src/config/configFile.js +40 -9
  120. package/src/config/configFile.ts +60 -11
  121. package/src/config/configService.js +443 -74
  122. package/src/config/configService.ts +723 -142
  123. package/src/config/numericLimits.js +22 -0
  124. package/src/config/numericLimits.ts +24 -0
  125. package/src/config.js +1 -0
  126. package/src/config.ts +3 -2
  127. package/src/context/budgetPlan.js +1 -1
  128. package/src/context/budgetPlan.ts +1 -1
  129. package/src/context/compact.js +7 -2
  130. package/src/context/compact.ts +9 -2
  131. package/src/context/microCompactLite.js +212 -13
  132. package/src/context/microCompactLite.ts +261 -12
  133. package/src/context/sessionRegistry.js +8 -1
  134. package/src/context/sessionRegistry.ts +18 -5
  135. package/src/context/toolResultArchive.js +72 -0
  136. package/src/context/toolResultArchive.ts +95 -0
  137. package/src/goal/errors.js +8 -0
  138. package/src/goal/errors.ts +18 -0
  139. package/src/goal/goalRuntime.js +344 -0
  140. package/src/goal/goalRuntime.ts +466 -0
  141. package/src/goal/goalStore.js +200 -0
  142. package/src/goal/goalStore.ts +219 -0
  143. package/src/goal/index.js +4 -0
  144. package/src/goal/index.ts +4 -0
  145. package/src/goal/statusMapping.js +71 -0
  146. package/src/goal/statusMapping.ts +95 -0
  147. package/src/goal/types.js +7 -0
  148. package/src/goal/types.ts +179 -0
  149. package/src/goal/workflowController.js +306 -0
  150. package/src/goal/workflowController.ts +360 -0
  151. package/src/harness/evidenceLedger.js +2 -0
  152. package/src/harness/evidenceLedger.ts +1 -0
  153. package/src/harness/stallGuard.js +9 -3
  154. package/src/harness/stallGuard.ts +7 -3
  155. package/src/images/imageResolver.js +161 -0
  156. package/src/images/imageResolver.ts +189 -0
  157. package/src/infra/verification.js +56 -20
  158. package/src/infra/verification.ts +87 -20
  159. package/src/llm/client.js +79 -21
  160. package/src/llm/client.ts +182 -35
  161. package/src/llm/gatewayClient.js +201 -0
  162. package/src/llm/gatewayClient.ts +299 -0
  163. package/src/llm/messageProvenance.js +46 -0
  164. package/src/llm/messageProvenance.ts +58 -0
  165. package/src/llm/modelProvider.js +1 -0
  166. package/src/llm/modelProvider.ts +22 -0
  167. package/src/llm/providers/anthropicFormat.js +108 -1
  168. package/src/llm/providers/anthropicFormat.ts +115 -4
  169. package/src/llm/providers/openaiFormat.js +9 -0
  170. package/src/llm/providers/openaiFormat.ts +12 -3
  171. package/src/loop.js +319 -54
  172. package/src/loop.ts +373 -57
  173. package/src/main.js +4 -1
  174. package/src/main.tsx +4 -1
  175. package/src/managedExecution/index.js +2 -0
  176. package/src/managedExecution/index.ts +2 -0
  177. package/src/managedExecution/registry.js +15 -0
  178. package/src/managedExecution/registry.ts +20 -0
  179. package/src/managedExecution/types.js +1 -0
  180. package/src/managedExecution/types.ts +86 -0
  181. package/src/modelRef.js +9 -0
  182. package/src/modelRef.ts +31 -0
  183. package/src/multimodal.js +14 -28
  184. package/src/multimodal.ts +17 -33
  185. package/src/net/lanDiscovery.js +65 -0
  186. package/src/net/lanDiscovery.ts +82 -0
  187. package/src/plugin-sdk.js +28 -3
  188. package/src/plugin-sdk.ts +39 -6
  189. package/src/plugins/pluginApi.ts +16 -1
  190. package/src/plugins/pluginLoader.js +4 -0
  191. package/src/plugins/pluginLoader.ts +6 -0
  192. package/src/plugins/pluginRunner.js +152 -9
  193. package/src/plugins/pluginRunner.ts +186 -8
  194. package/src/plugins/slashFallback.js +113 -0
  195. package/src/plugins/slashFallback.ts +137 -0
  196. package/src/prompts/policyRegistry.js +15 -8
  197. package/src/prompts/policyRegistry.ts +18 -8
  198. package/src/prompts/promptAssembly.js +103 -12
  199. package/src/prompts/promptAssembly.ts +114 -12
  200. package/src/prompts/skillList.js +21 -3
  201. package/src/prompts/skillList.ts +27 -2
  202. package/src/prompts/stableSystemPrompt.js +18 -0
  203. package/src/prompts/stableSystemPrompt.ts +33 -0
  204. package/src/runtime/agentRuntimeContext.js +8 -2
  205. package/src/runtime/agentRuntimeContext.ts +17 -4
  206. package/src/session/runAgentTurn.js +78 -12
  207. package/src/session/runAgentTurn.ts +105 -23
  208. package/src/session/runTurn.js +10 -9
  209. package/src/session/runTurn.ts +17 -8
  210. package/src/session/sessionState.ts +2 -0
  211. package/src/team/auth.js +186 -0
  212. package/src/team/auth.ts +298 -0
  213. package/src/team/cli.js +105 -0
  214. package/src/team/cli.ts +122 -0
  215. package/src/team/onlineToolContract.js +64 -0
  216. package/src/team/onlineToolContract.ts +109 -0
  217. package/src/team/toolPolicy.js +36 -0
  218. package/src/team/toolPolicy.ts +75 -0
  219. package/src/tools/advisor/advisor.js +148 -37
  220. package/src/tools/advisor/advisor.ts +161 -38
  221. package/src/tools/advisor/advisorDecision.js +71 -0
  222. package/src/tools/advisor/advisorDecision.ts +85 -0
  223. package/src/tools/agent/agent.js +18 -5
  224. package/src/tools/agent/agent.ts +21 -7
  225. package/src/tools/agent/agentControl.js +246 -0
  226. package/src/tools/agent/agentControl.ts +284 -0
  227. package/src/tools/agent/agentTaskService.js +40 -1
  228. package/src/tools/agent/agentTaskService.ts +44 -1
  229. package/src/tools/agent/agentTypes.js +14 -2
  230. package/src/tools/agent/agentTypes.ts +15 -2
  231. package/src/tools/applypatch/applyPatch.js +362 -0
  232. package/src/tools/applypatch/applyPatch.ts +428 -0
  233. package/src/tools/bash/bash.js +70 -18
  234. package/src/tools/bash/bash.ts +75 -20
  235. package/src/tools/capabilities.js +10 -5
  236. package/src/tools/capabilities.ts +10 -5
  237. package/src/tools/computeruse/computerIntent.js +32 -0
  238. package/src/tools/computeruse/computerIntent.ts +46 -0
  239. package/src/tools/computeruse/telemetryLog.js +12 -0
  240. package/src/tools/computeruse/telemetryLog.ts +16 -0
  241. package/src/tools/computeruse/vision/telemetry.js +1 -0
  242. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  243. package/src/tools/effectiveToolSet.js +4 -0
  244. package/src/tools/effectiveToolSet.ts +4 -0
  245. package/src/tools/index.js +21 -2
  246. package/src/tools/index.ts +33 -2
  247. package/src/tools/powershell/powershell.js +18 -1
  248. package/src/tools/powershell/powershell.ts +18 -2
  249. package/src/tools/presentresult/presentresult.js +6 -15
  250. package/src/tools/presentresult/presentresult.ts +6 -15
  251. package/src/tools/read/read.js +69 -11
  252. package/src/tools/read/read.ts +70 -11
  253. package/src/tools/shared/fileUtils.js +1 -2
  254. package/src/tools/shared/fileUtils.ts +1 -2
  255. package/src/tools/shared/permanentDeleteGuard.js +34 -3
  256. package/src/tools/shared/permanentDeleteGuard.ts +43 -6
  257. package/src/tools/shared/toolOutputStore.js +23 -1
  258. package/src/tools/shared/toolOutputStore.ts +22 -1
  259. package/src/tools/skill/skill.js +9 -3
  260. package/src/tools/skill/skill.ts +8 -4
  261. package/src/tools/toolPromptGuides.js +117 -0
  262. package/src/tools/toolPromptGuides.ts +137 -0
  263. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  264. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  265. package/src/tools/webbrowser/browser.js +27 -11
  266. package/src/tools/webbrowser/browser.ts +68 -24
  267. package/src/tools/webbrowser/browserAutomation.js +329 -0
  268. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  269. package/src/tools/webbrowser/browserAxState.js +97 -0
  270. package/src/tools/webbrowser/browserAxState.ts +142 -0
  271. package/src/tools/webbrowser/browserBatch.js +1578 -0
  272. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  273. package/src/tools/webbrowser/browserCdp.js +208 -0
  274. package/src/tools/webbrowser/browserCdp.ts +255 -0
  275. package/src/tools/webbrowser/browserConditions.js +24 -0
  276. package/src/tools/webbrowser/browserConditions.ts +45 -0
  277. package/src/tools/webbrowser/browserContent.js +281 -0
  278. package/src/tools/webbrowser/browserContent.ts +438 -0
  279. package/src/tools/webbrowser/browserControl.js +131 -30
  280. package/src/tools/webbrowser/browserControl.ts +210 -50
  281. package/src/tools/webbrowser/browserDaemon.js +22 -14
  282. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  283. package/src/tools/webbrowser/browserFrames.js +47 -0
  284. package/src/tools/webbrowser/browserFrames.ts +54 -0
  285. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  286. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  287. package/src/tools/webbrowser/browserLocator.js +140 -0
  288. package/src/tools/webbrowser/browserLocator.ts +194 -0
  289. package/src/tools/webbrowser/browserOutcome.js +10 -17
  290. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  291. package/src/tools/webbrowser/browserRecovery.js +58 -0
  292. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  293. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  294. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  295. package/src/tools/webbrowser/browserService.js +28 -2
  296. package/src/tools/webbrowser/browserService.ts +49 -9
  297. package/src/tools/webbrowser/browserStream.js +132 -33
  298. package/src/tools/webbrowser/browserStream.ts +224 -50
  299. package/src/tools/webbrowser/browserTabs.js +48 -15
  300. package/src/tools/webbrowser/browserTabs.ts +178 -96
  301. package/src/tools/webbrowser/webbrowser.js +683 -144
  302. package/src/tools/webbrowser/webbrowser.ts +979 -236
  303. package/src/tools/webfetch/preapproved.js +0 -2
  304. package/src/tools/webfetch/preapproved.ts +0 -2
  305. package/src/tools/websearch/websearch.js +129 -8
  306. package/src/tools/websearch/websearch.ts +138 -6
  307. package/src/types.ts +450 -42
  308. package/src/ui/App.js +377 -58
  309. package/src/ui/App.tsx +576 -111
  310. package/src/ui/LiveArea.js +14 -8
  311. package/src/ui/LiveArea.tsx +14 -10
  312. package/src/ui/MessageList.js +63 -20
  313. package/src/ui/MessageList.tsx +101 -16
  314. package/src/ui/ModelPicker.js +7 -4
  315. package/src/ui/ModelPicker.tsx +55 -8
  316. package/src/ui/hooks/useChat.js +152 -163
  317. package/src/ui/hooks/useChat.ts +396 -334
  318. package/src/ui/loginCommand.js +56 -0
  319. package/src/ui/loginCommand.ts +63 -0
  320. package/src/ui/toolProgress.js +41 -0
  321. package/src/ui/toolProgress.ts +44 -0
  322. package/src/web/browserBridge.js +20 -2
  323. package/src/web/browserBridge.ts +20 -2
  324. package/src/web/protocol.ts +1897 -194
  325. package/workflows/adaptive-research/workflow.mjs +86 -0
  326. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  327. package/workflows/e2e-llm/workflow.mjs +52 -0
  328. package/workflows/e2e-smoke/workflow.mjs +59 -0
  329. package/workflows/e2e-vision-schema/README.md +48 -0
  330. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  331. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  332. package/workflows/file-census/README.md +14 -0
  333. package/workflows/file-census/scripts/.gitkeep +2 -0
  334. package/workflows/file-census/workflow.mjs +110 -0
  335. package/workflows/goal/README.md +166 -0
  336. package/workflows/goal/prompts/audit.md +57 -0
  337. package/workflows/goal/prompts/contract.md +56 -0
  338. package/workflows/goal/prompts/iterate.md +37 -0
  339. package/workflows/goal/workflow.mjs +501 -0
  340. package/workflows/illustrate/README.md +90 -0
  341. package/workflows/illustrate/assets/engines.json +20 -0
  342. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  343. package/workflows/illustrate/assets/extract-bing.js +33 -0
  344. package/workflows/illustrate/assets/extract-google.js +31 -0
  345. package/workflows/illustrate/workflow.mjs +301 -0
  346. package/workflows/plan/workflow.mjs +389 -0
  347. package/workflows/question-crops/README.md +35 -0
  348. package/workflows/question-crops/prompts/inventory.md +23 -0
  349. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  350. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  351. package/workflows/question-crops/workflow.mjs +172 -0
  352. package/workflows/ralph-loop/workflow.mjs +162 -0
  353. package/workflows/research/DESIGN.md +449 -0
  354. package/workflows/research/README.md +74 -0
  355. package/workflows/research/prompts/s0-goal.md +43 -0
  356. package/workflows/research/prompts/s1-early-exit.md +22 -0
  357. package/workflows/research/prompts/s1-map.md +58 -0
  358. package/workflows/research/prompts/s2-options.md +53 -0
  359. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  360. package/workflows/research/prompts/s4-verify.md +74 -0
  361. package/workflows/research/prompts/s5-synthesis.md +81 -0
  362. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  363. package/workflows/research/prompts/s6-package.md +32 -0
  364. package/workflows/research/scripts/build-report.mjs +98 -0
  365. package/workflows/research/workflow.mjs +288 -0
  366. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  367. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  368. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  369. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  370. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  371. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  372. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  373. package/workflows/wrong-question-docx/README.md +55 -0
  374. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  375. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  376. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  377. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  378. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  379. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  380. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  381. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  382. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  383. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  384. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  385. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  386. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  387. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  388. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  389. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  390. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  391. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  392. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  393. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  394. package/plugins/workflow-runner/src/expressions.js +0 -618
  395. package/plugins/workflow-runner/src/expressions.ts +0 -622
  396. package/plugins/workflow-runner/src/loader.js +0 -573
  397. package/plugins/workflow-runner/src/loader.ts +0 -721
  398. package/plugins/workflow-runner/src/multimodal.js +0 -38
  399. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  400. package/plugins/workflow-runner/src/recovery.js +0 -119
  401. package/plugins/workflow-runner/src/recovery.ts +0 -149
  402. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  403. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  404. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -41
  405. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -50
  406. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  407. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  408. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  409. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  410. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  411. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  412. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  413. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  414. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -208
  415. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -256
  416. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -58
  417. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -68
  418. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  419. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  420. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  421. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  422. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  423. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  424. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -147
  425. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  426. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  427. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -226
  428. package/plugins/workflow-runner/test/expressions.test.ts +0 -397
  429. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  430. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  431. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  432. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  433. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  434. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  435. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  436. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  437. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  438. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  439. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -87
  440. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  441. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  442. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -220
  443. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  444. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  445. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -379
  446. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  447. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -150
  448. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  449. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -338
  450. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -270
  451. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  452. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  453. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  454. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  455. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  456. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  457. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  458. package/src/advisorState.js +0 -22
  459. package/src/cli/configWizard.js +0 -293
  460. package/src/tools/toolProgressBridge.js +0 -48
  461. package/workflows/adaptive-research.yaml +0 -50
  462. package/workflows/book-review-short.yaml +0 -125
  463. package/workflows/e2e-write-greet.yaml +0 -31
  464. package/workflows/illustrate.yaml +0 -227
  465. package/workflows/plan.yaml +0 -470
  466. package/workflows/ppt.yaml +0 -1670
  467. package/workflows/ralph-loop.yaml +0 -199
  468. package/workflows/schema.json +0 -176
  469. package/workflows/webppt.yaml +0 -496
  470. package/workflows/wechat-reading-shots.yaml +0 -315
  471. package/workflows/word.yaml +0 -720
  472. package/workflows/youtube-shorts.yaml +0 -202
@@ -33,6 +33,7 @@ export class EvidenceLedger {
33
33
  #failedToolCalls = 0;
34
34
 
35
35
  record(toolName: string, argsJson: string, result: ToolResult): void {
36
+ if (!result.ok && result.disposition === 'deferred') return;
36
37
  this.#toolCalls += 1;
37
38
  if (!result.ok) this.#failedToolCalls += 1;
38
39
  const base: RunEvidenceEntry = {
@@ -18,6 +18,8 @@ export class StallGuard {
18
18
  for (let index = 0; index < calls.length; index += 1) {
19
19
  const call = calls[index];
20
20
  const result = results[index];
21
+ if (!result.ok && result.disposition === 'deferred')
22
+ continue;
21
23
  if (result.ok) {
22
24
  this.#lastFailure = '';
23
25
  this.#lastFailureCall = '';
@@ -41,9 +43,13 @@ export class StallGuard {
41
43
  };
42
44
  }
43
45
  }
44
- const batch = hash(calls
45
- .map((call, index) => {
46
- const result = results[index];
46
+ const batchItems = calls
47
+ .map((call, index) => ({ call, result: results[index] }))
48
+ .filter(({ result }) => result.ok || result.disposition !== 'deferred');
49
+ if (batchItems.length === 0)
50
+ return { stalled: false };
51
+ const batch = hash(batchItems
52
+ .map(({ call, result }) => {
47
53
  return `${call.function.name}\0${call.function.arguments}\0${result.ok ? result.content : result.error}`;
48
54
  })
49
55
  .join('\x01'));
@@ -35,6 +35,7 @@ export class StallGuard {
35
35
  for (let index = 0; index < calls.length; index += 1) {
36
36
  const call = calls[index]!;
37
37
  const result = results[index]!;
38
+ if (!result.ok && result.disposition === 'deferred') continue;
38
39
  if (result.ok) {
39
40
  this.#lastFailure = '';
40
41
  this.#lastFailureCall = '';
@@ -60,10 +61,13 @@ export class StallGuard {
60
61
  }
61
62
  }
62
63
 
64
+ const batchItems = calls
65
+ .map((call, index) => ({ call, result: results[index]! }))
66
+ .filter(({ result }) => result.ok || result.disposition !== 'deferred');
67
+ if (batchItems.length === 0) return { stalled: false };
63
68
  const batch = hash(
64
- calls
65
- .map((call, index) => {
66
- const result = results[index]!;
69
+ batchItems
70
+ .map(({ call, result }) => {
67
71
  return `${call.function.name}\0${call.function.arguments}\0${result.ok ? result.content : result.error}`;
68
72
  })
69
73
  .join('\x01'),
@@ -0,0 +1,161 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
3
+ import { extname, join } from 'node:path';
4
+ import { agentStateDir } from '../context/agentHome.js';
5
+ export const DEFAULT_MAX_IMAGE_INPUT_BYTES = 25 * 1024 * 1024;
6
+ export const DEFAULT_MAX_MODEL_IMAGE_BYTES = 10 * 1024 * 1024;
7
+ function is(bytes, offset, signature) {
8
+ return signature.every((value, index) => bytes[offset + index] === value);
9
+ }
10
+ /** Actual bytes are authoritative; names and declared MIME are never trusted. */
11
+ export function detectImageMime(bytes) {
12
+ if (bytes.length >= 8 && is(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
13
+ return 'image/png';
14
+ if (bytes.length >= 3 && is(bytes, 0, [0xff, 0xd8, 0xff]))
15
+ return 'image/jpeg';
16
+ if (bytes.length >= 6 && ['GIF87a', 'GIF89a'].includes(bytes.toString('ascii', 0, 6)))
17
+ return 'image/gif';
18
+ if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
19
+ return 'image/webp';
20
+ if (bytes.length >= 2 && bytes.toString('ascii', 0, 2) === 'BM')
21
+ return 'image/bmp';
22
+ if (bytes.length >= 4 && (is(bytes, 0, [0x49, 0x49, 0x2a, 0x00]) || is(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a])))
23
+ return 'image/tiff';
24
+ if (bytes.length >= 16 && bytes.toString('ascii', 4, 8) === 'ftyp') {
25
+ const brands = bytes.subarray(8, Math.min(bytes.length, 64)).toString('ascii');
26
+ if (/(?:avif|avis)/.test(brands))
27
+ return 'image/avif';
28
+ if (/(?:heic|heix|hevc|hevx|heim|heis|mif1|msf1)/.test(brands))
29
+ return 'image/heic';
30
+ }
31
+ const prefix = bytes.subarray(0, Math.min(bytes.length, 4096)).toString('utf8').replace(/^\uFEFF/, '').trimStart();
32
+ if (/^(?:<\?xml[^>]*>\s*)?<svg\b/i.test(prefix))
33
+ return 'image/svg+xml';
34
+ return null;
35
+ }
36
+ function declaredMimeForExtension(path) {
37
+ return {
38
+ '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
39
+ '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
40
+ '.tif': 'image/tiff', '.tiff': 'image/tiff', '.avif': 'image/avif',
41
+ '.heic': 'image/heic', '.heif': 'image/heic', '.svg': 'image/svg+xml',
42
+ }[extname(path).toLowerCase()];
43
+ }
44
+ async function loadSharp() {
45
+ return (await import('sharp')).default;
46
+ }
47
+ async function decodeHeic(bytes) {
48
+ const imported = await import('heic-convert');
49
+ const convert = imported.default;
50
+ if (typeof convert !== 'function')
51
+ throw new Error('HEIC 解码器未正确加载');
52
+ const output = await convert({ buffer: bytes, format: 'JPEG', quality: 0.92 });
53
+ return Buffer.from(output);
54
+ }
55
+ async function normalizeForModel(bytes, detected, maxOutputBytes) {
56
+ const sharp = await loadSharp();
57
+ const decoded = detected === 'image/heic' ? await decodeHeic(bytes) : bytes;
58
+ const image = sharp(decoded, { animated: false }).rotate().resize({
59
+ width: 4096,
60
+ height: 4096,
61
+ fit: 'inside',
62
+ withoutEnlargement: true,
63
+ });
64
+ const metadata = await image.metadata();
65
+ const preserveAlpha = metadata.hasAlpha === true || detected === 'image/svg+xml';
66
+ let output = preserveAlpha
67
+ ? await image.clone().png({ compressionLevel: 9 }).toBuffer()
68
+ : await image.clone().jpeg({ quality: 88, mozjpeg: true }).toBuffer();
69
+ if (output.length > maxOutputBytes && !preserveAlpha) {
70
+ output = await image.clone().jpeg({ quality: 75, mozjpeg: true }).toBuffer();
71
+ }
72
+ if (output.length > maxOutputBytes) {
73
+ throw new Error(`规范化后图片仍超过 ${(maxOutputBytes / 1024 / 1024).toFixed(0)}MB 上限`);
74
+ }
75
+ return { bytes: output, mime: preserveAlpha ? 'image/png' : 'image/jpeg' };
76
+ }
77
+ function modelMime(detected) {
78
+ return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(detected)
79
+ ? detected
80
+ : null;
81
+ }
82
+ export async function resolveLocalImage(path, options = {}) {
83
+ const info = await stat(path).catch((error) => {
84
+ throw new Error(`无法读取图片:${path}(${error.message})`);
85
+ });
86
+ if (!info.isFile())
87
+ throw new Error(`图片路径不是文件:${path}`);
88
+ const maxInputBytes = options.maxInputBytes ?? DEFAULT_MAX_IMAGE_INPUT_BYTES;
89
+ const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_MODEL_IMAGE_BYTES;
90
+ if (info.size > maxInputBytes)
91
+ throw new Error(`图片超过 ${(maxInputBytes / 1024 / 1024).toFixed(0)}MB 输入上限:${path}`);
92
+ const bytes = await readFile(path);
93
+ const detected = detectImageMime(bytes);
94
+ if (!detected) {
95
+ const declared = declaredMimeForExtension(path);
96
+ throw new Error(declared
97
+ ? `文件扩展名声明为 ${declared},但内容不是可解码图片:${path}`
98
+ : `无法从文件内容识别图片格式:${path}`);
99
+ }
100
+ const declared = declaredMimeForExtension(path);
101
+ const diagnostic = declared && declared !== detected
102
+ ? `扩展名声明 ${declared},实际内容为 ${detected}`
103
+ : undefined;
104
+ const directMime = modelMime(detected);
105
+ if (directMime && bytes.length <= maxOutputBytes) {
106
+ // Decode metadata once so truncated/corrupt files do not masquerade as images.
107
+ const sharp = await loadSharp();
108
+ const metadata = await sharp(bytes, { animated: detected === 'image/gif' }).metadata();
109
+ const needsOrientationFix = detected === 'image/jpeg' && (metadata.orientation ?? 1) > 1;
110
+ if (!needsOrientationFix) {
111
+ return {
112
+ originalPath: path,
113
+ path,
114
+ originalMime: detected,
115
+ mime: directMime,
116
+ size: bytes.length,
117
+ transformed: false,
118
+ ...(diagnostic ? { diagnostic } : {}),
119
+ };
120
+ }
121
+ }
122
+ const normalized = await normalizeForModel(bytes, detected, maxOutputBytes).catch((error) => {
123
+ throw new Error(`图片格式 ${detected} 无法规范化:${path}(${error.message})`);
124
+ });
125
+ const hash = createHash('sha256')
126
+ .update(bytes)
127
+ .update('\0model-image-v1\0')
128
+ .update(normalized.mime)
129
+ .digest('hex');
130
+ const extension = normalized.mime === 'image/png' ? '.png' : '.jpg';
131
+ const dir = join(agentStateDir(), 'image-derivatives', hash.slice(0, 2));
132
+ const target = join(dir, `${hash}${extension}`);
133
+ await mkdir(dir, { recursive: true, mode: 0o700 });
134
+ const exists = await stat(target).then((item) => item.isFile()).catch(() => false);
135
+ if (!exists) {
136
+ const temp = `${target}.${randomUUID()}.tmp`;
137
+ await writeFile(temp, normalized.bytes, { mode: 0o600 });
138
+ await rename(temp, target).catch(async () => {
139
+ const wonRace = await stat(target).then((item) => item.isFile()).catch(() => false);
140
+ if (!wonRace)
141
+ throw new Error(`无法保存图片派生文件:${target}`);
142
+ });
143
+ }
144
+ return {
145
+ originalPath: path,
146
+ path: target,
147
+ originalMime: detected,
148
+ mime: normalized.mime,
149
+ size: normalized.bytes.length,
150
+ transformed: true,
151
+ diagnostic: [diagnostic, `已规范化为 ${normalized.mime}`].filter(Boolean).join(';'),
152
+ };
153
+ }
154
+ export async function tryResolveLocalImage(path) {
155
+ try {
156
+ return await resolveLocalImage(path);
157
+ }
158
+ catch {
159
+ return null;
160
+ }
161
+ }
@@ -0,0 +1,189 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
3
+ import { extname, join } from 'node:path';
4
+
5
+ import { agentStateDir } from '../context/agentHome.js';
6
+
7
+ export const DEFAULT_MAX_IMAGE_INPUT_BYTES = 25 * 1024 * 1024;
8
+ export const DEFAULT_MAX_MODEL_IMAGE_BYTES = 10 * 1024 * 1024;
9
+
10
+ export type DetectedImageMime =
11
+ | 'image/jpeg'
12
+ | 'image/png'
13
+ | 'image/gif'
14
+ | 'image/webp'
15
+ | 'image/bmp'
16
+ | 'image/tiff'
17
+ | 'image/avif'
18
+ | 'image/heic'
19
+ | 'image/svg+xml';
20
+
21
+ export interface ResolvedLocalImage {
22
+ originalPath: string;
23
+ path: string;
24
+ originalMime: DetectedImageMime;
25
+ mime: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp';
26
+ size: number;
27
+ transformed: boolean;
28
+ diagnostic?: string;
29
+ }
30
+
31
+ function is(bytes: Buffer, offset: number, signature: readonly number[]): boolean {
32
+ return signature.every((value, index) => bytes[offset + index] === value);
33
+ }
34
+
35
+ /** Actual bytes are authoritative; names and declared MIME are never trusted. */
36
+ export function detectImageMime(bytes: Buffer): DetectedImageMime | null {
37
+ if (bytes.length >= 8 && is(bytes, 0, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return 'image/png';
38
+ if (bytes.length >= 3 && is(bytes, 0, [0xff, 0xd8, 0xff])) return 'image/jpeg';
39
+ if (bytes.length >= 6 && ['GIF87a', 'GIF89a'].includes(bytes.toString('ascii', 0, 6))) return 'image/gif';
40
+ if (bytes.length >= 12 && bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP') return 'image/webp';
41
+ if (bytes.length >= 2 && bytes.toString('ascii', 0, 2) === 'BM') return 'image/bmp';
42
+ if (bytes.length >= 4 && (is(bytes, 0, [0x49, 0x49, 0x2a, 0x00]) || is(bytes, 0, [0x4d, 0x4d, 0x00, 0x2a]))) return 'image/tiff';
43
+ if (bytes.length >= 16 && bytes.toString('ascii', 4, 8) === 'ftyp') {
44
+ const brands = bytes.subarray(8, Math.min(bytes.length, 64)).toString('ascii');
45
+ if (/(?:avif|avis)/.test(brands)) return 'image/avif';
46
+ if (/(?:heic|heix|hevc|hevx|heim|heis|mif1|msf1)/.test(brands)) return 'image/heic';
47
+ }
48
+ const prefix = bytes.subarray(0, Math.min(bytes.length, 4096)).toString('utf8').replace(/^\uFEFF/, '').trimStart();
49
+ if (/^(?:<\?xml[^>]*>\s*)?<svg\b/i.test(prefix)) return 'image/svg+xml';
50
+ return null;
51
+ }
52
+
53
+ function declaredMimeForExtension(path: string): string | undefined {
54
+ return ({
55
+ '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png',
56
+ '.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
57
+ '.tif': 'image/tiff', '.tiff': 'image/tiff', '.avif': 'image/avif',
58
+ '.heic': 'image/heic', '.heif': 'image/heic', '.svg': 'image/svg+xml',
59
+ } as Record<string, string | undefined>)[extname(path).toLowerCase()];
60
+ }
61
+
62
+ async function loadSharp() {
63
+ return (await import('sharp')).default;
64
+ }
65
+
66
+ async function decodeHeic(bytes: Buffer): Promise<Buffer> {
67
+ const imported = await import('heic-convert');
68
+ const convert = imported.default;
69
+ if (typeof convert !== 'function') throw new Error('HEIC 解码器未正确加载');
70
+ const output = await convert({ buffer: bytes, format: 'JPEG', quality: 0.92 });
71
+ return Buffer.from(output);
72
+ }
73
+
74
+ async function normalizeForModel(
75
+ bytes: Buffer,
76
+ detected: DetectedImageMime,
77
+ maxOutputBytes: number,
78
+ ): Promise<{ bytes: Buffer; mime: 'image/jpeg' | 'image/png' }> {
79
+ const sharp = await loadSharp();
80
+ const decoded = detected === 'image/heic' ? await decodeHeic(bytes) : bytes;
81
+ const image = sharp(decoded, { animated: false }).rotate().resize({
82
+ width: 4096,
83
+ height: 4096,
84
+ fit: 'inside',
85
+ withoutEnlargement: true,
86
+ });
87
+ const metadata = await image.metadata();
88
+ const preserveAlpha = metadata.hasAlpha === true || detected === 'image/svg+xml';
89
+ let output = preserveAlpha
90
+ ? await image.clone().png({ compressionLevel: 9 }).toBuffer()
91
+ : await image.clone().jpeg({ quality: 88, mozjpeg: true }).toBuffer();
92
+ if (output.length > maxOutputBytes && !preserveAlpha) {
93
+ output = await image.clone().jpeg({ quality: 75, mozjpeg: true }).toBuffer();
94
+ }
95
+ if (output.length > maxOutputBytes) {
96
+ throw new Error(`规范化后图片仍超过 ${(maxOutputBytes / 1024 / 1024).toFixed(0)}MB 上限`);
97
+ }
98
+ return { bytes: output, mime: preserveAlpha ? 'image/png' : 'image/jpeg' };
99
+ }
100
+
101
+ function modelMime(detected: DetectedImageMime): ResolvedLocalImage['mime'] | null {
102
+ return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(detected)
103
+ ? detected as ResolvedLocalImage['mime']
104
+ : null;
105
+ }
106
+
107
+ export async function resolveLocalImage(
108
+ path: string,
109
+ options: { maxInputBytes?: number; maxOutputBytes?: number } = {},
110
+ ): Promise<ResolvedLocalImage> {
111
+ const info = await stat(path).catch((error: Error) => {
112
+ throw new Error(`无法读取图片:${path}(${error.message})`);
113
+ });
114
+ if (!info.isFile()) throw new Error(`图片路径不是文件:${path}`);
115
+ const maxInputBytes = options.maxInputBytes ?? DEFAULT_MAX_IMAGE_INPUT_BYTES;
116
+ const maxOutputBytes = options.maxOutputBytes ?? DEFAULT_MAX_MODEL_IMAGE_BYTES;
117
+ if (info.size > maxInputBytes) throw new Error(`图片超过 ${(maxInputBytes / 1024 / 1024).toFixed(0)}MB 输入上限:${path}`);
118
+ const bytes = await readFile(path);
119
+ const detected = detectImageMime(bytes);
120
+ if (!detected) {
121
+ const declared = declaredMimeForExtension(path);
122
+ throw new Error(
123
+ declared
124
+ ? `文件扩展名声明为 ${declared},但内容不是可解码图片:${path}`
125
+ : `无法从文件内容识别图片格式:${path}`,
126
+ );
127
+ }
128
+ const declared = declaredMimeForExtension(path);
129
+ const diagnostic = declared && declared !== detected
130
+ ? `扩展名声明 ${declared},实际内容为 ${detected}`
131
+ : undefined;
132
+ const directMime = modelMime(detected);
133
+ if (directMime && bytes.length <= maxOutputBytes) {
134
+ // Decode metadata once so truncated/corrupt files do not masquerade as images.
135
+ const sharp = await loadSharp();
136
+ const metadata = await sharp(bytes, { animated: detected === 'image/gif' }).metadata();
137
+ const needsOrientationFix = detected === 'image/jpeg' && (metadata.orientation ?? 1) > 1;
138
+ if (!needsOrientationFix) {
139
+ return {
140
+ originalPath: path,
141
+ path,
142
+ originalMime: detected,
143
+ mime: directMime,
144
+ size: bytes.length,
145
+ transformed: false,
146
+ ...(diagnostic ? { diagnostic } : {}),
147
+ };
148
+ }
149
+ }
150
+
151
+ const normalized = await normalizeForModel(bytes, detected, maxOutputBytes).catch((error) => {
152
+ throw new Error(`图片格式 ${detected} 无法规范化:${path}(${(error as Error).message})`);
153
+ });
154
+ const hash = createHash('sha256')
155
+ .update(bytes)
156
+ .update('\0model-image-v1\0')
157
+ .update(normalized.mime)
158
+ .digest('hex');
159
+ const extension = normalized.mime === 'image/png' ? '.png' : '.jpg';
160
+ const dir = join(agentStateDir(), 'image-derivatives', hash.slice(0, 2));
161
+ const target = join(dir, `${hash}${extension}`);
162
+ await mkdir(dir, { recursive: true, mode: 0o700 });
163
+ const exists = await stat(target).then((item) => item.isFile()).catch(() => false);
164
+ if (!exists) {
165
+ const temp = `${target}.${randomUUID()}.tmp`;
166
+ await writeFile(temp, normalized.bytes, { mode: 0o600 });
167
+ await rename(temp, target).catch(async () => {
168
+ const wonRace = await stat(target).then((item) => item.isFile()).catch(() => false);
169
+ if (!wonRace) throw new Error(`无法保存图片派生文件:${target}`);
170
+ });
171
+ }
172
+ return {
173
+ originalPath: path,
174
+ path: target,
175
+ originalMime: detected,
176
+ mime: normalized.mime,
177
+ size: normalized.bytes.length,
178
+ transformed: true,
179
+ diagnostic: [diagnostic, `已规范化为 ${normalized.mime}`].filter(Boolean).join(';'),
180
+ };
181
+ }
182
+
183
+ export async function tryResolveLocalImage(path: string): Promise<ResolvedLocalImage | null> {
184
+ try {
185
+ return await resolveLocalImage(path);
186
+ } catch {
187
+ return null;
188
+ }
189
+ }
@@ -16,13 +16,31 @@
16
16
  * 设计原则:
17
17
  * - 纯工具函数,无全局状态,无副作用(除了 shell 子进程)
18
18
  * - 不依赖任何插件代码,可被 src/ 和 plugins/ 同时导入
19
- * - 工作目录隔离:所有相对路径以 getWorkingDir() 为锚
19
+ * - 工作目录隔离:相对路径以调用方给的 cwd 为锚,缺省 getWorkingDir()
20
+ *
21
+ * ⚠️ cwd 参数不是可有可无的修饰:**验收必须和写入锚在同一个目录**。
22
+ * 会话进入 session worktree 后(EnterWorktree),写文件的工具跟着 worktree 走
23
+ * (loop.ts 按 sessionState.activeWorktree 重定向 cwd),验收若仍锚原 workspace,
24
+ * 就会在空目录里找产物 —— file_exists 恒 false,工作流被判失败并反复重试,
25
+ * 而产物其实好好躺在 worktree 里。调用方拿得到有效 cwd 时务必传进来。
20
26
  * ============================================================
21
27
  */
22
28
  import { existsSync, readFileSync } from 'node:fs';
23
29
  import { spawn } from 'node:child_process';
24
30
  import { sanitizeCredentialEnv } from '../config/configService.js';
31
+ import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
25
32
  import { getWorkingDir } from '../bootstrap/workingDir.js';
33
+ /**
34
+ * `shell:` 检查的默认超时——**唯一真值**。
35
+ *
36
+ * ⚠️ 这个数字此前在本文件里写了两份(`parseCheckString` 的构造处 + `runVerification`
37
+ * 的 `?? 30_000` 兜底),改一处漏一处就会出现「解析时 30s、执行时 60s」这种谁都看不出
38
+ * 的分裂。边界见 `config/numericLimits.ts::CONFIG_LIMITS.verifyShellTimeoutMs`。
39
+ *
40
+ * 为什么默认值不能太大:`verify()` 是工作流硬门,跑不完就阻塞整条流水线。
41
+ * 需要跑全量测试套件的场景应当缩到具体文件,或由调用方显式传 `defaultShellTimeoutMs`。
42
+ */
43
+ export const DEFAULT_SHELL_CHECK_TIMEOUT_MS = 30_000;
26
44
  /** 从 "type:value" 字符串解析为 Check 对象。 */
27
45
  function parseVerifyArg(arg) {
28
46
  const colonIdx = arg.indexOf(':');
@@ -32,7 +50,10 @@ function parseVerifyArg(arg) {
32
50
  const value = arg.slice(colonIdx + 1).trim();
33
51
  switch (type) {
34
52
  case 'shell':
35
- return { type: 'shell', command: value, timeout: 30_000 };
53
+ // ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
54
+ // 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
55
+ // 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
56
+ return { type: 'shell', command: value };
36
57
  case 'file_exists':
37
58
  return { type: 'file_exists', file: value };
38
59
  case 'file_contains': {
@@ -92,12 +113,23 @@ function isAbsolutePath(file) {
92
113
  /^[A-Za-z]:[\\/]/.test(file) // C:\ 或 C:/
93
114
  );
94
115
  }
95
- function runShell(command, timeout) {
116
+ function runShell(command, timeout, cwd) {
96
117
  return new Promise((resolve) => {
97
118
  const isWin = process.platform === 'win32';
119
+ const opts = { timeout, cwd, env: sanitizeCredentialEnv() };
120
+ // ⚠️ windowsVerbatimArguments: true 是必须的,不是可选优化:Node 给 cmd.exe/
121
+ // .bat 目标默认加一层它自己的参数转义(CVE-2024-27980 之后的安全加固),
122
+ // 会把 command 字符串里模型自己写好的转义(比如内嵌 PowerShell 单行脚本里的
123
+ // `\"..."`)再转义一遍,导致 cmd.exe 转交给子进程的字符串被改写、PowerShell
124
+ // 解析报 UnexpectedToken——文件内容明明字节级正确,check 却稳定失败。
125
+ // 已实测:一段合法的 `powershell -Command "...; Write-Host \"x\"; ..."`
126
+ // 不加这个选项必挂,加了立刻 exitCode 0;简单命令(echo/dir/bun --version)
127
+ // 行为不受影响。verify() 的 shell: 检查类型专供工作流的 hard_checks 使用,
128
+ // 命令来自可信的 LLM 契约草稿或用户输入,不是不可信的外部数据,
129
+ // 关掉这层转义不引入新的注入面。
98
130
  const child = isWin
99
- ? spawn('cmd', ['/c', command], { timeout, env: sanitizeCredentialEnv() })
100
- : spawn('bash', ['-c', command], { timeout, env: sanitizeCredentialEnv() });
131
+ ? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
132
+ : spawn('bash', ['-c', command], opts);
101
133
  let stdout = '';
102
134
  let stderr = '';
103
135
  child.stdout.on('data', (d) => {
@@ -114,8 +146,8 @@ function runShell(command, timeout) {
114
146
  });
115
147
  });
116
148
  }
117
- export async function verifyShell(command, timeout) {
118
- const r = await runShell(command, timeout);
149
+ export async function verifyShell(command, timeout, cwd = getWorkingDir()) {
150
+ const r = await runShell(command, timeout, cwd);
119
151
  if (r.errored) {
120
152
  return {
121
153
  check: { type: 'shell', command },
@@ -129,9 +161,9 @@ export async function verifyShell(command, timeout) {
129
161
  output: r.stdout.trim() || r.stderr.trim() || `exit code ${r.exitCode}`,
130
162
  };
131
163
  }
132
- export function verifyFileExists(file) {
133
- // 相对路径以 workingDir 为锚(绝对路径识别含 UNC)
134
- const resolved = isAbsolutePath(file) ? file : `${getWorkingDir()}/${file}`;
164
+ export function verifyFileExists(file, cwd = getWorkingDir()) {
165
+ // 相对路径以调用方给的 cwd 为锚(绝对路径识别含 UNC)
166
+ const resolved = isAbsolutePath(file) ? file : `${cwd}/${file}`;
135
167
  const exists = existsSync(resolved);
136
168
  return {
137
169
  check: { type: 'file_exists', file },
@@ -139,8 +171,8 @@ export function verifyFileExists(file) {
139
171
  output: exists ? '文件存在' : `文件不存在: ${resolved}`,
140
172
  };
141
173
  }
142
- export function verifyFileContains(file, pattern) {
143
- const resolved = isAbsolutePath(file) ? file : `${getWorkingDir()}/${file}`;
174
+ export function verifyFileContains(file, pattern, cwd = getWorkingDir()) {
175
+ const resolved = isAbsolutePath(file) ? file : `${cwd}/${file}`;
144
176
  try {
145
177
  const content = readFileSync(resolved, 'utf8');
146
178
  const regex = new RegExp(pattern);
@@ -159,11 +191,11 @@ export function verifyFileContains(file, pattern) {
159
191
  };
160
192
  }
161
193
  }
162
- async function verifyTestCount(minCount) {
194
+ async function verifyTestCount(minCount, cwd = getWorkingDir()) {
163
195
  // 跨运行时:开发用 bun → bun test;npm 全局安装用 Node → npm test --silent
164
196
  // 用 process.versions.bun 探测比 typeof Bun !== 'undefined' 更稳(不依赖全局类型)
165
197
  const cmd = process.versions.bun ? 'bun test' : 'npm test --silent';
166
- const r = await runShell(cmd, 60_000);
198
+ const r = await runShell(cmd, 60_000, cwd);
167
199
  const combined = `${r.stdout}\n${r.stderr}`;
168
200
  if (r.errored) {
169
201
  return {
@@ -185,7 +217,8 @@ async function verifyTestCount(minCount) {
185
217
  * 执行一组检查,返回聚合结果。
186
218
  * 全部通过 → passed=true;任一失败 → passed=false + summary 列出失败项。
187
219
  */
188
- export async function runVerification(checks) {
220
+ export async function runVerification(checks, cwd = getWorkingDir(), options = {}) {
221
+ const shellTimeout = normalizeLimited(options.defaultShellTimeoutMs, CONFIG_LIMITS.verifyShellTimeoutMs, DEFAULT_SHELL_CHECK_TIMEOUT_MS);
189
222
  if (checks.length === 0) {
190
223
  return { passed: true, details: [], summary: '无验证项' };
191
224
  }
@@ -194,16 +227,16 @@ export async function runVerification(checks) {
194
227
  let result;
195
228
  switch (check.type) {
196
229
  case 'shell':
197
- result = await verifyShell(check.command ?? '', check.timeout ?? 30_000);
230
+ result = await verifyShell(check.command ?? '', check.timeout ?? shellTimeout, cwd);
198
231
  break;
199
232
  case 'file_exists':
200
- result = verifyFileExists(check.file ?? '');
233
+ result = verifyFileExists(check.file ?? '', cwd);
201
234
  break;
202
235
  case 'file_contains':
203
- result = verifyFileContains(check.file ?? '', check.pattern ?? '');
236
+ result = verifyFileContains(check.file ?? '', check.pattern ?? '', cwd);
204
237
  break;
205
238
  case 'test_count':
206
- result = await verifyTestCount(check.minCount ?? 0);
239
+ result = await verifyTestCount(check.minCount ?? 0, cwd);
207
240
  break;
208
241
  default:
209
242
  result = {
@@ -212,7 +245,10 @@ export async function runVerification(checks) {
212
245
  output: `未知验证类型: ${check.type}`,
213
246
  };
214
247
  }
215
- results.push(result);
248
+ // 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
249
+ // 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
250
+ // 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
251
+ results.push({ ...result, check });
216
252
  }
217
253
  const allPassed = results.every((r) => r.passed);
218
254
  const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));