minimal-agent 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. package/README.md +6 -5
  2. package/package.json +60 -4
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +197 -206
  16. package/plugins/workflow-runner/src/index.ts +248 -249
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +400 -489
  28. package/plugins/workflow-runner/src/runner.ts +488 -560
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +151 -307
  54. package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -211
  58. package/plugins/workflow-runner/src/types.ts +154 -623
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +474 -802
  68. package/plugins/workflow-runner/src/workflowState.ts +575 -970
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/ui-design/SKILL.md +5 -2
  92. package/skills/ui-design/references/layout-composition.md +1 -1
  93. package/skills/workflow-creator/SKILL.md +344 -0
  94. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  95. package/skills/workflow-creator/references/api.md +216 -0
  96. package/skills/workflow-creator/references/migration.md +103 -0
  97. package/skills/workflow-creator/references/patterns.md +779 -0
  98. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  99. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  100. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  101. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  102. package/src/advisor/advisorCoordinator.js +503 -0
  103. package/src/advisor/advisorCoordinator.ts +657 -0
  104. package/src/cli/configMenu.tsx +0 -1
  105. package/src/cli/print.js +27 -6
  106. package/src/cli/print.ts +48 -4
  107. package/src/cli/streamJson.js +5 -6
  108. package/src/cli/streamJson.ts +8 -4
  109. package/src/cli/wizards/advisorWizard.js +4 -2
  110. package/src/cli/wizards/advisorWizard.tsx +8 -2
  111. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  112. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  113. package/src/collaboration/taskFlow.js +115 -0
  114. package/src/collaboration/taskFlow.ts +169 -0
  115. package/src/config/configDefaults.js +41 -1
  116. package/src/config/configDefaults.ts +43 -1
  117. package/src/config/configDiagnostics.ts +2 -2
  118. package/src/config/configFile.js +40 -9
  119. package/src/config/configFile.ts +60 -11
  120. package/src/config/configService.js +419 -74
  121. package/src/config/configService.ts +703 -145
  122. package/src/config/numericLimits.js +22 -0
  123. package/src/config/numericLimits.ts +24 -0
  124. package/src/config.js +1 -0
  125. package/src/config.ts +3 -2
  126. package/src/context/budgetPlan.js +1 -1
  127. package/src/context/budgetPlan.ts +1 -1
  128. package/src/context/compact.js +7 -2
  129. package/src/context/compact.ts +9 -2
  130. package/src/context/microCompactLite.js +212 -13
  131. package/src/context/microCompactLite.ts +261 -12
  132. package/src/context/sessionRegistry.js +8 -1
  133. package/src/context/sessionRegistry.ts +18 -5
  134. package/src/context/toolResultArchive.js +72 -0
  135. package/src/context/toolResultArchive.ts +95 -0
  136. package/src/goal/errors.js +8 -0
  137. package/src/goal/errors.ts +18 -0
  138. package/src/goal/goalRuntime.js +344 -0
  139. package/src/goal/goalRuntime.ts +466 -0
  140. package/src/goal/goalStore.js +200 -0
  141. package/src/goal/goalStore.ts +219 -0
  142. package/src/goal/index.js +4 -0
  143. package/src/goal/index.ts +4 -0
  144. package/src/goal/statusMapping.js +71 -0
  145. package/src/goal/statusMapping.ts +95 -0
  146. package/src/goal/types.js +7 -0
  147. package/src/goal/types.ts +179 -0
  148. package/src/goal/workflowController.js +306 -0
  149. package/src/goal/workflowController.ts +360 -0
  150. package/src/harness/evidenceLedger.js +2 -0
  151. package/src/harness/evidenceLedger.ts +1 -0
  152. package/src/harness/stallGuard.js +9 -3
  153. package/src/harness/stallGuard.ts +7 -3
  154. package/src/images/imageResolver.js +161 -0
  155. package/src/images/imageResolver.ts +189 -0
  156. package/src/infra/verification.js +34 -5
  157. package/src/infra/verification.ts +39 -4
  158. package/src/llm/client.js +79 -21
  159. package/src/llm/client.ts +182 -35
  160. package/src/llm/gatewayClient.js +201 -0
  161. package/src/llm/gatewayClient.ts +299 -0
  162. package/src/llm/messageProvenance.js +46 -0
  163. package/src/llm/messageProvenance.ts +58 -0
  164. package/src/llm/modelProvider.js +1 -0
  165. package/src/llm/modelProvider.ts +22 -0
  166. package/src/llm/providers/anthropicFormat.js +108 -1
  167. package/src/llm/providers/anthropicFormat.ts +115 -4
  168. package/src/llm/providers/openaiFormat.js +9 -0
  169. package/src/llm/providers/openaiFormat.ts +12 -3
  170. package/src/loop.js +319 -54
  171. package/src/loop.ts +373 -57
  172. package/src/main.js +4 -1
  173. package/src/main.tsx +4 -1
  174. package/src/managedExecution/index.js +2 -0
  175. package/src/managedExecution/index.ts +2 -0
  176. package/src/managedExecution/registry.js +15 -0
  177. package/src/managedExecution/registry.ts +20 -0
  178. package/src/managedExecution/types.js +1 -0
  179. package/src/managedExecution/types.ts +86 -0
  180. package/src/modelRef.js +9 -0
  181. package/src/modelRef.ts +31 -0
  182. package/src/multimodal.js +14 -28
  183. package/src/multimodal.ts +17 -33
  184. package/src/net/lanDiscovery.js +65 -0
  185. package/src/net/lanDiscovery.ts +82 -0
  186. package/src/plugin-sdk.js +28 -3
  187. package/src/plugin-sdk.ts +39 -6
  188. package/src/plugins/pluginApi.ts +16 -1
  189. package/src/plugins/pluginLoader.js +4 -0
  190. package/src/plugins/pluginLoader.ts +6 -0
  191. package/src/plugins/pluginRunner.js +139 -7
  192. package/src/plugins/pluginRunner.ts +173 -9
  193. package/src/plugins/slashFallback.js +113 -0
  194. package/src/plugins/slashFallback.ts +137 -0
  195. package/src/prompts/policyRegistry.js +12 -32
  196. package/src/prompts/policyRegistry.ts +15 -32
  197. package/src/prompts/promptAssembly.js +103 -12
  198. package/src/prompts/promptAssembly.ts +114 -12
  199. package/src/prompts/skillList.js +21 -3
  200. package/src/prompts/skillList.ts +27 -2
  201. package/src/prompts/stableSystemPrompt.js +18 -0
  202. package/src/prompts/stableSystemPrompt.ts +33 -0
  203. package/src/runtime/agentRuntimeContext.js +8 -2
  204. package/src/runtime/agentRuntimeContext.ts +17 -4
  205. package/src/session/runAgentTurn.js +78 -12
  206. package/src/session/runAgentTurn.ts +105 -23
  207. package/src/session/runTurn.js +10 -9
  208. package/src/session/runTurn.ts +17 -8
  209. package/src/session/sessionState.ts +2 -0
  210. package/src/team/auth.js +186 -0
  211. package/src/team/auth.ts +298 -0
  212. package/src/team/cli.js +105 -0
  213. package/src/team/cli.ts +122 -0
  214. package/src/team/onlineToolContract.js +64 -0
  215. package/src/team/onlineToolContract.ts +109 -0
  216. package/src/team/toolPolicy.js +36 -0
  217. package/src/team/toolPolicy.ts +75 -0
  218. package/src/tools/advisor/advisor.js +90 -46
  219. package/src/tools/advisor/advisor.ts +100 -47
  220. package/src/tools/advisor/advisorDecision.js +71 -0
  221. package/src/tools/advisor/advisorDecision.ts +85 -0
  222. package/src/tools/agent/agent.js +18 -5
  223. package/src/tools/agent/agent.ts +21 -7
  224. package/src/tools/agent/agentControl.js +246 -0
  225. package/src/tools/agent/agentControl.ts +284 -0
  226. package/src/tools/agent/agentTaskService.js +40 -1
  227. package/src/tools/agent/agentTaskService.ts +44 -1
  228. package/src/tools/agent/agentTypes.js +14 -2
  229. package/src/tools/agent/agentTypes.ts +15 -2
  230. package/src/tools/applypatch/applyPatch.js +362 -0
  231. package/src/tools/applypatch/applyPatch.ts +428 -0
  232. package/src/tools/bash/bash.js +22 -15
  233. package/src/tools/bash/bash.ts +23 -17
  234. package/src/tools/capabilities.js +10 -5
  235. package/src/tools/capabilities.ts +10 -5
  236. package/src/tools/computeruse/computerIntent.js +32 -0
  237. package/src/tools/computeruse/computerIntent.ts +46 -0
  238. package/src/tools/computeruse/telemetryLog.js +12 -0
  239. package/src/tools/computeruse/telemetryLog.ts +16 -0
  240. package/src/tools/computeruse/vision/telemetry.js +1 -0
  241. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  242. package/src/tools/effectiveToolSet.js +4 -0
  243. package/src/tools/effectiveToolSet.ts +4 -0
  244. package/src/tools/index.js +21 -2
  245. package/src/tools/index.ts +33 -2
  246. package/src/tools/presentresult/presentresult.js +6 -15
  247. package/src/tools/presentresult/presentresult.ts +6 -15
  248. package/src/tools/read/read.js +62 -9
  249. package/src/tools/read/read.ts +63 -9
  250. package/src/tools/shared/fileUtils.js +1 -2
  251. package/src/tools/shared/fileUtils.ts +1 -2
  252. package/src/tools/shared/toolOutputStore.js +23 -1
  253. package/src/tools/shared/toolOutputStore.ts +22 -1
  254. package/src/tools/skill/skill.js +9 -3
  255. package/src/tools/skill/skill.ts +8 -4
  256. package/src/tools/toolPromptGuides.js +117 -0
  257. package/src/tools/toolPromptGuides.ts +137 -0
  258. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  259. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  260. package/src/tools/webbrowser/browser.js +27 -11
  261. package/src/tools/webbrowser/browser.ts +68 -24
  262. package/src/tools/webbrowser/browserAutomation.js +329 -0
  263. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  264. package/src/tools/webbrowser/browserAxState.js +97 -0
  265. package/src/tools/webbrowser/browserAxState.ts +142 -0
  266. package/src/tools/webbrowser/browserBatch.js +1578 -0
  267. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  268. package/src/tools/webbrowser/browserCdp.js +208 -0
  269. package/src/tools/webbrowser/browserCdp.ts +255 -0
  270. package/src/tools/webbrowser/browserConditions.js +24 -0
  271. package/src/tools/webbrowser/browserConditions.ts +45 -0
  272. package/src/tools/webbrowser/browserContent.js +281 -0
  273. package/src/tools/webbrowser/browserContent.ts +438 -0
  274. package/src/tools/webbrowser/browserControl.js +131 -34
  275. package/src/tools/webbrowser/browserControl.ts +210 -56
  276. package/src/tools/webbrowser/browserDaemon.js +22 -14
  277. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  278. package/src/tools/webbrowser/browserFrames.js +47 -0
  279. package/src/tools/webbrowser/browserFrames.ts +54 -0
  280. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  281. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  282. package/src/tools/webbrowser/browserLocator.js +140 -0
  283. package/src/tools/webbrowser/browserLocator.ts +194 -0
  284. package/src/tools/webbrowser/browserOutcome.js +10 -17
  285. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  286. package/src/tools/webbrowser/browserRecovery.js +58 -0
  287. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  288. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  289. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  290. package/src/tools/webbrowser/browserService.js +28 -2
  291. package/src/tools/webbrowser/browserService.ts +49 -9
  292. package/src/tools/webbrowser/browserStream.js +132 -33
  293. package/src/tools/webbrowser/browserStream.ts +224 -50
  294. package/src/tools/webbrowser/browserTabs.js +48 -15
  295. package/src/tools/webbrowser/browserTabs.ts +178 -96
  296. package/src/tools/webbrowser/webbrowser.js +679 -144
  297. package/src/tools/webbrowser/webbrowser.ts +975 -236
  298. package/src/tools/webfetch/preapproved.js +0 -2
  299. package/src/tools/webfetch/preapproved.ts +0 -2
  300. package/src/tools/websearch/websearch.js +129 -8
  301. package/src/tools/websearch/websearch.ts +138 -6
  302. package/src/types.ts +440 -38
  303. package/src/ui/App.js +377 -58
  304. package/src/ui/App.tsx +576 -111
  305. package/src/ui/LiveArea.js +14 -8
  306. package/src/ui/LiveArea.tsx +14 -10
  307. package/src/ui/MessageList.js +63 -20
  308. package/src/ui/MessageList.tsx +101 -16
  309. package/src/ui/ModelPicker.js +7 -4
  310. package/src/ui/ModelPicker.tsx +55 -8
  311. package/src/ui/hooks/useChat.js +152 -163
  312. package/src/ui/hooks/useChat.ts +396 -334
  313. package/src/ui/loginCommand.js +56 -0
  314. package/src/ui/loginCommand.ts +63 -0
  315. package/src/ui/toolProgress.js +41 -0
  316. package/src/ui/toolProgress.ts +44 -0
  317. package/src/web/browserBridge.js +20 -2
  318. package/src/web/browserBridge.ts +20 -2
  319. package/src/web/protocol.ts +1897 -194
  320. package/workflows/adaptive-research/workflow.mjs +86 -0
  321. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  322. package/workflows/e2e-llm/workflow.mjs +52 -0
  323. package/workflows/e2e-smoke/workflow.mjs +59 -0
  324. package/workflows/e2e-vision-schema/README.md +48 -0
  325. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  326. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  327. package/workflows/file-census/README.md +14 -0
  328. package/workflows/file-census/scripts/.gitkeep +2 -0
  329. package/workflows/file-census/workflow.mjs +110 -0
  330. package/workflows/goal/README.md +166 -0
  331. package/workflows/goal/prompts/audit.md +57 -0
  332. package/workflows/goal/prompts/contract.md +56 -0
  333. package/workflows/goal/prompts/iterate.md +37 -0
  334. package/workflows/goal/workflow.mjs +501 -0
  335. package/workflows/illustrate/README.md +90 -0
  336. package/workflows/illustrate/assets/engines.json +20 -0
  337. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  338. package/workflows/illustrate/assets/extract-bing.js +33 -0
  339. package/workflows/illustrate/assets/extract-google.js +31 -0
  340. package/workflows/illustrate/workflow.mjs +301 -0
  341. package/workflows/plan/workflow.mjs +389 -0
  342. package/workflows/question-crops/README.md +35 -0
  343. package/workflows/question-crops/prompts/inventory.md +23 -0
  344. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  345. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  346. package/workflows/question-crops/workflow.mjs +172 -0
  347. package/workflows/ralph-loop/workflow.mjs +162 -0
  348. package/workflows/research/README.md +74 -0
  349. package/workflows/research/prompts/s0-goal.md +43 -0
  350. package/workflows/research/prompts/s1-early-exit.md +22 -0
  351. package/workflows/research/prompts/s1-map.md +58 -0
  352. package/workflows/research/prompts/s2-options.md +53 -0
  353. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  354. package/workflows/research/prompts/s4-verify.md +74 -0
  355. package/workflows/research/prompts/s5-synthesis.md +81 -0
  356. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  357. package/workflows/research/prompts/s6-package.md +32 -0
  358. package/workflows/research/scripts/build-report.mjs +98 -0
  359. package/workflows/research/workflow.mjs +288 -0
  360. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  361. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  362. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  363. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  364. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  365. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  366. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  367. package/workflows/wrong-question-docx/README.md +55 -0
  368. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  369. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  370. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  371. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  372. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  373. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  374. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  375. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  376. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  377. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  378. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  379. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  380. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  381. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  382. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  383. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  384. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  385. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  386. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  387. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  388. package/plugins/workflow-runner/src/expressions.js +0 -639
  389. package/plugins/workflow-runner/src/expressions.ts +0 -640
  390. package/plugins/workflow-runner/src/loader.js +0 -573
  391. package/plugins/workflow-runner/src/loader.ts +0 -721
  392. package/plugins/workflow-runner/src/multimodal.js +0 -38
  393. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  394. package/plugins/workflow-runner/src/recovery.js +0 -126
  395. package/plugins/workflow-runner/src/recovery.ts +0 -156
  396. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  397. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  398. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -42
  399. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
  400. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  401. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  402. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  403. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  404. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  405. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  406. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  407. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  408. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -227
  409. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
  410. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
  411. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
  412. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  413. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  414. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  415. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  416. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  417. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  418. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -167
  419. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  420. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  421. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +0 -314
  422. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
  423. package/plugins/workflow-runner/test/expressions.test.ts +0 -439
  424. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  425. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  426. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  427. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  428. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  429. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  430. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  431. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  432. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  433. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  434. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -106
  435. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  436. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  437. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -221
  438. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  439. package/plugins/workflow-runner/test/research.load.test.ts +0 -328
  440. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  441. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
  442. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  443. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
  444. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  445. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
  446. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
  447. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  448. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  449. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  450. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  451. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  452. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  453. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  454. package/src/advisorState.js +0 -22
  455. package/src/cli/configWizard.js +0 -293
  456. package/src/tools/toolProgressBridge.js +0 -48
  457. package/workflows/adaptive-research.yaml +0 -50
  458. package/workflows/book-review-short.yaml +0 -125
  459. package/workflows/e2e-write-greet.yaml +0 -31
  460. package/workflows/illustrate.yaml +0 -227
  461. package/workflows/plan.yaml +0 -470
  462. package/workflows/ppt.yaml +0 -1670
  463. package/workflows/ralph-loop.yaml +0 -201
  464. package/workflows/research.yaml +0 -873
  465. package/workflows/schema.json +0 -176
  466. package/workflows/webppt.yaml +0 -496
  467. package/workflows/wechat-reading-shots.yaml +0 -315
  468. package/workflows/word.yaml +0 -720
  469. package/workflows/youtube-shorts.yaml +0 -202
  470. /package/workflows/{research.md → research/DESIGN.md} +0 -0
@@ -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
+ }
@@ -28,7 +28,19 @@
28
28
  import { existsSync, readFileSync } from 'node:fs';
29
29
  import { spawn } from 'node:child_process';
30
30
  import { sanitizeCredentialEnv } from '../config/configService.js';
31
+ import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
31
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;
32
44
  /** 从 "type:value" 字符串解析为 Check 对象。 */
33
45
  function parseVerifyArg(arg) {
34
46
  const colonIdx = arg.indexOf(':');
@@ -38,7 +50,10 @@ function parseVerifyArg(arg) {
38
50
  const value = arg.slice(colonIdx + 1).trim();
39
51
  switch (type) {
40
52
  case 'shell':
41
- return { type: 'shell', command: value, timeout: 30_000 };
53
+ // ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
54
+ // 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
55
+ // 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
56
+ return { type: 'shell', command: value };
42
57
  case 'file_exists':
43
58
  return { type: 'file_exists', file: value };
44
59
  case 'file_contains': {
@@ -102,8 +117,18 @@ function runShell(command, timeout, cwd) {
102
117
  return new Promise((resolve) => {
103
118
  const isWin = process.platform === 'win32';
104
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
+ // 关掉这层转义不引入新的注入面。
105
130
  const child = isWin
106
- ? spawn('cmd', ['/c', command], opts)
131
+ ? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
107
132
  : spawn('bash', ['-c', command], opts);
108
133
  let stdout = '';
109
134
  let stderr = '';
@@ -192,7 +217,8 @@ async function verifyTestCount(minCount, cwd = getWorkingDir()) {
192
217
  * 执行一组检查,返回聚合结果。
193
218
  * 全部通过 → passed=true;任一失败 → passed=false + summary 列出失败项。
194
219
  */
195
- export async function runVerification(checks, cwd = getWorkingDir()) {
220
+ export async function runVerification(checks, cwd = getWorkingDir(), options = {}) {
221
+ const shellTimeout = normalizeLimited(options.defaultShellTimeoutMs, CONFIG_LIMITS.verifyShellTimeoutMs, DEFAULT_SHELL_CHECK_TIMEOUT_MS);
196
222
  if (checks.length === 0) {
197
223
  return { passed: true, details: [], summary: '无验证项' };
198
224
  }
@@ -201,7 +227,7 @@ export async function runVerification(checks, cwd = getWorkingDir()) {
201
227
  let result;
202
228
  switch (check.type) {
203
229
  case 'shell':
204
- result = await verifyShell(check.command ?? '', check.timeout ?? 30_000, cwd);
230
+ result = await verifyShell(check.command ?? '', check.timeout ?? shellTimeout, cwd);
205
231
  break;
206
232
  case 'file_exists':
207
233
  result = verifyFileExists(check.file ?? '', cwd);
@@ -219,7 +245,10 @@ export async function runVerification(checks, cwd = getWorkingDir()) {
219
245
  output: `未知验证类型: ${check.type}`,
220
246
  };
221
247
  }
222
- results.push(result);
248
+ // 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
249
+ // 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
250
+ // 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
251
+ results.push({ ...result, check });
223
252
  }
224
253
  const allPassed = results.every((r) => r.passed);
225
254
  const failedNames = results.filter((r) => !r.passed).map((r) => formatCheckName(r.check));
@@ -29,9 +29,22 @@
29
29
  import { existsSync, readFileSync } from 'node:fs';
30
30
  import { spawn } from 'node:child_process';
31
31
  import { sanitizeCredentialEnv } from '../config/configService.js';
32
+ import { CONFIG_LIMITS, normalizeLimited } from '../config/numericLimits.js';
32
33
 
33
34
  import { getWorkingDir } from '../bootstrap/workingDir.js';
34
35
 
36
+ /**
37
+ * `shell:` 检查的默认超时——**唯一真值**。
38
+ *
39
+ * ⚠️ 这个数字此前在本文件里写了两份(`parseCheckString` 的构造处 + `runVerification`
40
+ * 的 `?? 30_000` 兜底),改一处漏一处就会出现「解析时 30s、执行时 60s」这种谁都看不出
41
+ * 的分裂。边界见 `config/numericLimits.ts::CONFIG_LIMITS.verifyShellTimeoutMs`。
42
+ *
43
+ * 为什么默认值不能太大:`verify()` 是工作流硬门,跑不完就阻塞整条流水线。
44
+ * 需要跑全量测试套件的场景应当缩到具体文件,或由调用方显式传 `defaultShellTimeoutMs`。
45
+ */
46
+ export const DEFAULT_SHELL_CHECK_TIMEOUT_MS = 30_000;
47
+
35
48
  export interface Check {
36
49
  type: 'shell' | 'file_contains' | 'file_exists' | 'test_count';
37
50
  command?: string;
@@ -63,7 +76,10 @@ function parseVerifyArg(arg: string): Check | null {
63
76
 
64
77
  switch (type) {
65
78
  case 'shell':
66
- return { type: 'shell', command: value, timeout: 30_000 };
79
+ // ⚠️ 这里**不填** timeout:解析层只管语法,超时是策略、由 runVerification 决定。
80
+ // 曾经在这里写死 30_000,于是 `check.timeout ?? 默认值` 里的默认值永远走不到 ——
81
+ // 调用方传的超时被一个「解析时顺手填上的」值静默盖掉。
82
+ return { type: 'shell', command: value };
67
83
  case 'file_exists':
68
84
  return { type: 'file_exists', file: value };
69
85
  case 'file_contains': {
@@ -142,8 +158,18 @@ function runShell(
142
158
  return new Promise((resolve) => {
143
159
  const isWin = process.platform === 'win32';
144
160
  const opts = { timeout, cwd, env: sanitizeCredentialEnv() };
161
+ // ⚠️ windowsVerbatimArguments: true 是必须的,不是可选优化:Node 给 cmd.exe/
162
+ // .bat 目标默认加一层它自己的参数转义(CVE-2024-27980 之后的安全加固),
163
+ // 会把 command 字符串里模型自己写好的转义(比如内嵌 PowerShell 单行脚本里的
164
+ // `\"..."`)再转义一遍,导致 cmd.exe 转交给子进程的字符串被改写、PowerShell
165
+ // 解析报 UnexpectedToken——文件内容明明字节级正确,check 却稳定失败。
166
+ // 已实测:一段合法的 `powershell -Command "...; Write-Host \"x\"; ..."`
167
+ // 不加这个选项必挂,加了立刻 exitCode 0;简单命令(echo/dir/bun --version)
168
+ // 行为不受影响。verify() 的 shell: 检查类型专供工作流的 hard_checks 使用,
169
+ // 命令来自可信的 LLM 契约草稿或用户输入,不是不可信的外部数据,
170
+ // 关掉这层转义不引入新的注入面。
145
171
  const child = isWin
146
- ? spawn('cmd', ['/c', command], opts)
172
+ ? spawn('cmd', ['/c', command], { ...opts, windowsVerbatimArguments: true })
147
173
  : spawn('bash', ['-c', command], opts);
148
174
 
149
175
  let stdout = '';
@@ -260,7 +286,13 @@ async function verifyTestCount(
260
286
  export async function runVerification(
261
287
  checks: Check[],
262
288
  cwd: string = getWorkingDir(),
289
+ options: { defaultShellTimeoutMs?: number } = {},
263
290
  ): Promise<VerifyResult> {
291
+ const shellTimeout = normalizeLimited(
292
+ options.defaultShellTimeoutMs,
293
+ CONFIG_LIMITS.verifyShellTimeoutMs,
294
+ DEFAULT_SHELL_CHECK_TIMEOUT_MS,
295
+ );
264
296
  if (checks.length === 0) {
265
297
  return { passed: true, details: [], summary: '无验证项' };
266
298
  }
@@ -274,7 +306,7 @@ export async function runVerification(
274
306
  case 'shell':
275
307
  result = await verifyShell(
276
308
  check.command ?? '',
277
- check.timeout ?? 30_000,
309
+ check.timeout ?? shellTimeout,
278
310
  cwd,
279
311
  );
280
312
  break;
@@ -295,7 +327,10 @@ export async function runVerification(
295
327
  };
296
328
  }
297
329
 
298
- results.push(result);
330
+ // 回填**原始** check:各 verifier 只收散参(command/file/pattern),返回时会
331
+ // 自己重建一个残缺的 check 对象,把 timeout 这类字段丢掉。details 是给人排障和
332
+ // 给工作流做卡死指纹用的,它必须如实反映「这一条到底是按什么跑的」。
333
+ results.push({ ...result, check });
299
334
  }
300
335
 
301
336
  const allPassed = results.every((r) => r.passed);