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
@@ -0,0 +1,857 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdir, mkdtemp, readFile, stat, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { basename, dirname, join, resolve } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { DOMParser } from '@xmldom/xmldom';
7
+ import {
8
+ BorderStyle,
9
+ Document,
10
+ ImageRun,
11
+ LineRuleType,
12
+ MathFraction,
13
+ MathRadical,
14
+ MathRun,
15
+ MathSubScript,
16
+ MathSubSuperScript,
17
+ MathSuperScript,
18
+ Math as OfficeMath,
19
+ Packer,
20
+ Paragraph,
21
+ TextRun,
22
+ } from 'docx';
23
+ import { strFromU8, unzipSync } from 'fflate';
24
+ import sharp from 'sharp';
25
+
26
+ export function parseSelection(text) {
27
+ const s = String(text ?? '').trim();
28
+ if (/^(全部|全抄|all)$/i.test(s)) return { all: true, items: [] };
29
+ if (!s) return { all: false, items: [] };
30
+ const items = [];
31
+ for (const token of s
32
+ .replace(/[,、;;]/g, ',')
33
+ .split(',')
34
+ .map((s) => s.trim())
35
+ .filter(Boolean)) {
36
+ const m = token.match(/^(?:(.+?)[::])?(\d+)(?:\s*[-–~至到]\s*(\d+))?$/);
37
+ if (!m)
38
+ throw new Error(`无法理解题号:${token},请使用5,9,13-16或章节:题号`);
39
+ const start = Number(m[2]);
40
+ const end = Number(m[3] ?? m[2]);
41
+ if (start < 1 || end < start || end - start > 500)
42
+ throw new Error(`非法题号范围:${token}`);
43
+ for (let n = start; n <= end; n++) {
44
+ const item = { section: m[1]?.trim() ?? '', number: String(n) };
45
+ if (
46
+ !items.some(
47
+ (i) => i.section === item.section && i.number === item.number,
48
+ )
49
+ )
50
+ items.push(item);
51
+ }
52
+ }
53
+ return { all: false, items };
54
+ }
55
+
56
+ export function selectQuestions(inventory, selection) {
57
+ const available = inventory.questions;
58
+ const wanted = selection.all
59
+ ? available.map((q) => ({ number: q.number, section: q.section, id: q.id }))
60
+ : selection.items;
61
+ return wanted.map((item, i) => {
62
+ const matches = available.filter((q) =>
63
+ item.id
64
+ ? q.id === item.id
65
+ : q.number === item.number &&
66
+ (!item.section || q.section === item.section),
67
+ );
68
+ if (matches.length > 1)
69
+ throw new Error(`第${item.number}题有多个章节匹配,请指定章节:题号`);
70
+ return {
71
+ ...(matches[0] ?? {}),
72
+ requested: item,
73
+ sourceId: matches[0]?.id ?? '',
74
+ id: `item-${String(i + 1).padStart(3, '0')}`,
75
+ number: item.number,
76
+ section: matches[0]?.section ?? item.section,
77
+ status: matches[0]?.status === 'exported' ? 'pending' : 'missing',
78
+ reason: matches[0]
79
+ ? matches[0].status === 'exported'
80
+ ? ''
81
+ : '题目切图未完成'
82
+ : '指定题目未找到',
83
+ };
84
+ });
85
+ }
86
+
87
+ async function save(path, data) {
88
+ await mkdir(dirname(path), { recursive: true });
89
+ await writeFile(path, data);
90
+ }
91
+ const geometry = (width, height) => {
92
+ const s = Math.min(1, 1920 / Math.max(width, height));
93
+ const w = Math.max(1, Math.round(width * s));
94
+ const h = Math.max(1, Math.round(height * s));
95
+ return {
96
+ sourceWidth: width,
97
+ sourceHeight: height,
98
+ width: w,
99
+ height: h,
100
+ scaleX: w / width,
101
+ scaleY: h / height,
102
+ };
103
+ };
104
+ async function preview(input, output) {
105
+ const m = await sharp(input).metadata();
106
+ const g = geometry(m.width, m.height);
107
+ await save(
108
+ output,
109
+ await sharp(input)
110
+ .resize({
111
+ width: g.width,
112
+ height: g.height,
113
+ kernel: 'lanczos3',
114
+ fastShrinkOnLoad: false,
115
+ withoutEnlargement: true,
116
+ })
117
+ .png()
118
+ .toBuffer(),
119
+ );
120
+ return { ...g, input, path: output };
121
+ }
122
+ export function validBox(b) {
123
+ return (
124
+ b &&
125
+ ['x', 'y', 'width', 'height'].every((k) => Number.isFinite(b[k])) &&
126
+ b.x >= 0 &&
127
+ b.y >= 0 &&
128
+ b.width > 0 &&
129
+ b.height > 0 &&
130
+ b.x + b.width <= 1000 &&
131
+ b.y + b.height <= 1000
132
+ );
133
+ }
134
+ export function toPixels(b, g, padding = 0) {
135
+ if (!validBox(b)) throw new Error('配图坐标非法');
136
+ const left = Math.max(
137
+ 0,
138
+ Math.floor(((b.x / 1000) * g.width) / g.scaleX) - padding,
139
+ );
140
+ const top = Math.max(
141
+ 0,
142
+ Math.floor(((b.y / 1000) * g.height) / g.scaleY) - padding,
143
+ );
144
+ const right = Math.min(
145
+ g.sourceWidth,
146
+ Math.ceil((((b.x + b.width) / 1000) * g.width) / g.scaleX) + padding,
147
+ );
148
+ const bottom = Math.min(
149
+ g.sourceHeight,
150
+ Math.ceil((((b.y + b.height) / 1000) * g.height) / g.scaleY) + padding,
151
+ );
152
+ return { left, top, width: right - left, height: bottom - top };
153
+ }
154
+
155
+ async function prepare({ root, question: q }) {
156
+ const artifacts = [];
157
+ const layers = [];
158
+ let height = 0;
159
+ let width = 0;
160
+ for (const [i, f] of q.fragments.entries()) {
161
+ const m = await sharp(f.path).metadata();
162
+ if (i) height += 24;
163
+ if (
164
+ f.canvas.x !== 0 ||
165
+ f.canvas.y !== height ||
166
+ m.width !== f.canvas.width ||
167
+ m.height !== f.canvas.height
168
+ )
169
+ throw new Error('子流程片段拼接坐标不匹配');
170
+ layers.push({ input: f.path, left: 0, top: height });
171
+ width = Math.max(width, m.width);
172
+ height += m.height;
173
+ }
174
+ const m = await sharp(q.path).metadata();
175
+ if (m.width !== width || m.height !== height)
176
+ throw new Error('子流程成品与原片段尺寸不匹配');
177
+ const faithful = join(root, `questions/${q.id}-faithful.png`);
178
+ await save(
179
+ faithful,
180
+ await sharp({ create: { width, height, channels: 3, background: '#fff' } })
181
+ .composite(layers)
182
+ .png()
183
+ .toBuffer(),
184
+ );
185
+ const enhanced = await preview(q.path, join(root, `qa/${q.id}-preview.png`));
186
+ const plain = await preview(
187
+ faithful,
188
+ join(root, `qa/${q.id}-faithful-preview.png`),
189
+ );
190
+ artifacts.push(faithful, enhanced.path, plain.path);
191
+ return { faithful, enhanced, plain, artifacts };
192
+ }
193
+
194
+ async function crop({ root, id, base, geometry: g, figures, attempt }) {
195
+ const artifacts = [];
196
+ const result = [];
197
+ const m = await sharp(base).metadata();
198
+ if (m.width !== g.sourceWidth || m.height !== g.sourceHeight)
199
+ throw new Error('配图底图与预览不匹配');
200
+ for (const [i, figure] of figures.entries()) {
201
+ const b = toPixels(figure.box, g, 12);
202
+ const path = join(root, `figures/${id}-${i + 1}-a${attempt}.png`);
203
+ await save(path, await sharp(base).extract(b).png().toBuffer());
204
+ const p = await preview(
205
+ path,
206
+ join(root, `qa/${id}-${i + 1}-a${attempt}.png`),
207
+ );
208
+ const overlay = join(root, `qa/${id}-${i + 1}-a${attempt}-box.png`);
209
+ const svg = Buffer.from(
210
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${g.width}" height="${g.height}"><rect x="${b.left * g.scaleX}" y="${b.top * g.scaleY}" width="${b.width * g.scaleX}" height="${b.height * g.scaleY}" fill="none" stroke="red" stroke-width="2"/></svg>`,
211
+ );
212
+ await save(
213
+ overlay,
214
+ await sharp(g.path)
215
+ .composite([{ input: svg, top: 0, left: 0 }])
216
+ .png()
217
+ .toBuffer(),
218
+ );
219
+ result.push({
220
+ ...figure,
221
+ path,
222
+ preview: p.path,
223
+ overlay,
224
+ sourceBox: b,
225
+ width: b.width,
226
+ height: b.height,
227
+ });
228
+ artifacts.push(path, p.path, overlay);
229
+ }
230
+ return { figures: result, artifacts };
231
+ }
232
+
233
+ /** Same-target, nearby side-by-side figures are one group. Distant fragments
234
+ * stay separate; the subsequent visual check still decides completeness/ownership. */
235
+ export function groupFigures(figures) {
236
+ const groups = figures.map((f) => ({
237
+ ...f,
238
+ box: { ...f.box },
239
+ members: [f.id],
240
+ }));
241
+ for (let i = 0; i < groups.length; i++) {
242
+ for (let j = i + 1; j < groups.length; j++) {
243
+ const a = groups[i];
244
+ const b = groups[j];
245
+ if (a.kind !== 'figure' || b.kind !== 'figure') continue;
246
+ const overlap =
247
+ Math.min(a.box.y + a.box.height, b.box.y + b.box.height) -
248
+ Math.max(a.box.y, b.box.y);
249
+ const gap =
250
+ Math.max(a.box.x, b.box.x) -
251
+ Math.min(a.box.x + a.box.width, b.box.x + b.box.width);
252
+ // The normalized gap can include a wide printed gutter (q16's 图(1)/图(2)
253
+ // is about 175/1000 apart). Keep it as one crop while refusing distant
254
+ // figures; vertically separated fragments such as q5 remain independent.
255
+ if (overlap < 0.3 * Math.min(a.box.height, b.box.height) || gap > 200)
256
+ continue;
257
+ const x = Math.min(a.box.x, b.box.x);
258
+ const y = Math.min(a.box.y, b.box.y);
259
+ a.box = {
260
+ x,
261
+ y,
262
+ width: Math.max(a.box.x + a.box.width, b.box.x + b.box.width) - x,
263
+ height: Math.max(a.box.y + a.box.height, b.box.y + b.box.height) - y,
264
+ };
265
+ a.members.push(...b.members);
266
+ a.description += `;${b.description}`;
267
+ groups.splice(j, 1);
268
+ i = -1;
269
+ break;
270
+ }
271
+ }
272
+ return groups;
273
+ }
274
+
275
+ export function checkDetail(q, detail) {
276
+ if (detail.question_id !== q.sourceId || detail.observed_number !== q.number)
277
+ return '目标题号与实际观察题号不匹配';
278
+ if (!['ready', 'partial'].includes(detail.status))
279
+ return detail.reason || '原题内容无法确认';
280
+ if (!detail.blocks.some((b) => b.text.trim())) return '未得到可编辑题干';
281
+ const unsupported = detail.blocks.flatMap((block) =>
282
+ unsupportedMathCommands(block.text),
283
+ );
284
+ if (unsupported.length)
285
+ return `公式包含暂不支持的数学标记:${[...new Set(unsupported)].map((name) => `\\${name}`).join('、')}`;
286
+ if (
287
+ detail.figures.some((f) => !validBox(f.box)) ||
288
+ new Set(detail.figures.map((f) => f.id)).size !== detail.figures.length
289
+ )
290
+ return '配图坐标或分组不明确';
291
+ const missing = detailMissingParts(q, detail);
292
+ if (missing.some((part) => part.kind !== 'figure'))
293
+ return missing.map((part) => part.reason).join(';');
294
+ if (detail.status === 'partial' && !missing.length)
295
+ return '题目自报局部缺失,但没有说明缺失内容';
296
+ return '';
297
+ }
298
+
299
+ export function detailMissingParts(q, detail) {
300
+ const parts = [...(detail.missing_parts ?? [])];
301
+ for (const part of q.missing_parts ?? [])
302
+ parts.push({
303
+ ...part,
304
+ source_limited: q.content_status === 'source_truncated',
305
+ });
306
+ const legacyFigureWarning = (q.warnings ?? []).find(
307
+ (warning) =>
308
+ /(?:图|数轴).*(?:未找到|缺失|未.*(?:观察|出现))/.test(warning) ||
309
+ /(?:未找到|缺失|未.*(?:观察|出现)).*(?:图|数轴)/.test(warning),
310
+ );
311
+ if (legacyFigureWarning && !detail.figures.length)
312
+ parts.push({
313
+ kind: 'figure',
314
+ reason: legacyFigureWarning,
315
+ source_limited: q.content_status === 'source_truncated',
316
+ });
317
+ return parts.filter(
318
+ (part, index) =>
319
+ part?.reason &&
320
+ parts.findIndex(
321
+ (candidate) =>
322
+ candidate.kind === part.kind && candidate.reason === part.reason,
323
+ ) === index &&
324
+ (!part.source_limited ||
325
+ parts.findIndex(
326
+ (candidate) =>
327
+ candidate.kind === part.kind && candidate.source_limited,
328
+ ) === index),
329
+ );
330
+ }
331
+
332
+ export function answerLines(type, mode, figures) {
333
+ const base = Math.max(type === 'long' ? 12 : 6, figures ? 10 : 0);
334
+ return Math.max(
335
+ 2,
336
+ Math.round(
337
+ base * (mode === 'compact' ? 0.65 : mode === 'generous' ? 1.45 : 1),
338
+ ),
339
+ );
340
+ }
341
+
342
+ export function missingLabel(reason = '') {
343
+ if (/超时|timeout|时限/i.test(reason)) return '识别服务超时,请稍后补全。';
344
+ if (/配图|数轴|缺图/.test(reason))
345
+ return '必要配图缺失或不完整,请补拍完整题目。';
346
+ if (/不匹配|归属|多个章节/.test(reason))
347
+ return '题目归属无法确认,请补充题号或章节。';
348
+ if (/未找到/.test(reason)) return '指定题目未找到,请补充该题图片。';
349
+ return '原题内容尚未完整识别,请补充清晰完整的图片。';
350
+ }
351
+
352
+ const MATH_COMMANDS = {
353
+ angle: '∠',
354
+ cdot: '·',
355
+ cos: 'cos',
356
+ div: '÷',
357
+ ge: '≥',
358
+ geq: '≥',
359
+ in: '∈',
360
+ infty: '∞',
361
+ le: '≤',
362
+ leq: '≤',
363
+ ln: 'ln',
364
+ log: 'log',
365
+ neq: '≠',
366
+ notin: '∉',
367
+ parallel: '∥',
368
+ perp: '⊥',
369
+ pi: 'π',
370
+ pm: '±',
371
+ quad: ' ',
372
+ qquad: ' ',
373
+ sin: 'sin',
374
+ tan: 'tan',
375
+ times: '×',
376
+ };
377
+
378
+ const STRUCTURED_MATH_COMMANDS = new Set([
379
+ 'dfrac',
380
+ 'frac',
381
+ 'left',
382
+ 'mathrm',
383
+ 'right',
384
+ 'sqrt',
385
+ 'text',
386
+ 'tfrac',
387
+ ]);
388
+
389
+ export function containsMathMarkup(block) {
390
+ return block.kind === 'formula' || /\\[A-Za-z]+/.test(block.text);
391
+ }
392
+
393
+ export function unsupportedMathCommands(text) {
394
+ return [
395
+ ...new Set(
396
+ [...String(text ?? '').matchAll(/\\([A-Za-z]+)/g)]
397
+ .map((match) => match[1])
398
+ .filter(
399
+ (name) =>
400
+ !(name in MATH_COMMANDS) && !STRUCTURED_MATH_COMMANDS.has(name),
401
+ ),
402
+ ),
403
+ ];
404
+ }
405
+
406
+ /** Convert the small TeX-like interchange form returned by vision models into
407
+ * native Word OMML. The source syntax stays in manifest.json for auditing; it
408
+ * must never leak into the learner-facing document. */
409
+ export function mathComponents(source) {
410
+ const input = String(source ?? '');
411
+ let index = 0;
412
+
413
+ const group = () => {
414
+ while (/\s/.test(input[index] ?? '')) index++;
415
+ if (input[index] !== '{') return atom();
416
+ index++;
417
+ const value = sequence('}');
418
+ if (input[index] === '}') index++;
419
+ return value;
420
+ };
421
+
422
+ const command = () => {
423
+ index++; // backslash
424
+ const start = index;
425
+ while (/[A-Za-z]/.test(input[index] ?? '')) index++;
426
+ const name = input.slice(start, index);
427
+ if (!name) return [new MathRun(input[index++] ?? '')];
428
+ if (name === 'frac' || name === 'dfrac' || name === 'tfrac')
429
+ return [new MathFraction({ numerator: group(), denominator: group() })];
430
+ if (name === 'sqrt') return [new MathRadical({ children: group() })];
431
+ if (name === 'text' || name === 'mathrm') return group();
432
+ if (name === 'left' || name === 'right') return [];
433
+ if (!(name in MATH_COMMANDS))
434
+ throw new Error(`不支持的数学公式标记:\\${name}`);
435
+ return [new MathRun(MATH_COMMANDS[name])];
436
+ };
437
+
438
+ const atom = () => {
439
+ if (index >= input.length) return [];
440
+ if (input[index] === '\\') return command();
441
+ if (input[index] === '{') return group();
442
+ return [new MathRun(input[index++])];
443
+ };
444
+
445
+ const script = () => {
446
+ while (/\s/.test(input[index] ?? '')) index++;
447
+ return group();
448
+ };
449
+
450
+ const sequence = (until = '') => {
451
+ const out = [];
452
+ while (index < input.length && (!until || input[index] !== until)) {
453
+ let base = atom();
454
+ if (!base.length) continue;
455
+ let subScript;
456
+ let superScript;
457
+ while (input[index] === '_' || input[index] === '^') {
458
+ const marker = input[index++];
459
+ if (marker === '_') subScript = script();
460
+ else superScript = script();
461
+ }
462
+ if (subScript && superScript)
463
+ base = [
464
+ new MathSubSuperScript({ children: base, subScript, superScript }),
465
+ ];
466
+ else if (subScript)
467
+ base = [new MathSubScript({ children: base, subScript })];
468
+ else if (superScript)
469
+ base = [new MathSuperScript({ children: base, superScript })];
470
+ out.push(...base);
471
+ }
472
+ return out;
473
+ };
474
+
475
+ return sequence();
476
+ }
477
+
478
+ function blockRuns(block) {
479
+ if (containsMathMarkup(block))
480
+ return [new OfficeMath({ children: mathComponents(block.text) })];
481
+ return [new TextRun({ text: block.text, font: '宋体' })];
482
+ }
483
+
484
+ export async function build({ root, manifest }) {
485
+ const renderable = (q) => ['success', 'partial'].includes(q.status);
486
+ const ready = manifest.questions.filter(renderable).length;
487
+ const complete = manifest.questions.filter(
488
+ (q) => q.status === 'success',
489
+ ).length;
490
+ const partial = manifest.questions.filter(
491
+ (q) => q.status === 'partial',
492
+ ).length;
493
+ if (!ready) throw new Error('没有成功题,不能生成伪成品');
494
+ const children = [
495
+ new Paragraph({
496
+ children: [new TextRun({ text: '错题练习册', bold: true, size: 32 })],
497
+ spacing: { after: 180 },
498
+ keepNext: true,
499
+ }),
500
+ new Paragraph({
501
+ children: [
502
+ new TextRun(
503
+ `完整整理 ${complete} 题,局部待补 ${partial} 题,无法整理 ${manifest.questions.length - ready} 题`,
504
+ ),
505
+ ],
506
+ spacing: { after: 180 },
507
+ keepNext: true,
508
+ }),
509
+ ];
510
+ for (const [index, q] of manifest.questions.entries()) {
511
+ const isChoice = q.blocks?.some((b) => b.kind === 'option');
512
+ children.push(
513
+ new Paragraph({
514
+ pageBreakBefore: index > 0,
515
+ keepNext: true,
516
+ children: [
517
+ new TextRun({
518
+ text: `第${q.number}题${q.status === 'partial' ? '(配图待补)' : q.status === 'success' ? '' : '待补全'}`,
519
+ bold: true,
520
+ }),
521
+ ],
522
+ spacing: { after: 100 },
523
+ }),
524
+ );
525
+ if (renderable(q)) {
526
+ let renderBlocks = (q.blocks ?? []).map((block) => ({ ...block }));
527
+ // The header already identifies the target. Bare A–D labels are retained
528
+ // inside verified graphic options, not repeated as four empty paragraphs.
529
+ const options = renderBlocks.filter((b) => b.kind === 'option');
530
+ if (
531
+ q.figures.length &&
532
+ options.length > 1 &&
533
+ options.every((b) => /^[A-D][..、]?\s*$/.test(b.text.trim()))
534
+ )
535
+ renderBlocks = renderBlocks.filter((b) => b.kind !== 'option');
536
+ else if (
537
+ options.length > 1 &&
538
+ options.every((b) => b.text.length <= 18 && !b.text.includes('\n')) &&
539
+ // 含公式标记的选项不能拼进同一个纯文本块:合并会把多个独立
540
+ // OfficeMath 塌缩成一个,字符串拼接出的假"公式"里混进 A/B/C/D
541
+ // 字母和分隔空格,既破坏可读性也让 validate() 的公式计数对不上
542
+ // (真实端到端跑卷纸.jpg 第9题 A/C 选项都是分数选项时实测触发)。
543
+ !options.some(containsMathMarkup)
544
+ ) {
545
+ let inserted = false;
546
+ renderBlocks = renderBlocks.flatMap((b) => {
547
+ if (b.kind !== 'option') return [b];
548
+ if (inserted) return [];
549
+ inserted = true;
550
+ return [
551
+ {
552
+ kind: 'option',
553
+ text: options.map((o) => o.text).join(' '),
554
+ },
555
+ ];
556
+ });
557
+ }
558
+ if (renderBlocks[0] && /^\d+$/.test(q.number))
559
+ renderBlocks[0] = {
560
+ ...renderBlocks[0],
561
+ text: renderBlocks[0].text.replace(
562
+ new RegExp(`^\\s*${q.number}\\s*[..、]\\s*`),
563
+ '',
564
+ ),
565
+ };
566
+ for (const block of renderBlocks)
567
+ children.push(
568
+ new Paragraph({
569
+ keepNext: true,
570
+ children: blockRuns(block),
571
+ spacing: { after: 100 },
572
+ }),
573
+ );
574
+ const ratios = q.figures.map((f) =>
575
+ Math.min(1, 620 / f.width, 300 / f.height),
576
+ );
577
+ const height = q.figures.reduce(
578
+ (sum, f, i) => sum + f.height * ratios[i],
579
+ 0,
580
+ );
581
+ const fit = Math.min(1, 300 / Math.max(1, height));
582
+ for (const [i, f] of q.figures.entries()) {
583
+ const ratio = ratios[i] * fit;
584
+ children.push(
585
+ new Paragraph({
586
+ keepNext: true,
587
+ children: [
588
+ new ImageRun({
589
+ type: 'png',
590
+ data: await readFile(f.path),
591
+ transformation: {
592
+ width: Math.max(1, Math.round(f.width * ratio)),
593
+ height: Math.max(1, Math.round(f.height * ratio)),
594
+ },
595
+ altText: {
596
+ title: f.id,
597
+ description: f.description,
598
+ name: f.id,
599
+ },
600
+ }),
601
+ ],
602
+ }),
603
+ );
604
+ }
605
+ if (q.status === 'partial')
606
+ for (const part of q.missingParts ?? [])
607
+ children.push(
608
+ new Paragraph({
609
+ keepNext: true,
610
+ children: [
611
+ new TextRun({
612
+ text: part.source_limited
613
+ ? `配图待补:原照片未拍到完整内容。${part.reason}`
614
+ : `配图待补:${part.reason}`,
615
+ color: '808080',
616
+ italics: true,
617
+ }),
618
+ ],
619
+ spacing: { after: 100 },
620
+ }),
621
+ );
622
+ } else
623
+ children.push(
624
+ new Paragraph({
625
+ keepNext: true,
626
+ children: [new TextRun(missingLabel(q.reason))],
627
+ }),
628
+ );
629
+ const n = answerLines(
630
+ isChoice ? 'short' : q.type,
631
+ manifest.answerSpace,
632
+ q.figures?.length,
633
+ );
634
+ q.answerLines = n;
635
+ // End keepNext at the first writing line; long questions may break naturally.
636
+ for (let i = 0; i < n; i++)
637
+ children.push(
638
+ new Paragraph({
639
+ children: [new TextRun(' ')],
640
+ spacing: {
641
+ before: 0,
642
+ after: 0,
643
+ line: 510,
644
+ lineRule: LineRuleType.EXACT,
645
+ },
646
+ border: {
647
+ bottom: {
648
+ color: 'E2E2E2',
649
+ style: BorderStyle.SINGLE,
650
+ size: 2,
651
+ space: 0,
652
+ },
653
+ between: {
654
+ color: 'E2E2E2',
655
+ style: BorderStyle.SINGLE,
656
+ size: 2,
657
+ space: 0,
658
+ },
659
+ },
660
+ }),
661
+ );
662
+ }
663
+ const doc = new Document({
664
+ styles: { default: { document: { run: { font: '宋体', size: 24 } } } },
665
+ sections: [
666
+ {
667
+ properties: {
668
+ page: {
669
+ size: { width: 11906, height: 16838 },
670
+ margin: { top: 1020, bottom: 1020, left: 1020, right: 1020 },
671
+ },
672
+ },
673
+ children,
674
+ },
675
+ ],
676
+ });
677
+ const path = join(root, 'wrong-questions.docx');
678
+ await save(path, await Packer.toBuffer(doc));
679
+ return { path, questions: manifest.questions, artifacts: [path] };
680
+ }
681
+
682
+ function xml(bytes) {
683
+ const issues = [];
684
+ const doc = new DOMParser({
685
+ onError: (level, message) => {
686
+ if (level !== 'warning') issues.push(message);
687
+ },
688
+ }).parseFromString(strFromU8(bytes), 'text/xml');
689
+ if (issues.length) throw new Error(`DOCX XML无效:${issues.join(';')}`);
690
+ return doc;
691
+ }
692
+ export async function validate({ path, manifest }) {
693
+ const entries = unzipSync(await readFile(path));
694
+ for (const name of [
695
+ '[Content_Types].xml',
696
+ '_rels/.rels',
697
+ 'word/document.xml',
698
+ 'word/_rels/document.xml.rels',
699
+ ])
700
+ if (!entries[name]?.length) throw new Error(`DOCX缺少${name}`);
701
+ for (const [name, bytes] of Object.entries(entries)) {
702
+ if (!name.endsWith('/') && !bytes.length)
703
+ throw new Error(`DOCX空文件:${name}`);
704
+ if (!name.endsWith('.rels')) continue;
705
+ const rels = xml(bytes).getElementsByTagName('Relationship');
706
+ const base =
707
+ name === '_rels/.rels' ? '' : name.slice(0, name.lastIndexOf('/_rels/'));
708
+ for (const r of Array.from(rels)) {
709
+ if (r.getAttribute('TargetMode') === 'External') continue;
710
+ const target = r.getAttribute('Target');
711
+ const resolved = target.startsWith('/')
712
+ ? target.slice(1)
713
+ : join(base, target).replaceAll('\\', '/');
714
+ if (!entries[resolved]?.length)
715
+ throw new Error(`DOCX关系目标缺失:${resolved}`);
716
+ }
717
+ }
718
+ const document = xml(entries['word/document.xml']);
719
+ const texts = Array.from(document.getElementsByTagName('w:t')).map(
720
+ (n) => n.textContent,
721
+ );
722
+ const mathTexts = Array.from(document.getElementsByTagName('m:t')).map(
723
+ (n) => n.textContent,
724
+ );
725
+ const content = [...texts, ...mathTexts].join('\n');
726
+ const expectedMath = manifest.questions.flatMap((q) =>
727
+ ['success', 'partial'].includes(q.status)
728
+ ? (q.blocks ?? []).filter((block) => containsMathMarkup(block))
729
+ : [],
730
+ ).length;
731
+ const actualMath = document.getElementsByTagName('m:oMath').length;
732
+ if (actualMath !== expectedMath)
733
+ throw new Error(`DOCX原生公式数不符:${actualMath}/${expectedMath}`);
734
+ if (/\\[A-Za-z]+/.test(content)) throw new Error('DOCX正文泄漏了LaTeX控制串');
735
+ for (const q of manifest.questions) {
736
+ const title = `第${q.number}题${q.status === 'partial' ? '(配图待补)' : q.status === 'success' ? '' : '待补全'}`;
737
+ if (!texts.includes(title)) throw new Error(`DOCX缺少${title}`);
738
+ for (const block of q.blocks ?? [])
739
+ if (
740
+ ['success', 'partial'].includes(q.status) &&
741
+ !containsMathMarkup(block) &&
742
+ !content.includes(block.text)
743
+ )
744
+ throw new Error(`DOCX缺少第${q.number}题文字块`);
745
+ }
746
+ const expected = manifest.questions.flatMap((q) =>
747
+ ['success', 'partial'].includes(q.status) ? q.figures : [],
748
+ ).length;
749
+ const drawings = document.getElementsByTagName('w:drawing').length;
750
+ if (drawings !== expected)
751
+ throw new Error(`DOCX图片数不符:${drawings}/${expected}`);
752
+ const rels = Array.from(
753
+ xml(entries['word/_rels/document.xml.rels']).getElementsByTagName(
754
+ 'Relationship',
755
+ ),
756
+ );
757
+ for (const b of Array.from(document.getElementsByTagName('a:blip')))
758
+ if (!rels.some((r) => r.getAttribute('Id') === b.getAttribute('r:embed')))
759
+ throw new Error('DOCX媒体引用失效');
760
+ return {
761
+ passed: true,
762
+ questions: manifest.questions.length,
763
+ images: expected,
764
+ formulas: expectedMath,
765
+ };
766
+ }
767
+
768
+ function run(bin, args) {
769
+ const r = spawnSync(bin, args, {
770
+ encoding: 'utf8',
771
+ timeout: 120000,
772
+ maxBuffer: 8 * 1024 * 1024,
773
+ });
774
+ if (r.error || r.status !== 0)
775
+ throw new Error(`${bin}执行失败:${r.error?.message ?? r.stderr}`);
776
+ return r.stdout;
777
+ }
778
+ async function resolveOffice() {
779
+ if (process.platform === 'darwin')
780
+ return '/Applications/LibreOffice.app/Contents/MacOS/soffice';
781
+ if (process.platform === 'win32') {
782
+ // Windows 安装 LibreOffice 默认不写入 PATH,裸 'soffice' 在很多机器上解析不到
783
+ // ——真实端到端测试实测触发。probe 常见安装目录,找不到再退回裸命令名,
784
+ // 兼容确实把它加进 PATH 的机器。
785
+ for (const candidate of [
786
+ 'C:\\Program Files\\LibreOffice\\program\\soffice.exe',
787
+ 'C:\\Program Files (x86)\\LibreOffice\\program\\soffice.exe',
788
+ ]) {
789
+ if (await stat(candidate).catch(() => null)) return candidate;
790
+ }
791
+ }
792
+ return 'soffice';
793
+ }
794
+ export async function render({ path, root }) {
795
+ const dir = join(root, 'qa/rendered');
796
+ await mkdir(dir, { recursive: true });
797
+ const office = await resolveOffice();
798
+ const profile = await mkdtemp(join(tmpdir(), 'wq-docx-office-'));
799
+ run(office, [
800
+ `-env:UserInstallation=${pathToFileURL(profile).href}`,
801
+ '--headless',
802
+ '--convert-to',
803
+ 'pdf',
804
+ '--outdir',
805
+ root,
806
+ path,
807
+ ]);
808
+ const pdf = join(root, `${basename(path, '.docx')}.pdf`);
809
+ if (!(await stat(pdf)).size) throw new Error('PDF为空');
810
+ run('pdftoppm', ['-scale-to', '1600', '-png', pdf, join(dir, 'page')]);
811
+ const { readdir } = await import('node:fs/promises');
812
+ const pages = (await readdir(dir))
813
+ .filter((n) => /^page-\d+\.png$/.test(n))
814
+ .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
815
+ .map((n) => join(dir, n));
816
+ if (!pages.length) throw new Error('未生成渲染页面');
817
+ return { pdf, pages, artifacts: [pdf, ...pages] };
818
+ }
819
+
820
+ const actions = {
821
+ prepare,
822
+ crop,
823
+ build,
824
+ validate,
825
+ render,
826
+ selection: async ({ text }) => parseSelection(text),
827
+ select: async ({ manifest, selection }) =>
828
+ selectQuestions(manifest, selection),
829
+ detail: async ({ question, detail }) => {
830
+ const reason = checkDetail(question, detail);
831
+ const missingParts = reason ? [] : detailMissingParts(question, detail);
832
+ return {
833
+ reason,
834
+ status: missingParts.length ? 'partial' : 'success',
835
+ missingParts,
836
+ figures: reason ? [] : groupFigures(detail.figures),
837
+ };
838
+ },
839
+ absolute: async ({ path }) => resolve(path),
840
+ };
841
+ if (
842
+ process.argv[1] &&
843
+ import.meta.url === pathToFileURL(resolve(process.argv[1])).href
844
+ ) {
845
+ try {
846
+ const [action, payload] = process.argv.slice(2);
847
+ if (!Object.hasOwn(actions, action)) throw new Error('未知操作');
848
+ console.log(
849
+ JSON.stringify(
850
+ await actions[action](JSON.parse(await readFile(payload, 'utf8'))),
851
+ ),
852
+ );
853
+ } catch (e) {
854
+ console.error(e.stack || String(e));
855
+ process.exitCode = 1;
856
+ }
857
+ }