minimal-agent 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/README.md +6 -5
  2. package/package.json +62 -5
  3. package/plugins/computer_use/.claude-plugin/plugin.json +1 -1
  4. package/plugins/cron-runner/src/commands.js +4 -4
  5. package/plugins/cron-runner/src/commands.ts +4 -4
  6. package/plugins/workflow-runner/.claude-plugin/plugin.json +2 -2
  7. package/plugins/workflow-runner/commands/workflow.md +57 -90
  8. package/plugins/workflow-runner/commands/workflows.md +4 -4
  9. package/plugins/workflow-runner/plugin.js +24 -13
  10. package/plugins/workflow-runner/plugin.ts +28 -13
  11. package/plugins/workflow-runner/src/discovery.js +160 -0
  12. package/plugins/workflow-runner/src/discovery.ts +194 -0
  13. package/plugins/workflow-runner/src/goalAnchor.js +53 -0
  14. package/plugins/workflow-runner/src/goalAnchor.ts +59 -0
  15. package/plugins/workflow-runner/src/index.js +204 -185
  16. package/plugins/workflow-runner/src/index.ts +249 -224
  17. package/plugins/workflow-runner/src/inputCoercion.js +40 -0
  18. package/plugins/workflow-runner/src/inputCoercion.ts +43 -0
  19. package/plugins/workflow-runner/src/journal.js +224 -0
  20. package/plugins/workflow-runner/src/journal.ts +274 -0
  21. package/plugins/workflow-runner/src/jsonSchema.js +132 -0
  22. package/plugins/workflow-runner/src/jsonSchema.ts +162 -0
  23. package/plugins/workflow-runner/src/moduleResolution.js +64 -0
  24. package/plugins/workflow-runner/src/moduleResolution.ts +71 -0
  25. package/plugins/workflow-runner/src/resourceInputs.js +220 -0
  26. package/plugins/workflow-runner/src/resourceInputs.ts +300 -0
  27. package/plugins/workflow-runner/src/runner.js +404 -467
  28. package/plugins/workflow-runner/src/runner.ts +491 -535
  29. package/plugins/workflow-runner/src/runtime/config.js +46 -0
  30. package/plugins/workflow-runner/src/runtime/config.ts +69 -0
  31. package/plugins/workflow-runner/src/runtime/context.js +1304 -0
  32. package/plugins/workflow-runner/src/runtime/context.ts +1786 -0
  33. package/plugins/workflow-runner/src/runtime/exec.js +34 -0
  34. package/plugins/workflow-runner/src/runtime/exec.ts +53 -0
  35. package/plugins/workflow-runner/src/runtime/flow.js +8 -0
  36. package/plugins/workflow-runner/src/runtime/flow.ts +12 -0
  37. package/plugins/workflow-runner/src/runtime/fs.js +27 -0
  38. package/plugins/workflow-runner/src/runtime/fs.ts +41 -0
  39. package/plugins/workflow-runner/src/runtime/model.js +6 -0
  40. package/plugins/workflow-runner/src/runtime/model.ts +8 -0
  41. package/plugins/workflow-runner/src/runtime/output.js +7 -0
  42. package/plugins/workflow-runner/src/runtime/output.ts +21 -0
  43. package/plugins/workflow-runner/src/runtime/scheduler.js +141 -0
  44. package/plugins/workflow-runner/src/runtime/scheduler.ts +166 -0
  45. package/plugins/workflow-runner/src/runtime/state.js +16 -0
  46. package/plugins/workflow-runner/src/runtime/state.ts +19 -0
  47. package/plugins/workflow-runner/src/runtime/tools.js +3 -0
  48. package/plugins/workflow-runner/src/runtime/tools.ts +6 -0
  49. package/plugins/workflow-runner/src/sandbox.js +141 -0
  50. package/plugins/workflow-runner/src/sandbox.ts +182 -0
  51. package/plugins/workflow-runner/src/scriptGuard.d.mts +38 -0
  52. package/plugins/workflow-runner/src/scriptGuard.mjs +449 -0
  53. package/plugins/workflow-runner/src/taskIdentity.js +172 -152
  54. package/plugins/workflow-runner/src/taskIdentity.ts +233 -200
  55. package/plugins/workflow-runner/src/toolRuntime.js +29 -8
  56. package/plugins/workflow-runner/src/toolRuntime.ts +36 -11
  57. package/plugins/workflow-runner/src/types.js +7 -105
  58. package/plugins/workflow-runner/src/types.ts +155 -520
  59. package/plugins/workflow-runner/src/util.js +47 -0
  60. package/plugins/workflow-runner/src/util.ts +51 -0
  61. package/plugins/workflow-runner/src/workflowContext.js +20 -0
  62. package/plugins/workflow-runner/src/workflowContext.ts +58 -0
  63. package/plugins/workflow-runner/src/workflowDependencies.js +36 -0
  64. package/plugins/workflow-runner/src/workflowDependencies.ts +59 -0
  65. package/plugins/workflow-runner/src/workflowFingerprint.js +118 -0
  66. package/plugins/workflow-runner/src/workflowFingerprint.ts +122 -0
  67. package/plugins/workflow-runner/src/workflowState.js +542 -469
  68. package/plugins/workflow-runner/src/workflowState.ts +639 -589
  69. package/plugins/workflow-runner/test/api.test.ts +223 -0
  70. package/plugins/workflow-runner/test/apiCapabilities.test.ts +614 -0
  71. package/plugins/workflow-runner/test/apiConcurrencyGate.test.ts +104 -0
  72. package/plugins/workflow-runner/test/apiEcho.test.ts +210 -0
  73. package/plugins/workflow-runner/test/apiReadText.test.ts +178 -0
  74. package/plugins/workflow-runner/test/apiResourceLedger.test.ts +184 -0
  75. package/plugins/workflow-runner/test/childWorkflow.test.ts +304 -0
  76. package/plugins/workflow-runner/test/discovery.test.ts +234 -0
  77. package/plugins/workflow-runner/test/goalAnchor.test.ts +106 -0
  78. package/plugins/workflow-runner/test/goalController.test.ts +317 -0
  79. package/plugins/workflow-runner/test/journalLanes.test.ts +198 -0
  80. package/plugins/workflow-runner/test/listResources.test.ts +106 -0
  81. package/plugins/workflow-runner/test/resourceInputs.test.ts +294 -0
  82. package/plugins/workflow-runner/test/sandbox.test.ts +126 -0
  83. package/plugins/workflow-runner/test/scheduler.test.ts +70 -0
  84. package/plugins/workflow-runner/test/scriptGuard.test.ts +276 -0
  85. package/plugins/workflow-runner/test/skillDocSync.test.ts +78 -0
  86. package/plugins/workflow-runner/test/taskIdentity.test.ts +288 -179
  87. package/plugins/workflow-runner/test/workflowFingerprint.test.ts +106 -0
  88. package/plugins/workflow-runner/test/workflowState.test.ts +429 -0
  89. package/skills/pptx/examples/assets/cover-bg.svg +23 -0
  90. package/skills/pptx/examples/assets/icon-spark.svg +5 -0
  91. package/skills/skill-creator/scripts/run_eval.py +3 -2
  92. package/skills/ui-design/SKILL.md +5 -2
  93. package/skills/ui-design/references/layout-composition.md +1 -1
  94. package/skills/workflow-creator/SKILL.md +344 -0
  95. package/skills/workflow-creator/assets/template/workflow.mjs +48 -0
  96. package/skills/workflow-creator/references/api.md +216 -0
  97. package/skills/workflow-creator/references/migration.md +103 -0
  98. package/skills/workflow-creator/references/patterns.md +779 -0
  99. package/skills/workflow-creator/scripts/list-resources.mjs +205 -0
  100. package/skills/workflow-creator/scripts/scaffold.mjs +148 -0
  101. package/skills/workflow-creator/scripts/validate.mjs +275 -0
  102. package/skills/workflow-creator/scripts/workflow-creator.test.mjs +82 -0
  103. package/src/advisor/advisorCoordinator.js +503 -0
  104. package/src/advisor/advisorCoordinator.ts +657 -0
  105. package/src/cli/configMenu.tsx +0 -1
  106. package/src/cli/print.js +44 -6
  107. package/src/cli/print.ts +73 -4
  108. package/src/cli/streamJson.js +5 -6
  109. package/src/cli/streamJson.ts +8 -4
  110. package/src/cli/wizards/advisorWizard.js +4 -2
  111. package/src/cli/wizards/advisorWizard.tsx +8 -2
  112. package/src/cli/wizards/contextAdvisorWizard.js +5 -4
  113. package/src/cli/wizards/contextAdvisorWizard.tsx +7 -6
  114. package/src/collaboration/taskFlow.js +115 -0
  115. package/src/collaboration/taskFlow.ts +169 -0
  116. package/src/config/configDefaults.js +41 -1
  117. package/src/config/configDefaults.ts +43 -1
  118. package/src/config/configDiagnostics.ts +2 -2
  119. package/src/config/configFile.js +40 -9
  120. package/src/config/configFile.ts +60 -11
  121. package/src/config/configService.js +443 -74
  122. package/src/config/configService.ts +723 -142
  123. package/src/config/numericLimits.js +22 -0
  124. package/src/config/numericLimits.ts +24 -0
  125. package/src/config.js +1 -0
  126. package/src/config.ts +3 -2
  127. package/src/context/budgetPlan.js +1 -1
  128. package/src/context/budgetPlan.ts +1 -1
  129. package/src/context/compact.js +7 -2
  130. package/src/context/compact.ts +9 -2
  131. package/src/context/microCompactLite.js +212 -13
  132. package/src/context/microCompactLite.ts +261 -12
  133. package/src/context/sessionRegistry.js +8 -1
  134. package/src/context/sessionRegistry.ts +18 -5
  135. package/src/context/toolResultArchive.js +72 -0
  136. package/src/context/toolResultArchive.ts +95 -0
  137. package/src/goal/errors.js +8 -0
  138. package/src/goal/errors.ts +18 -0
  139. package/src/goal/goalRuntime.js +344 -0
  140. package/src/goal/goalRuntime.ts +466 -0
  141. package/src/goal/goalStore.js +200 -0
  142. package/src/goal/goalStore.ts +219 -0
  143. package/src/goal/index.js +4 -0
  144. package/src/goal/index.ts +4 -0
  145. package/src/goal/statusMapping.js +71 -0
  146. package/src/goal/statusMapping.ts +95 -0
  147. package/src/goal/types.js +7 -0
  148. package/src/goal/types.ts +179 -0
  149. package/src/goal/workflowController.js +306 -0
  150. package/src/goal/workflowController.ts +360 -0
  151. package/src/harness/evidenceLedger.js +2 -0
  152. package/src/harness/evidenceLedger.ts +1 -0
  153. package/src/harness/stallGuard.js +9 -3
  154. package/src/harness/stallGuard.ts +7 -3
  155. package/src/images/imageResolver.js +161 -0
  156. package/src/images/imageResolver.ts +189 -0
  157. package/src/infra/verification.js +56 -20
  158. package/src/infra/verification.ts +87 -20
  159. package/src/llm/client.js +79 -21
  160. package/src/llm/client.ts +182 -35
  161. package/src/llm/gatewayClient.js +201 -0
  162. package/src/llm/gatewayClient.ts +299 -0
  163. package/src/llm/messageProvenance.js +46 -0
  164. package/src/llm/messageProvenance.ts +58 -0
  165. package/src/llm/modelProvider.js +1 -0
  166. package/src/llm/modelProvider.ts +22 -0
  167. package/src/llm/providers/anthropicFormat.js +108 -1
  168. package/src/llm/providers/anthropicFormat.ts +115 -4
  169. package/src/llm/providers/openaiFormat.js +9 -0
  170. package/src/llm/providers/openaiFormat.ts +12 -3
  171. package/src/loop.js +319 -54
  172. package/src/loop.ts +373 -57
  173. package/src/main.js +4 -1
  174. package/src/main.tsx +4 -1
  175. package/src/managedExecution/index.js +2 -0
  176. package/src/managedExecution/index.ts +2 -0
  177. package/src/managedExecution/registry.js +15 -0
  178. package/src/managedExecution/registry.ts +20 -0
  179. package/src/managedExecution/types.js +1 -0
  180. package/src/managedExecution/types.ts +86 -0
  181. package/src/modelRef.js +9 -0
  182. package/src/modelRef.ts +31 -0
  183. package/src/multimodal.js +14 -28
  184. package/src/multimodal.ts +17 -33
  185. package/src/net/lanDiscovery.js +65 -0
  186. package/src/net/lanDiscovery.ts +82 -0
  187. package/src/plugin-sdk.js +28 -3
  188. package/src/plugin-sdk.ts +39 -6
  189. package/src/plugins/pluginApi.ts +16 -1
  190. package/src/plugins/pluginLoader.js +4 -0
  191. package/src/plugins/pluginLoader.ts +6 -0
  192. package/src/plugins/pluginRunner.js +152 -9
  193. package/src/plugins/pluginRunner.ts +186 -8
  194. package/src/plugins/slashFallback.js +113 -0
  195. package/src/plugins/slashFallback.ts +137 -0
  196. package/src/prompts/policyRegistry.js +15 -8
  197. package/src/prompts/policyRegistry.ts +18 -8
  198. package/src/prompts/promptAssembly.js +103 -12
  199. package/src/prompts/promptAssembly.ts +114 -12
  200. package/src/prompts/skillList.js +21 -3
  201. package/src/prompts/skillList.ts +27 -2
  202. package/src/prompts/stableSystemPrompt.js +18 -0
  203. package/src/prompts/stableSystemPrompt.ts +33 -0
  204. package/src/runtime/agentRuntimeContext.js +8 -2
  205. package/src/runtime/agentRuntimeContext.ts +17 -4
  206. package/src/session/runAgentTurn.js +78 -12
  207. package/src/session/runAgentTurn.ts +105 -23
  208. package/src/session/runTurn.js +10 -9
  209. package/src/session/runTurn.ts +17 -8
  210. package/src/session/sessionState.ts +2 -0
  211. package/src/team/auth.js +186 -0
  212. package/src/team/auth.ts +298 -0
  213. package/src/team/cli.js +105 -0
  214. package/src/team/cli.ts +122 -0
  215. package/src/team/onlineToolContract.js +64 -0
  216. package/src/team/onlineToolContract.ts +109 -0
  217. package/src/team/toolPolicy.js +36 -0
  218. package/src/team/toolPolicy.ts +75 -0
  219. package/src/tools/advisor/advisor.js +148 -37
  220. package/src/tools/advisor/advisor.ts +161 -38
  221. package/src/tools/advisor/advisorDecision.js +71 -0
  222. package/src/tools/advisor/advisorDecision.ts +85 -0
  223. package/src/tools/agent/agent.js +18 -5
  224. package/src/tools/agent/agent.ts +21 -7
  225. package/src/tools/agent/agentControl.js +246 -0
  226. package/src/tools/agent/agentControl.ts +284 -0
  227. package/src/tools/agent/agentTaskService.js +40 -1
  228. package/src/tools/agent/agentTaskService.ts +44 -1
  229. package/src/tools/agent/agentTypes.js +14 -2
  230. package/src/tools/agent/agentTypes.ts +15 -2
  231. package/src/tools/applypatch/applyPatch.js +362 -0
  232. package/src/tools/applypatch/applyPatch.ts +428 -0
  233. package/src/tools/bash/bash.js +70 -18
  234. package/src/tools/bash/bash.ts +75 -20
  235. package/src/tools/capabilities.js +10 -5
  236. package/src/tools/capabilities.ts +10 -5
  237. package/src/tools/computeruse/computerIntent.js +32 -0
  238. package/src/tools/computeruse/computerIntent.ts +46 -0
  239. package/src/tools/computeruse/telemetryLog.js +12 -0
  240. package/src/tools/computeruse/telemetryLog.ts +16 -0
  241. package/src/tools/computeruse/vision/telemetry.js +1 -0
  242. package/src/tools/computeruse/vision/telemetry.ts +1 -0
  243. package/src/tools/effectiveToolSet.js +4 -0
  244. package/src/tools/effectiveToolSet.ts +4 -0
  245. package/src/tools/index.js +21 -2
  246. package/src/tools/index.ts +33 -2
  247. package/src/tools/powershell/powershell.js +18 -1
  248. package/src/tools/powershell/powershell.ts +18 -2
  249. package/src/tools/presentresult/presentresult.js +6 -15
  250. package/src/tools/presentresult/presentresult.ts +6 -15
  251. package/src/tools/read/read.js +69 -11
  252. package/src/tools/read/read.ts +70 -11
  253. package/src/tools/shared/fileUtils.js +1 -2
  254. package/src/tools/shared/fileUtils.ts +1 -2
  255. package/src/tools/shared/permanentDeleteGuard.js +34 -3
  256. package/src/tools/shared/permanentDeleteGuard.ts +43 -6
  257. package/src/tools/shared/toolOutputStore.js +23 -1
  258. package/src/tools/shared/toolOutputStore.ts +22 -1
  259. package/src/tools/skill/skill.js +9 -3
  260. package/src/tools/skill/skill.ts +8 -4
  261. package/src/tools/toolPromptGuides.js +117 -0
  262. package/src/tools/toolPromptGuides.ts +137 -0
  263. package/src/tools/webbrowser/agentBrowserPath.js +19 -9
  264. package/src/tools/webbrowser/agentBrowserPath.ts +70 -28
  265. package/src/tools/webbrowser/browser.js +27 -11
  266. package/src/tools/webbrowser/browser.ts +68 -24
  267. package/src/tools/webbrowser/browserAutomation.js +329 -0
  268. package/src/tools/webbrowser/browserAutomation.ts +467 -0
  269. package/src/tools/webbrowser/browserAxState.js +97 -0
  270. package/src/tools/webbrowser/browserAxState.ts +142 -0
  271. package/src/tools/webbrowser/browserBatch.js +1578 -0
  272. package/src/tools/webbrowser/browserBatch.ts +2382 -0
  273. package/src/tools/webbrowser/browserCdp.js +208 -0
  274. package/src/tools/webbrowser/browserCdp.ts +255 -0
  275. package/src/tools/webbrowser/browserConditions.js +24 -0
  276. package/src/tools/webbrowser/browserConditions.ts +45 -0
  277. package/src/tools/webbrowser/browserContent.js +281 -0
  278. package/src/tools/webbrowser/browserContent.ts +438 -0
  279. package/src/tools/webbrowser/browserControl.js +131 -30
  280. package/src/tools/webbrowser/browserControl.ts +210 -50
  281. package/src/tools/webbrowser/browserDaemon.js +22 -14
  282. package/src/tools/webbrowser/browserDaemon.ts +54 -21
  283. package/src/tools/webbrowser/browserFrames.js +47 -0
  284. package/src/tools/webbrowser/browserFrames.ts +54 -0
  285. package/src/tools/webbrowser/browserInteractionResolver.js +17 -0
  286. package/src/tools/webbrowser/browserInteractionResolver.ts +40 -0
  287. package/src/tools/webbrowser/browserLocator.js +140 -0
  288. package/src/tools/webbrowser/browserLocator.ts +194 -0
  289. package/src/tools/webbrowser/browserOutcome.js +10 -17
  290. package/src/tools/webbrowser/browserOutcome.ts +48 -31
  291. package/src/tools/webbrowser/browserRecovery.js +58 -0
  292. package/src/tools/webbrowser/browserRecovery.ts +83 -0
  293. package/src/tools/webbrowser/browserRunRegistry.js +35 -3
  294. package/src/tools/webbrowser/browserRunRegistry.ts +91 -15
  295. package/src/tools/webbrowser/browserService.js +28 -2
  296. package/src/tools/webbrowser/browserService.ts +49 -9
  297. package/src/tools/webbrowser/browserStream.js +132 -33
  298. package/src/tools/webbrowser/browserStream.ts +224 -50
  299. package/src/tools/webbrowser/browserTabs.js +48 -15
  300. package/src/tools/webbrowser/browserTabs.ts +178 -96
  301. package/src/tools/webbrowser/webbrowser.js +683 -144
  302. package/src/tools/webbrowser/webbrowser.ts +979 -236
  303. package/src/tools/webfetch/preapproved.js +0 -2
  304. package/src/tools/webfetch/preapproved.ts +0 -2
  305. package/src/tools/websearch/websearch.js +129 -8
  306. package/src/tools/websearch/websearch.ts +138 -6
  307. package/src/types.ts +450 -42
  308. package/src/ui/App.js +377 -58
  309. package/src/ui/App.tsx +576 -111
  310. package/src/ui/LiveArea.js +14 -8
  311. package/src/ui/LiveArea.tsx +14 -10
  312. package/src/ui/MessageList.js +63 -20
  313. package/src/ui/MessageList.tsx +101 -16
  314. package/src/ui/ModelPicker.js +7 -4
  315. package/src/ui/ModelPicker.tsx +55 -8
  316. package/src/ui/hooks/useChat.js +152 -163
  317. package/src/ui/hooks/useChat.ts +396 -334
  318. package/src/ui/loginCommand.js +56 -0
  319. package/src/ui/loginCommand.ts +63 -0
  320. package/src/ui/toolProgress.js +41 -0
  321. package/src/ui/toolProgress.ts +44 -0
  322. package/src/web/browserBridge.js +20 -2
  323. package/src/web/browserBridge.ts +20 -2
  324. package/src/web/protocol.ts +1897 -194
  325. package/workflows/adaptive-research/workflow.mjs +86 -0
  326. package/workflows/e2e-capabilities/workflow.mjs +102 -0
  327. package/workflows/e2e-llm/workflow.mjs +52 -0
  328. package/workflows/e2e-smoke/workflow.mjs +59 -0
  329. package/workflows/e2e-vision-schema/README.md +48 -0
  330. package/workflows/e2e-vision-schema/workflow.mjs +149 -0
  331. package/workflows/e2e-write-greet/workflow.mjs +37 -0
  332. package/workflows/file-census/README.md +14 -0
  333. package/workflows/file-census/scripts/.gitkeep +2 -0
  334. package/workflows/file-census/workflow.mjs +110 -0
  335. package/workflows/goal/README.md +166 -0
  336. package/workflows/goal/prompts/audit.md +57 -0
  337. package/workflows/goal/prompts/contract.md +56 -0
  338. package/workflows/goal/prompts/iterate.md +37 -0
  339. package/workflows/goal/workflow.mjs +501 -0
  340. package/workflows/illustrate/README.md +90 -0
  341. package/workflows/illustrate/assets/engines.json +20 -0
  342. package/workflows/illustrate/assets/extract-baidu.js +21 -0
  343. package/workflows/illustrate/assets/extract-bing.js +33 -0
  344. package/workflows/illustrate/assets/extract-google.js +31 -0
  345. package/workflows/illustrate/workflow.mjs +301 -0
  346. package/workflows/plan/workflow.mjs +389 -0
  347. package/workflows/question-crops/README.md +35 -0
  348. package/workflows/question-crops/prompts/inventory.md +23 -0
  349. package/workflows/question-crops/scripts/image-ops.mjs +305 -0
  350. package/workflows/question-crops/scripts/image-ops.test.mjs +95 -0
  351. package/workflows/question-crops/workflow.mjs +172 -0
  352. package/workflows/ralph-loop/workflow.mjs +162 -0
  353. package/workflows/research/DESIGN.md +449 -0
  354. package/workflows/research/README.md +74 -0
  355. package/workflows/research/prompts/s0-goal.md +43 -0
  356. package/workflows/research/prompts/s1-early-exit.md +22 -0
  357. package/workflows/research/prompts/s1-map.md +58 -0
  358. package/workflows/research/prompts/s2-options.md +53 -0
  359. package/workflows/research/prompts/s3-deep-dive.md +56 -0
  360. package/workflows/research/prompts/s4-verify.md +74 -0
  361. package/workflows/research/prompts/s5-synthesis.md +81 -0
  362. package/workflows/research/prompts/s5b-fill-gaps.md +32 -0
  363. package/workflows/research/prompts/s6-package.md +32 -0
  364. package/workflows/research/scripts/build-report.mjs +98 -0
  365. package/workflows/research/workflow.mjs +288 -0
  366. package/workflows/wechat-reading-shots/prompts/collect-params.md +16 -0
  367. package/workflows/wechat-reading-shots/prompts/evaluate.md +20 -0
  368. package/workflows/wechat-reading-shots/prompts/open-book.md +28 -0
  369. package/workflows/wechat-reading-shots/prompts/read-page.md +13 -0
  370. package/workflows/wechat-reading-shots/prompts/turn-batch.md +24 -0
  371. package/workflows/wechat-reading-shots/test/control-flow.test.ts +329 -0
  372. package/workflows/wechat-reading-shots/workflow.mjs +266 -0
  373. package/workflows/wrong-question-docx/README.md +55 -0
  374. package/workflows/wrong-question-docx/prompts/figure-qa.md +8 -0
  375. package/workflows/wrong-question-docx/prompts/transcribe.md +25 -0
  376. package/workflows/wrong-question-docx/scripts/workbook.mjs +857 -0
  377. package/workflows/wrong-question-docx/test/control-flow.test.ts +165 -0
  378. package/workflows/wrong-question-docx/test/workbook.test.mjs +303 -0
  379. package/workflows/wrong-question-docx/workflow.mjs +485 -0
  380. package/plugins/workflow-runner/src/agentCommon.js +0 -354
  381. package/plugins/workflow-runner/src/agentCommon.ts +0 -435
  382. package/plugins/workflow-runner/src/controlFlow/branch.js +0 -40
  383. package/plugins/workflow-runner/src/controlFlow/branch.ts +0 -46
  384. package/plugins/workflow-runner/src/controlFlow/loop.js +0 -211
  385. package/plugins/workflow-runner/src/controlFlow/loop.ts +0 -248
  386. package/plugins/workflow-runner/src/controlFlow/pause.js +0 -129
  387. package/plugins/workflow-runner/src/controlFlow/pause.ts +0 -141
  388. package/plugins/workflow-runner/src/controlFlow/signal.js +0 -21
  389. package/plugins/workflow-runner/src/controlFlow/signal.ts +0 -29
  390. package/plugins/workflow-runner/src/controlFlow/types.js +0 -18
  391. package/plugins/workflow-runner/src/controlFlow/types.ts +0 -63
  392. package/plugins/workflow-runner/src/dslManifest.js +0 -72
  393. package/plugins/workflow-runner/src/dslManifest.ts +0 -127
  394. package/plugins/workflow-runner/src/expressions.js +0 -618
  395. package/plugins/workflow-runner/src/expressions.ts +0 -622
  396. package/plugins/workflow-runner/src/loader.js +0 -573
  397. package/plugins/workflow-runner/src/loader.ts +0 -721
  398. package/plugins/workflow-runner/src/multimodal.js +0 -38
  399. package/plugins/workflow-runner/src/multimodal.ts +0 -64
  400. package/plugins/workflow-runner/src/recovery.js +0 -119
  401. package/plugins/workflow-runner/src/recovery.ts +0 -149
  402. package/plugins/workflow-runner/src/stepExecutors/agent.js +0 -173
  403. package/plugins/workflow-runner/src/stepExecutors/agent.ts +0 -234
  404. package/plugins/workflow-runner/src/stepExecutors/assert.js +0 -41
  405. package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -50
  406. package/plugins/workflow-runner/src/stepExecutors/echo.js +0 -31
  407. package/plugins/workflow-runner/src/stepExecutors/echo.ts +0 -38
  408. package/plugins/workflow-runner/src/stepExecutors/llm.js +0 -99
  409. package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -133
  410. package/plugins/workflow-runner/src/stepExecutors/set.js +0 -49
  411. package/plugins/workflow-runner/src/stepExecutors/set.ts +0 -56
  412. package/plugins/workflow-runner/src/stepExecutors/skill.js +0 -111
  413. package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -140
  414. package/plugins/workflow-runner/src/stepExecutors/tool.js +0 -208
  415. package/plugins/workflow-runner/src/stepExecutors/tool.ts +0 -256
  416. package/plugins/workflow-runner/src/stepExecutors/verify.js +0 -58
  417. package/plugins/workflow-runner/src/stepExecutors/verify.ts +0 -68
  418. package/plugins/workflow-runner/src/stepPipeline.js +0 -277
  419. package/plugins/workflow-runner/src/stepPipeline.ts +0 -366
  420. package/plugins/workflow-runner/test/agent.test.ts +0 -587
  421. package/plugins/workflow-runner/test/agentLlmError.test.ts +0 -373
  422. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +0 -65
  423. package/plugins/workflow-runner/test/captureMisbind.test.ts +0 -99
  424. package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -147
  425. package/plugins/workflow-runner/test/context-files.test.ts +0 -320
  426. package/plugins/workflow-runner/test/e2e.test.ts +0 -370
  427. package/plugins/workflow-runner/test/engineRobustness.test.ts +0 -226
  428. package/plugins/workflow-runner/test/expressions.test.ts +0 -397
  429. package/plugins/workflow-runner/test/foundation.test.ts +0 -126
  430. package/plugins/workflow-runner/test/graceful.test.ts +0 -139
  431. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +0 -47
  432. package/plugins/workflow-runner/test/hostCapability.test.ts +0 -108
  433. package/plugins/workflow-runner/test/illustrate.load.test.ts +0 -183
  434. package/plugins/workflow-runner/test/jsonPayload.test.ts +0 -83
  435. package/plugins/workflow-runner/test/loader.test.ts +0 -617
  436. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +0 -33
  437. package/plugins/workflow-runner/test/multimodal.test.ts +0 -342
  438. package/plugins/workflow-runner/test/output-schema.test.ts +0 -213
  439. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +0 -87
  440. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -269
  441. package/plugins/workflow-runner/test/ppt.load.test.ts +0 -1220
  442. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +0 -220
  443. package/plugins/workflow-runner/test/recovery.test.ts +0 -265
  444. package/plugins/workflow-runner/test/runner.test.ts +0 -911
  445. package/plugins/workflow-runner/test/runnerPreflight.test.ts +0 -379
  446. package/plugins/workflow-runner/test/skillTurnDone.test.ts +0 -71
  447. package/plugins/workflow-runner/test/stateLifecycle.test.ts +0 -150
  448. package/plugins/workflow-runner/test/stepPipeline.test.ts +0 -114
  449. package/plugins/workflow-runner/test/taskArbitration.test.ts +0 -338
  450. package/plugins/workflow-runner/test/taskLifecycle.test.ts +0 -270
  451. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +0 -221
  452. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +0 -29
  453. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +0 -168
  454. package/plugins/workflow-runner/test/webppt.load.test.ts +0 -250
  455. package/plugins/workflow-runner/test/word.load.test.ts +0 -208
  456. package/plugins/workflow-runner/test/workflows.smoke.test.ts +0 -76
  457. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +0 -207
  458. package/src/advisorState.js +0 -22
  459. package/src/cli/configWizard.js +0 -293
  460. package/src/tools/toolProgressBridge.js +0 -48
  461. package/workflows/adaptive-research.yaml +0 -50
  462. package/workflows/book-review-short.yaml +0 -125
  463. package/workflows/e2e-write-greet.yaml +0 -31
  464. package/workflows/illustrate.yaml +0 -227
  465. package/workflows/plan.yaml +0 -470
  466. package/workflows/ppt.yaml +0 -1670
  467. package/workflows/ralph-loop.yaml +0 -199
  468. package/workflows/schema.json +0 -176
  469. package/workflows/webppt.yaml +0 -496
  470. package/workflows/wechat-reading-shots.yaml +0 -315
  471. package/workflows/word.yaml +0 -720
  472. package/workflows/youtube-shorts.yaml +0 -202
@@ -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
+ }