minimal-agent 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. package/README.md +6 -5
  2. package/package.json +60 -4
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +197 -206
  16. package/plugins/workflow-runner/src/index.ts +248 -249
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +400 -489
  28. package/plugins/workflow-runner/src/runner.ts +488 -560
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +151 -307
  54. package/plugins/workflow-runner/src/taskIdentity.ts +207 -355
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -211
  58. package/plugins/workflow-runner/src/types.ts +154 -623
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +474 -802
  68. package/plugins/workflow-runner/src/workflowState.ts +575 -970
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/ui-design/SKILL.md +5 -2
  92. package/skills/ui-design/references/layout-composition.md +1 -1
  93. package/skills/workflow-creator/SKILL.md +344 -0
  94. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  95. package/skills/workflow-creator/references/api.md +216 -0
  96. package/skills/workflow-creator/references/migration.md +103 -0
  97. package/skills/workflow-creator/references/patterns.md +779 -0
  98. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  99. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  100. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  101. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  102. package/src/advisor/advisorCoordinator.js +503 -0
  103. package/src/advisor/advisorCoordinator.ts +657 -0
  104. package/src/cli/configMenu.tsx +0 -1
  105. package/src/cli/print.js +27 -6
  106. package/src/cli/print.ts +48 -4
  107. package/src/cli/streamJson.js +5 -6
  108. package/src/cli/streamJson.ts +8 -4
  109. package/src/cli/wizards/advisorWizard.js +4 -2
  110. package/src/cli/wizards/advisorWizard.tsx +8 -2
  111. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  112. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  113. package/src/collaboration/taskFlow.js +115 -0
  114. package/src/collaboration/taskFlow.ts +169 -0
  115. package/src/config/configDefaults.js +41 -1
  116. package/src/config/configDefaults.ts +43 -1
  117. package/src/config/configDiagnostics.ts +2 -2
  118. package/src/config/configFile.js +40 -9
  119. package/src/config/configFile.ts +60 -11
  120. package/src/config/configService.js +419 -74
  121. package/src/config/configService.ts +703 -145
  122. package/src/config/numericLimits.js +22 -0
  123. package/src/config/numericLimits.ts +24 -0
  124. package/src/config.js +1 -0
  125. package/src/config.ts +3 -2
  126. package/src/context/budgetPlan.js +1 -1
  127. package/src/context/budgetPlan.ts +1 -1
  128. package/src/context/compact.js +7 -2
  129. package/src/context/compact.ts +9 -2
  130. package/src/context/microCompactLite.js +212 -13
  131. package/src/context/microCompactLite.ts +261 -12
  132. package/src/context/sessionRegistry.js +8 -1
  133. package/src/context/sessionRegistry.ts +18 -5
  134. package/src/context/toolResultArchive.js +72 -0
  135. package/src/context/toolResultArchive.ts +95 -0
  136. package/src/goal/errors.js +8 -0
  137. package/src/goal/errors.ts +18 -0
  138. package/src/goal/goalRuntime.js +344 -0
  139. package/src/goal/goalRuntime.ts +466 -0
  140. package/src/goal/goalStore.js +200 -0
  141. package/src/goal/goalStore.ts +219 -0
  142. package/src/goal/index.js +4 -0
  143. package/src/goal/index.ts +4 -0
  144. package/src/goal/statusMapping.js +71 -0
  145. package/src/goal/statusMapping.ts +95 -0
  146. package/src/goal/types.js +7 -0
  147. package/src/goal/types.ts +179 -0
  148. package/src/goal/workflowController.js +306 -0
  149. package/src/goal/workflowController.ts +360 -0
  150. package/src/harness/evidenceLedger.js +2 -0
  151. package/src/harness/evidenceLedger.ts +1 -0
  152. package/src/harness/stallGuard.js +9 -3
  153. package/src/harness/stallGuard.ts +7 -3
  154. package/src/images/imageResolver.js +161 -0
  155. package/src/images/imageResolver.ts +189 -0
  156. package/src/infra/verification.js +34 -5
  157. package/src/infra/verification.ts +39 -4
  158. package/src/llm/client.js +79 -21
  159. package/src/llm/client.ts +182 -35
  160. package/src/llm/gatewayClient.js +201 -0
  161. package/src/llm/gatewayClient.ts +299 -0
  162. package/src/llm/messageProvenance.js +46 -0
  163. package/src/llm/messageProvenance.ts +58 -0
  164. package/src/llm/modelProvider.js +1 -0
  165. package/src/llm/modelProvider.ts +22 -0
  166. package/src/llm/providers/anthropicFormat.js +108 -1
  167. package/src/llm/providers/anthropicFormat.ts +115 -4
  168. package/src/llm/providers/openaiFormat.js +9 -0
  169. package/src/llm/providers/openaiFormat.ts +12 -3
  170. package/src/loop.js +319 -54
  171. package/src/loop.ts +373 -57
  172. package/src/main.js +4 -1
  173. package/src/main.tsx +4 -1
  174. package/src/managedExecution/index.js +2 -0
  175. package/src/managedExecution/index.ts +2 -0
  176. package/src/managedExecution/registry.js +15 -0
  177. package/src/managedExecution/registry.ts +20 -0
  178. package/src/managedExecution/types.js +1 -0
  179. package/src/managedExecution/types.ts +86 -0
  180. package/src/modelRef.js +9 -0
  181. package/src/modelRef.ts +31 -0
  182. package/src/multimodal.js +14 -28
  183. package/src/multimodal.ts +17 -33
  184. package/src/net/lanDiscovery.js +65 -0
  185. package/src/net/lanDiscovery.ts +82 -0
  186. package/src/plugin-sdk.js +28 -3
  187. package/src/plugin-sdk.ts +39 -6
  188. package/src/plugins/pluginApi.ts +16 -1
  189. package/src/plugins/pluginLoader.js +4 -0
  190. package/src/plugins/pluginLoader.ts +6 -0
  191. package/src/plugins/pluginRunner.js +139 -7
  192. package/src/plugins/pluginRunner.ts +173 -9
  193. package/src/plugins/slashFallback.js +113 -0
  194. package/src/plugins/slashFallback.ts +137 -0
  195. package/src/prompts/policyRegistry.js +12 -32
  196. package/src/prompts/policyRegistry.ts +15 -32
  197. package/src/prompts/promptAssembly.js +103 -12
  198. package/src/prompts/promptAssembly.ts +114 -12
  199. package/src/prompts/skillList.js +21 -3
  200. package/src/prompts/skillList.ts +27 -2
  201. package/src/prompts/stableSystemPrompt.js +18 -0
  202. package/src/prompts/stableSystemPrompt.ts +33 -0
  203. package/src/runtime/agentRuntimeContext.js +8 -2
  204. package/src/runtime/agentRuntimeContext.ts +17 -4
  205. package/src/session/runAgentTurn.js +78 -12
  206. package/src/session/runAgentTurn.ts +105 -23
  207. package/src/session/runTurn.js +10 -9
  208. package/src/session/runTurn.ts +17 -8
  209. package/src/session/sessionState.ts +2 -0
  210. package/src/team/auth.js +186 -0
  211. package/src/team/auth.ts +298 -0
  212. package/src/team/cli.js +105 -0
  213. package/src/team/cli.ts +122 -0
  214. package/src/team/onlineToolContract.js +64 -0
  215. package/src/team/onlineToolContract.ts +109 -0
  216. package/src/team/toolPolicy.js +36 -0
  217. package/src/team/toolPolicy.ts +75 -0
  218. package/src/tools/advisor/advisor.js +90 -46
  219. package/src/tools/advisor/advisor.ts +100 -47
  220. package/src/tools/advisor/advisorDecision.js +71 -0
  221. package/src/tools/advisor/advisorDecision.ts +85 -0
  222. package/src/tools/agent/agent.js +18 -5
  223. package/src/tools/agent/agent.ts +21 -7
  224. package/src/tools/agent/agentControl.js +246 -0
  225. package/src/tools/agent/agentControl.ts +284 -0
  226. package/src/tools/agent/agentTaskService.js +40 -1
  227. package/src/tools/agent/agentTaskService.ts +44 -1
  228. package/src/tools/agent/agentTypes.js +14 -2
  229. package/src/tools/agent/agentTypes.ts +15 -2
  230. package/src/tools/applypatch/applyPatch.js +362 -0
  231. package/src/tools/applypatch/applyPatch.ts +428 -0
  232. package/src/tools/bash/bash.js +22 -15
  233. package/src/tools/bash/bash.ts +23 -17
  234. package/src/tools/capabilities.js +10 -5
  235. package/src/tools/capabilities.ts +10 -5
  236. package/src/tools/computeruse/computerIntent.js +32 -0
  237. package/src/tools/computeruse/computerIntent.ts +46 -0
  238. package/src/tools/computeruse/telemetryLog.js +12 -0
  239. package/src/tools/computeruse/telemetryLog.ts +16 -0
  240. package/src/tools/computeruse/vision/telemetry.js +1 -0
  241. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  242. package/src/tools/effectiveToolSet.js +4 -0
  243. package/src/tools/effectiveToolSet.ts +4 -0
  244. package/src/tools/index.js +21 -2
  245. package/src/tools/index.ts +33 -2
  246. package/src/tools/presentresult/presentresult.js +6 -15
  247. package/src/tools/presentresult/presentresult.ts +6 -15
  248. package/src/tools/read/read.js +62 -9
  249. package/src/tools/read/read.ts +63 -9
  250. package/src/tools/shared/fileUtils.js +1 -2
  251. package/src/tools/shared/fileUtils.ts +1 -2
  252. package/src/tools/shared/toolOutputStore.js +23 -1
  253. package/src/tools/shared/toolOutputStore.ts +22 -1
  254. package/src/tools/skill/skill.js +9 -3
  255. package/src/tools/skill/skill.ts +8 -4
  256. package/src/tools/toolPromptGuides.js +117 -0
  257. package/src/tools/toolPromptGuides.ts +137 -0
  258. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  259. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  260. package/src/tools/webbrowser/browser.js +27 -11
  261. package/src/tools/webbrowser/browser.ts +68 -24
  262. package/src/tools/webbrowser/browserAutomation.js +329 -0
  263. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  264. package/src/tools/webbrowser/browserAxState.js +97 -0
  265. package/src/tools/webbrowser/browserAxState.ts +142 -0
  266. package/src/tools/webbrowser/browserBatch.js +1578 -0
  267. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  268. package/src/tools/webbrowser/browserCdp.js +208 -0
  269. package/src/tools/webbrowser/browserCdp.ts +255 -0
  270. package/src/tools/webbrowser/browserConditions.js +24 -0
  271. package/src/tools/webbrowser/browserConditions.ts +45 -0
  272. package/src/tools/webbrowser/browserContent.js +281 -0
  273. package/src/tools/webbrowser/browserContent.ts +438 -0
  274. package/src/tools/webbrowser/browserControl.js +131 -34
  275. package/src/tools/webbrowser/browserControl.ts +210 -56
  276. package/src/tools/webbrowser/browserDaemon.js +22 -14
  277. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  278. package/src/tools/webbrowser/browserFrames.js +47 -0
  279. package/src/tools/webbrowser/browserFrames.ts +54 -0
  280. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  281. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  282. package/src/tools/webbrowser/browserLocator.js +140 -0
  283. package/src/tools/webbrowser/browserLocator.ts +194 -0
  284. package/src/tools/webbrowser/browserOutcome.js +10 -17
  285. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  286. package/src/tools/webbrowser/browserRecovery.js +58 -0
  287. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  288. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  289. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  290. package/src/tools/webbrowser/browserService.js +28 -2
  291. package/src/tools/webbrowser/browserService.ts +49 -9
  292. package/src/tools/webbrowser/browserStream.js +132 -33
  293. package/src/tools/webbrowser/browserStream.ts +224 -50
  294. package/src/tools/webbrowser/browserTabs.js +48 -15
  295. package/src/tools/webbrowser/browserTabs.ts +178 -96
  296. package/src/tools/webbrowser/webbrowser.js +679 -144
  297. package/src/tools/webbrowser/webbrowser.ts +975 -236
  298. package/src/tools/webfetch/preapproved.js +0 -2
  299. package/src/tools/webfetch/preapproved.ts +0 -2
  300. package/src/tools/websearch/websearch.js +129 -8
  301. package/src/tools/websearch/websearch.ts +138 -6
  302. package/src/types.ts +440 -38
  303. package/src/ui/App.js +377 -58
  304. package/src/ui/App.tsx +576 -111
  305. package/src/ui/LiveArea.js +14 -8
  306. package/src/ui/LiveArea.tsx +14 -10
  307. package/src/ui/MessageList.js +63 -20
  308. package/src/ui/MessageList.tsx +101 -16
  309. package/src/ui/ModelPicker.js +7 -4
  310. package/src/ui/ModelPicker.tsx +55 -8
  311. package/src/ui/hooks/useChat.js +152 -163
  312. package/src/ui/hooks/useChat.ts +396 -334
  313. package/src/ui/loginCommand.js +56 -0
  314. package/src/ui/loginCommand.ts +63 -0
  315. package/src/ui/toolProgress.js +41 -0
  316. package/src/ui/toolProgress.ts +44 -0
  317. package/src/web/browserBridge.js +20 -2
  318. package/src/web/browserBridge.ts +20 -2
  319. package/src/web/protocol.ts +1897 -194
  320. package/workflows/adaptive-research/workflow.mjs +86 -0
  321. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  322. package/workflows/e2e-llm/workflow.mjs +52 -0
  323. package/workflows/e2e-smoke/workflow.mjs +59 -0
  324. package/workflows/e2e-vision-schema/README.md +48 -0
  325. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  326. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  327. package/workflows/file-census/README.md +14 -0
  328. package/workflows/file-census/scripts/.gitkeep +2 -0
  329. package/workflows/file-census/workflow.mjs +110 -0
  330. package/workflows/goal/README.md +166 -0
  331. package/workflows/goal/prompts/audit.md +57 -0
  332. package/workflows/goal/prompts/contract.md +56 -0
  333. package/workflows/goal/prompts/iterate.md +37 -0
  334. package/workflows/goal/workflow.mjs +501 -0
  335. package/workflows/illustrate/README.md +90 -0
  336. package/workflows/illustrate/assets/engines.json +20 -0
  337. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  338. package/workflows/illustrate/assets/extract-bing.js +33 -0
  339. package/workflows/illustrate/assets/extract-google.js +31 -0
  340. package/workflows/illustrate/workflow.mjs +301 -0
  341. package/workflows/plan/workflow.mjs +389 -0
  342. package/workflows/question-crops/README.md +35 -0
  343. package/workflows/question-crops/prompts/inventory.md +23 -0
  344. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  345. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  346. package/workflows/question-crops/workflow.mjs +172 -0
  347. package/workflows/ralph-loop/workflow.mjs +162 -0
  348. package/workflows/research/README.md +74 -0
  349. package/workflows/research/prompts/s0-goal.md +43 -0
  350. package/workflows/research/prompts/s1-early-exit.md +22 -0
  351. package/workflows/research/prompts/s1-map.md +58 -0
  352. package/workflows/research/prompts/s2-options.md +53 -0
  353. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  354. package/workflows/research/prompts/s4-verify.md +74 -0
  355. package/workflows/research/prompts/s5-synthesis.md +81 -0
  356. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  357. package/workflows/research/prompts/s6-package.md +32 -0
  358. package/workflows/research/scripts/build-report.mjs +98 -0
  359. package/workflows/research/workflow.mjs +288 -0
  360. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  361. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  362. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  363. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  364. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  365. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  366. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  367. package/workflows/wrong-question-docx/README.md +55 -0
  368. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  369. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  370. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  371. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  372. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  373. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  374. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  375. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  376. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  377. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  378. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  379. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  380. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  381. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  382. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  383. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  384. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  385. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  386. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  387. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  388. package/plugins/workflow-runner/src/expressions.js +0 -639
  389. package/plugins/workflow-runner/src/expressions.ts +0 -640
  390. package/plugins/workflow-runner/src/loader.js +0 -573
  391. package/plugins/workflow-runner/src/loader.ts +0 -721
  392. package/plugins/workflow-runner/src/multimodal.js +0 -38
  393. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  394. package/plugins/workflow-runner/src/recovery.js +0 -126
  395. package/plugins/workflow-runner/src/recovery.ts +0 -156
  396. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  397. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  398. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -42
  399. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -52
  400. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  401. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  402. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  403. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  404. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  405. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  406. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  407. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  408. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -227
  409. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -274
  410. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -61
  411. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -72
  412. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  413. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  414. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  415. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  416. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  417. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  418. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -167
  419. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  420. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  421. package/plugins/workflow-runner/test/e2eNewLayout.test.ts +0 -314
  422. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -234
  423. package/plugins/workflow-runner/test/expressions.test.ts +0 -439
  424. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  425. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  426. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  427. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  428. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  429. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  430. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  431. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  432. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  433. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  434. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -106
  435. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  436. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  437. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -221
  438. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  439. package/plugins/workflow-runner/test/research.load.test.ts +0 -328
  440. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  441. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -436
  442. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  443. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -198
  444. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  445. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -354
  446. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -324
  447. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  448. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  449. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  450. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  451. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  452. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  453. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  454. package/src/advisorState.js +0 -22
  455. package/src/cli/configWizard.js +0 -293
  456. package/src/tools/toolProgressBridge.js +0 -48
  457. package/workflows/adaptive-research.yaml +0 -50
  458. package/workflows/book-review-short.yaml +0 -125
  459. package/workflows/e2e-write-greet.yaml +0 -31
  460. package/workflows/illustrate.yaml +0 -227
  461. package/workflows/plan.yaml +0 -470
  462. package/workflows/ppt.yaml +0 -1670
  463. package/workflows/ralph-loop.yaml +0 -201
  464. package/workflows/research.yaml +0 -873
  465. package/workflows/schema.json +0 -176
  466. package/workflows/webppt.yaml +0 -496
  467. package/workflows/wechat-reading-shots.yaml +0 -315
  468. package/workflows/word.yaml +0 -720
  469. package/workflows/youtube-shorts.yaml +0 -202
  470. /package/workflows/{research.md → research/DESIGN.md} +0 -0
@@ -1,640 +0,0 @@
1
- /**
2
- * ============================================================
3
- * plugins/workflow-runner/src/expressions.ts —— workflow 表达式引擎(mini)
4
- * ------------------------------------------------------------
5
- * WHY 不直接 eval / Function?
6
- * Workflow 来源不可信(用户编辑器生成 / 别人贡献的 yaml),允许任意
7
- * JS 执行 = 任意远程代码执行。所以我们手写一个**只支持必要语法**的
8
- * 递归下降解析器:常量、变量引用、点路径、字符串/数字字面量、数组、
9
- * 比较、布尔运算、白名单内置函数。
10
- *
11
- * 支持的语法:
12
- * expr := or
13
- * or := and ( '||' and )*
14
- * and := not ( '&&' not )*
15
- * not := '!' not | comparison
16
- * comparison := additive ( ('==' | '!=' | '>=' | '<=' | '>' | '<') additive )?
17
- * additive := primary ( '+' primary )* // number 相加 / 否则 stringify 后拼接
18
- * primary := number | string | array | call | path | interp | '(' expr ')'
19
- * array := '[' expr (',' expr)* ']'
20
- * call := IDENT '(' expr (',' expr)* ')' // 白名单
21
- * path := IDENT ('.' IDENT)* // 变量 + 点路径
22
- * interp := '${' EXPR '}' // 取 EXPR 的"值"(任意类型,不字符串化)
23
- * string := "…" | '…' // 内嵌 ${EXPR} 走 template-literal 插值
24
- *
25
- * 白名单函数:fileExists / readJson / parseJson / asArray / length / lower / upper / includes /
26
- * startsWith / endsWith / match / split / extract / join / json
27
- *
28
- * ★ P2 根治:表达式上下文(when/condition/over/assert/vote.input)直接 evalExpr,
29
- * 不再"先 interpolate 再 eval"(旧双求值的 footgun 源头)。四种写法都 OK:
30
- * 裸 ${x} == 'y' → ${x} 取值后比较(修掉旧 footgun:旧实现把 ${x} 替成裸字面量被当变量名)
31
- * "a/${x}/b" → 字符串字面量内插值拼路径(向后兼容旧 fileExists("…${x}…") 写法)
32
- * "a/" + x + "/b" → + 拼接(新增)
33
- * x == 'y' → 裸变量名(向后兼容)
34
- * 安全性也更好:变量的字符串内容不再被当作表达式二次执行(旧实现若插值结果落在引号外会被 eval)。
35
- *
36
- * 插值函数 interpolate(${EXPR}) 仍用于**模板上下文**(prompt / args / file_path)——
37
- * 把 EXPR 求值后 toString 替换;对象/数组用 JSON.stringify。interpolateDeep 递归处理对象/数组/字符串。
38
- * ============================================================
39
- */
40
-
41
- import { existsSync, readFileSync } from 'node:fs';
42
- import { join, isAbsolute } from 'node:path';
43
-
44
- import { getWorkingDir } from '../../../src/plugin-sdk.js';
45
- import { DANGEROUS_KEYS, isAbsWithinCwd, type VarStack } from './types.js';
46
-
47
- // ---------------- Tokenizer ----------------
48
-
49
- type Token =
50
- | { kind: 'num'; value: number }
51
- | { kind: 'str'; value: string }
52
- | { kind: 'ident'; value: string }
53
- | { kind: 'interp'; value: string }
54
- | { kind: 'punct'; value: string };
55
-
56
- const PUNCT2 = new Set(['==', '!=', '>=', '<=', '&&', '||']);
57
- const PUNCT1 = new Set(['(', ')', '[', ']', ',', '.', '!', '>', '<', '+', '?', ':']);
58
-
59
- function tokenize(src: string): Token[] {
60
- const out: Token[] = [];
61
- let i = 0;
62
- while (i < src.length) {
63
- const c = src[i];
64
- if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
65
- i++;
66
- continue;
67
- }
68
- // 独立 ${...} 取值 token。注意:字符串字面量内部的 ${} 不会到这里——
69
- // 会被下面的字符串分支整体读进 str token,由 parsePrimary 在 eval 时插值。
70
- if (c === '$' && src[i + 1] === '{') {
71
- let depth = 1;
72
- let j = i + 2;
73
- while (j < src.length && depth > 0) {
74
- if (src[j] === '{') depth++;
75
- else if (src[j] === '}') depth--;
76
- if (depth > 0) j++;
77
- }
78
- if (depth !== 0) throw new Error(`未闭合的 \${ 在表达式 "${src}"`);
79
- out.push({ kind: 'interp', value: src.slice(i + 2, j) });
80
- i = j + 1;
81
- continue;
82
- }
83
- // 字符串
84
- if (c === '"' || c === "'") {
85
- const quote = c;
86
- let s = '';
87
- i++;
88
- while (i < src.length && src[i] !== quote) {
89
- if (src[i] === '\\' && i + 1 < src.length) {
90
- const nx = src[i + 1];
91
- s += nx === 'n' ? '\n' : nx === 't' ? '\t' : nx;
92
- i += 2;
93
- } else {
94
- s += src[i++];
95
- }
96
- }
97
- if (src[i] !== quote) throw new Error(`未闭合的字符串:${src}`);
98
- i++;
99
- out.push({ kind: 'str', value: s });
100
- continue;
101
- }
102
- // 数字
103
- if ((c >= '0' && c <= '9') || (c === '-' && src[i + 1] >= '0' && src[i + 1] <= '9')) {
104
- let j = i;
105
- if (src[j] === '-') j++;
106
- while (j < src.length && ((src[j] >= '0' && src[j] <= '9') || src[j] === '.')) j++;
107
- out.push({ kind: 'num', value: parseFloat(src.slice(i, j)) });
108
- i = j;
109
- continue;
110
- }
111
- // 双字符标点
112
- const two = src.slice(i, i + 2);
113
- if (PUNCT2.has(two)) {
114
- out.push({ kind: 'punct', value: two });
115
- i += 2;
116
- continue;
117
- }
118
- // 单字符标点
119
- if (PUNCT1.has(c)) {
120
- out.push({ kind: 'punct', value: c });
121
- i++;
122
- continue;
123
- }
124
- // 标识符
125
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c === '_') {
126
- let j = i;
127
- while (
128
- j < src.length &&
129
- ((src[j] >= 'a' && src[j] <= 'z') ||
130
- (src[j] >= 'A' && src[j] <= 'Z') ||
131
- (src[j] >= '0' && src[j] <= '9') ||
132
- src[j] === '_')
133
- ) {
134
- j++;
135
- }
136
- out.push({ kind: 'ident', value: src.slice(i, j) });
137
- i = j;
138
- continue;
139
- }
140
- throw new Error(`非法字符 '${c}' 在表达式 "${src}"`);
141
- }
142
- return out;
143
- }
144
-
145
- // ---------------- Parser → AST → eval(一次性递归下降,零 AST 对象) ----------------
146
-
147
- interface Cursor {
148
- toks: Token[];
149
- pos: number;
150
- }
151
-
152
- function peek(c: Cursor): Token | null {
153
- return c.pos < c.toks.length ? c.toks[c.pos] : null;
154
- }
155
- function eat(c: Cursor): Token {
156
- return c.toks[c.pos++];
157
- }
158
- function isPunct(c: Cursor, v: string): boolean {
159
- const t = peek(c);
160
- return !!t && t.kind === 'punct' && t.value === v;
161
- }
162
-
163
- /**
164
- * 读磁盘的内置函数(fileExists / readJson)的锚点。
165
- * 必须和写入侧同源:进了 session worktree 后产物落在 worktree 里,
166
- * 这里若仍锚原 workspace,done_when 会在空目录里找文件而恒判 false。
167
- */
168
- function cwdOf(vars: VarStack): string {
169
- return vars.resolveCwd?.() ?? getWorkingDir();
170
- }
171
-
172
- const BUILTINS: Record<string, (args: unknown[], cwd: string) => unknown> = {
173
- fileExists(args, cwd) {
174
- const p = String(args[0] ?? '');
175
- if (!p) return false;
176
- const abs = isAbsolute(p) ? p : join(cwd, p);
177
- // 安全守卫:越出 cwd 的路径视为"不存在"(fail-closed,与 readJson 同策略)。
178
- // 不可信 YAML(模板库/他人分享)可能带 fileExists('/etc/passwd') 做信息刺探,
179
- // 这里与 cleanupDeclaredArtifacts 保持一致:只允许操作 cwd 下的文件。
180
- if (!isAbsWithinCwd(cwd, abs)) return false;
181
- return existsSync(abs);
182
- },
183
- length(args) {
184
- const v = args[0];
185
- if (typeof v === 'string' || Array.isArray(v)) return v.length;
186
- if (v && typeof v === 'object') return Object.keys(v).length;
187
- return 0;
188
- },
189
- // 把可选 JSON 数组标准化为 for-each loop 可消费的数组。用于
190
- // over: "${asArray(readJson('manifest.json', 'images'))}" 这类场景:
191
- // 缺文件/缺字段/null 表示 0 轮;但对象/字符串等结构错误要响亮失败,
192
- // 避免把坏 manifest 静默吞掉。
193
- asArray(args) {
194
- const v = args[0];
195
- if (Array.isArray(v)) return v;
196
- if (v === null || v === undefined) return [];
197
- throw new Error(`asArray 期望数组/null/undefined,得到 ${Array.isArray(v) ? 'array' : typeof v}`);
198
- },
199
- lower(args) {
200
- return String(args[0] ?? '').toLowerCase();
201
- },
202
- upper(args) {
203
- return String(args[0] ?? '').toUpperCase();
204
- },
205
- includes(args) {
206
- const haystack = String(args[0] ?? '');
207
- const needle = String(args[1] ?? '');
208
- return haystack.includes(needle);
209
- },
210
- startsWith(args) {
211
- const s = String(args[0] ?? '');
212
- const prefix = String(args[1] ?? '');
213
- return s.startsWith(prefix);
214
- },
215
- endsWith(args) {
216
- const s = String(args[0] ?? '');
217
- const suffix = String(args[1] ?? '');
218
- return s.endsWith(suffix);
219
- },
220
- match(args) {
221
- const s = String(args[0] ?? '');
222
- const pattern = String(args[1] ?? '');
223
- try {
224
- const re = new RegExp(pattern);
225
- return re.test(s);
226
- } catch {
227
- return false; // 非法正则 → 不匹配
228
- }
229
- },
230
- split(args) {
231
- const s = String(args[0] ?? '');
232
- const delim = args[1] !== undefined ? String(args[1]) : ',';
233
- return s.split(delim);
234
- },
235
- // 全局正则提取:返回匹配数组。有捕获组取 group 1(典型:提取 <tag>(.*?)</tag> 内容),
236
- // 否则取全匹配。flags 固定 gs(跨行 . 匹配);非法正则返回 [](与 match 同哲学:不抛)。
237
- // 动机:从 agent/llm 输出里确定性抽取协议标签(如 <learning>…</learning>),
238
- // 替代「再开一个 LLM 步去提取」的幻觉/改写风险。
239
- extract(args) {
240
- const s = String(args[0] ?? '');
241
- const pattern = String(args[1] ?? '');
242
- if (!pattern) return [];
243
- try {
244
- const re = new RegExp(pattern, 'gs');
245
- const out: string[] = [];
246
- for (const m of s.matchAll(re)) {
247
- out.push(m[1] !== undefined ? m[1] : m[0]);
248
- }
249
- return out;
250
- } catch {
251
- return [];
252
- }
253
- },
254
- // 数组拼接成字符串;非数组按 stringify 兜底。与 extract 配对使用。
255
- join(args) {
256
- const v = args[0];
257
- const sep = args[1] !== undefined ? String(args[1]) : ',';
258
- if (Array.isArray(v)) return v.map((x) => stringify(x)).join(sep);
259
- return stringify(v);
260
- },
261
- // JSON.stringify 安全嵌入:值含引号/换行时拼 JSON 文件不产坏文档
262
- // (模板里写 "k": ${json(v)},外层引号由 json() 自己生成)。
263
- json(args) {
264
- try {
265
- return JSON.stringify(args[0] ?? null);
266
- } catch {
267
- return 'null';
268
- }
269
- },
270
- // 读磁盘 JSON 文件并取值:path 相对当前有效 cwd(绝对路径先经 cwd 安全校验,
271
- // 越界直接 null,与 fileExists 同 fail-closed 策略);给了 key 则返回顶层
272
- // obj[key],否则返回整值。缺文件 / 坏 JSON / 键不存在 / 越界 → null
273
- //(与 match/extract 同「不抛」哲学)。动机:让确定性脚本产出的 manifest
274
- //(如 source_assets.json)直接驱动工作流 for-each / branch —— 填补「磁盘
275
- // 结构化数据 → 控制流」的空白(与 fileExists 平级的基础能力)。
276
- readJson(args, cwd) {
277
- const p = String(args[0] ?? '');
278
- if (!p) return null;
279
- const abs = isAbsolute(p) ? p : join(cwd, p);
280
- if (!isAbsWithinCwd(cwd, abs)) return null; // 越界 → null(不抛,fail-closed)
281
- try {
282
- // 剥前导 BOM:某些工具/重定向写出的 JSON 带 BOM,JSON.parse 会因此抛错。
283
- let raw = readFileSync(abs, 'utf8');
284
- if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
285
- const val = JSON.parse(raw);
286
- if (args.length >= 2) {
287
- const key = String(args[1] ?? '');
288
- return val != null && typeof val === 'object' ? (val[key] ?? null) : null;
289
- }
290
- return val;
291
- } catch {
292
- return null;
293
- }
294
- },
295
- // 解析 JSON 字符串成值(与 readJson 平级,但源是**字符串**而非文件):剥前导 BOM、
296
- // trim、JSON.parse;非字符串 / 空 / 坏 JSON → null(同 match/extract/readJson「不抛」哲学)。
297
- // 动机:把 WebBrowser eval / agent / 工具 capture 到的 JSON 文本变成数组/对象,
298
- // 直接驱动 loop over / branch —— 填补「字符串结构化数据 → 控制流」的空白
299
- //(配 asArray 用:over: "${asArray(parseJson(urls_json))}")。
300
- parseJson(args) {
301
- const v = args[0];
302
- if (typeof v !== 'string') return null;
303
- let raw = v.charCodeAt(0) === 0xfeff ? v.slice(1) : v;
304
- raw = raw.trim();
305
- if (!raw) return null;
306
- try {
307
- return JSON.parse(raw);
308
- } catch {
309
- return null;
310
- }
311
- },
312
- };
313
-
314
- function parseOr(c: Cursor, vars: VarStack): unknown {
315
- let left = parseAnd(c, vars);
316
- while (isPunct(c, '||')) {
317
- eat(c);
318
- const right = parseAnd(c, vars);
319
- left = truthy(left) || truthy(right);
320
- }
321
- return left;
322
- }
323
-
324
- // 三元表达式:cond ? true_expr : false_expr
325
- // 优先级低于 ||(即先算完 || 再判断 ? :)
326
- // 右结合:a ? b ? c : d : e 等价于 a ? (b ? c : d) : e
327
- function parseTernary(c: Cursor, vars: VarStack): unknown {
328
- const cond = parseOr(c, vars);
329
- if (!isPunct(c, '?')) return cond;
330
- eat(c); // 吃掉 '?'
331
- const consequent = parseTernary(c, vars); // 右结合:true 分支可以是嵌套三元
332
- if (!isPunct(c, ':')) throw new Error("三元表达式缺少 ':'");
333
- eat(c); // 吃掉 ':'
334
- const alternate = parseTernary(c, vars); // 右结合:false 分支也可以是嵌套三元
335
- return truthy(cond) ? consequent : alternate;
336
- }
337
- function parseAnd(c: Cursor, vars: VarStack): unknown {
338
- let left = parseNot(c, vars);
339
- while (isPunct(c, '&&')) {
340
- eat(c);
341
- const right = parseNot(c, vars);
342
- left = truthy(left) && truthy(right);
343
- }
344
- return left;
345
- }
346
- function parseNot(c: Cursor, vars: VarStack): unknown {
347
- if (isPunct(c, '!')) {
348
- eat(c);
349
- return !truthy(parseNot(c, vars));
350
- }
351
- return parseComparison(c, vars);
352
- }
353
- function parseComparison(c: Cursor, vars: VarStack): unknown {
354
- const left = parseAdditive(c, vars);
355
- const t = peek(c);
356
- if (t && t.kind === 'punct' && ['==', '!=', '>=', '<=', '>', '<'].includes(t.value)) {
357
- eat(c);
358
- const right = parseAdditive(c, vars);
359
- switch (t.value) {
360
- case '==':
361
- // eslint-disable-next-line eqeqeq
362
- return left == right;
363
- case '!=':
364
- // eslint-disable-next-line eqeqeq
365
- return left != right;
366
- case '>':
367
- return (left as number) > (right as number);
368
- case '<':
369
- return (left as number) < (right as number);
370
- case '>=':
371
- return (left as number) >= (right as number);
372
- case '<=':
373
- return (left as number) <= (right as number);
374
- }
375
- }
376
- return left;
377
- }
378
- // 加法/拼接层:number + number → 相加;否则两边 stringify 后字符串拼接。
379
- // 表达式语言原本无拼接符,导致拼路径只能"先插值再 eval"(footgun 源头);
380
- // 有了 + 之后可写 fileExists("videos/" + inputs.book + "/x") 这种纯表达式。
381
- function parseAdditive(c: Cursor, vars: VarStack): unknown {
382
- let left = parsePrimary(c, vars);
383
- while (isPunct(c, '+')) {
384
- eat(c);
385
- const right = parsePrimary(c, vars);
386
- left = addValues(left, right);
387
- }
388
- return left;
389
- }
390
-
391
- function addValues(l: unknown, r: unknown): unknown {
392
- if (typeof l === 'number' && typeof r === 'number') return l + r;
393
- return stringify(l) + stringify(r);
394
- }
395
- function parsePrimary(c: Cursor, vars: VarStack): unknown {
396
- const t = peek(c);
397
- if (!t) throw new Error('表达式意外结束');
398
-
399
- if (t.kind === 'punct' && t.value === '(') {
400
- eat(c);
401
- // 括号内是完整表达式(含三元)——曾只递归 parseOr,(a ? b : c) 报「缺少 ')'」
402
- const v = parseTernary(c, vars);
403
- if (!isPunct(c, ')')) throw new Error("缺少 ')'");
404
- eat(c);
405
- return v;
406
- }
407
- if (t.kind === 'punct' && t.value === '[') {
408
- eat(c);
409
- const arr: unknown[] = [];
410
- if (!isPunct(c, ']')) {
411
- arr.push(parseTernary(c, vars));
412
- while (isPunct(c, ',')) {
413
- eat(c);
414
- arr.push(parseTernary(c, vars));
415
- }
416
- }
417
- if (!isPunct(c, ']')) throw new Error("缺少 ']'");
418
- eat(c);
419
- return arr;
420
- }
421
- if (t.kind === 'num') {
422
- eat(c);
423
- return t.value;
424
- }
425
- if (t.kind === 'str') {
426
- eat(c);
427
- // 字符串字面量支持内嵌 ${...} 插值(template-literal 语义):
428
- // fileExists("videos/${inputs.book}/x") 不必预插值也能工作。无 ${} 时原样返回。
429
- return interpolate(t.value, vars);
430
- }
431
- if (t.kind === 'interp') {
432
- eat(c);
433
- // 独立 ${EXPR}:求值为 EXPR 的"值"(任意类型,不字符串化)。
434
- // 修掉"裸 ${x} == 'y' 被当成变量名 x"的 footgun。
435
- return evalExpr(t.value, vars);
436
- }
437
- if (t.kind === 'ident') {
438
- eat(c);
439
- // 函数调用?
440
- if (isPunct(c, '(')) {
441
- eat(c);
442
- const argv: unknown[] = [];
443
- if (!isPunct(c, ')')) {
444
- argv.push(parseTernary(c, vars));
445
- while (isPunct(c, ',')) {
446
- eat(c);
447
- argv.push(parseTernary(c, vars));
448
- }
449
- }
450
- if (!isPunct(c, ')')) throw new Error("缺少 ')'");
451
- eat(c);
452
- const fn = BUILTINS[t.value];
453
- if (!fn) throw new Error(`未知函数 "${t.value}"。白名单:${Object.keys(BUILTINS).join(', ')}`);
454
- return fn(argv, cwdOf(vars));
455
- }
456
- // 字面常量
457
- if (t.value === 'true') return true;
458
- if (t.value === 'false') return false;
459
- if (t.value === 'null') return null;
460
- // 变量 + 点路径
461
- let cur: unknown = vars.get(t.value);
462
- while (isPunct(c, '.')) {
463
- eat(c);
464
- const next = eat(c);
465
- if (next.kind !== 'ident') throw new Error('点号后必须是标识符');
466
- // S1:禁止经点路径访问原型链(__proto__ / constructor / prototype),防原型污染 / 函数对象逃逸。
467
- if (
468
- next.value === '__proto__' ||
469
- next.value === 'constructor' ||
470
- next.value === 'prototype'
471
- ) {
472
- cur = undefined;
473
- } else {
474
- cur =
475
- cur != null && typeof cur === 'object'
476
- ? (cur as Record<string, unknown>)[next.value]
477
- : undefined;
478
- }
479
- }
480
- return cur;
481
- }
482
- throw new Error(`无法解析 token: ${JSON.stringify(t)}`);
483
- }
484
-
485
- function truthy(v: unknown): boolean {
486
- if (v === null || v === undefined) return false;
487
- if (typeof v === 'boolean') return v;
488
- if (typeof v === 'number') return v !== 0 && !Number.isNaN(v);
489
- if (typeof v === 'string') return v.length > 0;
490
- if (Array.isArray(v)) return v.length > 0;
491
- if (typeof v === 'object') return Object.keys(v).length > 0;
492
- return true;
493
- }
494
-
495
- // ---------------- 对外 API ----------------
496
-
497
- /** 计算表达式(裸的,不带 ${} 包裹)。失败抛错。 */
498
- export function evalExpr(expr: string, vars: VarStack): unknown {
499
- const toks = tokenize(expr);
500
- if (toks.length === 0) return undefined;
501
- const cursor: Cursor = { toks, pos: 0 };
502
- const v = parseTernary(cursor, vars);
503
- if (cursor.pos !== toks.length) {
504
- throw new Error(`表达式多余 token: "${expr}"`);
505
- }
506
- return v;
507
- }
508
-
509
- /**
510
- * interpolate / interpolateDeep 的可选项。
511
- * onMissing:某个 ${EXPR} 求值为 undefined(变量不存在/点路径落空)时回调。
512
- * 设计动机:未定义变量静默插成空串是 ralph-loop 裸 ${goal} 渲染为空的病根——
513
- * 引擎不能猜作者意图(undefined 可能是合法的可选值),但**必须可见化**;
514
- * 执行器收集后 yield 一条 warning(含表达式原文),作者一眼定位拼写/命名空间错误。
515
- */
516
- export interface InterpolateOptions {
517
- onMissing?: (expr: string) => void;
518
- }
519
-
520
- /**
521
- * 字符串插值:把 "${EXPR}" 替换成 EXPR 求值结果。
522
- * 多个 ${} 串联也支持。括号配对识别 —— "${ ${nested} }" 会被当成一个外层
523
- * EXPR(内部 ${nested} 文本原样,但表达式里不允许 ${} 语法,所以会报错)。
524
- *
525
- * 转义:`\$` → 字面 $,跳过插值。
526
- */
527
- export function interpolate(
528
- template: string,
529
- vars: VarStack,
530
- opts?: InterpolateOptions,
531
- ): string {
532
- let out = '';
533
- let i = 0;
534
- while (i < template.length) {
535
- const c = template[i];
536
- if (c === '\\' && template[i + 1] === '$') {
537
- out += '$';
538
- i += 2;
539
- continue;
540
- }
541
- if (c === '$' && template[i + 1] === '{') {
542
- // 找匹配的 '}'
543
- let depth = 1;
544
- let j = i + 2;
545
- while (j < template.length && depth > 0) {
546
- if (template[j] === '{') depth++;
547
- else if (template[j] === '}') depth--;
548
- if (depth > 0) j++;
549
- }
550
- if (depth !== 0) {
551
- // 不匹配;原样输出
552
- out += template.slice(i);
553
- return out;
554
- }
555
- const expr = template.slice(i + 2, j);
556
- const v = evalExpr(expr, vars);
557
- if (v === undefined) opts?.onMissing?.(expr);
558
- out += stringify(v);
559
- i = j + 1;
560
- continue;
561
- }
562
- out += c;
563
- i++;
564
- }
565
- return out;
566
- }
567
-
568
- /**
569
- * 类型保留插值:模板整串恰好是单一 "${EXPR}" 时直接返回 EXPR 的值
570
- * (布尔/数字/数组原样,不字符串化);多片段模板退回普通 interpolate。
571
- * 非字符串值原样透传(与 interpolateDeep 对非字符串的行为一致)。
572
- *
573
- * 动机:set 步写 `flag: "${qualified}"` 曾被字符串化成 "true",逼出下游
574
- * 满篇 `== true || == 'true'` 防御——单表达式赋值就该保住类型。
575
- */
576
- export function interpolateTyped(
577
- value: unknown,
578
- vars: VarStack,
579
- opts?: InterpolateOptions,
580
- ): unknown {
581
- if (typeof value !== 'string') return value;
582
- const expr = singleExprOf(value);
583
- if (expr === null) return interpolate(value, vars, opts);
584
- const v = evalExpr(expr, vars);
585
- if (v === undefined) opts?.onMissing?.(expr);
586
- return v;
587
- }
588
-
589
- /** 整串恰好是一个 "${EXPR}"(括号配对到串尾)时返回内层 EXPR,否则 null。 */
590
- function singleExprOf(template: string): string | null {
591
- if (!template.startsWith('${') || !template.endsWith('}')) return null;
592
- let depth = 1;
593
- for (let j = 2; j < template.length; j++) {
594
- if (template[j] === '{') depth++;
595
- else if (template[j] === '}') {
596
- depth--;
597
- // 提前归零 = 中途闭合(如 "${a}-${b}"),不是单一表达式
598
- if (depth === 0) return j === template.length - 1 ? template.slice(2, j) : null;
599
- }
600
- }
601
- return null;
602
- }
603
-
604
- function stringify(v: unknown): string {
605
- if (v === null || v === undefined) return '';
606
- if (typeof v === 'string') return v;
607
- if (typeof v === 'number' || typeof v === 'boolean') return String(v);
608
- try {
609
- return JSON.stringify(v);
610
- } catch {
611
- return String(v);
612
- }
613
- }
614
-
615
- /** 递归对对象/数组/字符串做插值;其它原样返回。 */
616
- export function interpolateDeep<T>(
617
- value: T,
618
- vars: VarStack,
619
- opts?: InterpolateOptions,
620
- ): T {
621
- if (typeof value === 'string') {
622
- return interpolate(value, vars, opts) as unknown as T;
623
- }
624
- if (Array.isArray(value)) {
625
- return value.map((v) => interpolateDeep(v, vars, opts)) as unknown as T;
626
- }
627
- if (value && typeof value === 'object') {
628
- const out: Record<string, unknown> = {};
629
- for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
630
- // 注意:key 也插值(loop 里 `hook_${idx}` 这种),但只对字符串 key 有意义;
631
- // 插值后的 key 若命中危险键(__proto__/constructor/prototype)→ 直接跳过不写,
632
- // 防止经动态 key 注入原型链属性(与 VarStack 防线保持一致)。
633
- const newKey = interpolate(k, vars, opts);
634
- if (DANGEROUS_KEYS.has(newKey)) continue;
635
- out[newKey] = interpolateDeep(v, vars, opts);
636
- }
637
- return out as unknown as T;
638
- }
639
- return value;
640
- }