minimal-agent 0.8.2 → 0.10.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 (484) hide show
  1. package/README.md +14 -12
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,204 @@
1
+ /**
2
+ * crawl.test.mjs —— web-crawler 引擎单测(DI 假 fetch/写盘,不连真网)。
3
+ * 用 bun test 跑:bun test skills/web-crawler/scripts/crawl.test.mjs
4
+ */
5
+
6
+ import { describe, expect, test } from 'bun:test';
7
+
8
+ import {
9
+ canonicalizeUrl,
10
+ crawl,
11
+ decodeEntities,
12
+ extractJsonLd,
13
+ extractLinks,
14
+ extractMainImage,
15
+ extractOpenGraph,
16
+ extractTitle,
17
+ fallbackHtmlToMarkdown,
18
+ isAllowedByRobots,
19
+ looksJsRendered,
20
+ parseRobots,
21
+ shouldFollow,
22
+ } from './crawl.mjs';
23
+
24
+ describe('抽取纯函数', () => {
25
+ test('extractTitle 去标签解实体', () => {
26
+ expect(extractTitle('<title>Hello &amp; World</title>')).toBe('Hello & World');
27
+ expect(extractTitle('<p>no title</p>')).toBe('');
28
+ });
29
+
30
+ test('extractOpenGraph property/content 任意顺序', () => {
31
+ const html =
32
+ '<meta content="T" property="og:title"><meta property="og:image" content="/i.png">';
33
+ expect(extractOpenGraph(html)).toEqual({ 'og:title': 'T', 'og:image': '/i.png' });
34
+ });
35
+
36
+ test('extractJsonLd 解析、坏块跳过', () => {
37
+ const html =
38
+ '<script type="application/ld+json">{"@type":"Article","h":1}</script>' +
39
+ '<script type="application/ld+json">{bad</script>';
40
+ expect(extractJsonLd(html)).toEqual([{ '@type': 'Article', h: 1 }]);
41
+ });
42
+
43
+ test('extractLinks 相对地址按 base 解析 + 去重 + 过滤 mailto/#', () => {
44
+ const html =
45
+ '<a href="/a">A</a><a href="b">B</a><a href="/a">dup</a>' +
46
+ '<a href="#x">hash</a><a href="mailto:x@y">m</a><a href="https://o.test/z">O</a>';
47
+ expect(extractLinks(html, 'https://s.test/dir/').sort()).toEqual(
48
+ ['https://o.test/z', 'https://s.test/a', 'https://s.test/dir/b'].sort(),
49
+ );
50
+ });
51
+
52
+ test('extractMainImage 优先 og:image', () => {
53
+ expect(
54
+ extractMainImage('<img src="/x.png">', { 'og:image': 'https://s.test/og.png' }, 'https://s.test/'),
55
+ ).toBe('https://s.test/og.png');
56
+ expect(extractMainImage('<img src="/x.png">', {}, 'https://s.test/')).toBe('https://s.test/x.png');
57
+ });
58
+
59
+ test('decodeEntities + fallbackHtmlToMarkdown', () => {
60
+ expect(decodeEntities('a&amp;b&#39;c')).toBe("a&b'c");
61
+ const md = fallbackHtmlToMarkdown(
62
+ '<head><title>x</title></head><h1>Title</h1><p>Hello <a href="/u">link</a></p><script>junk()</script>',
63
+ );
64
+ expect(md).toContain('# Title');
65
+ expect(md).toContain('[link](/u)');
66
+ expect(md).not.toContain('junk');
67
+ });
68
+ });
69
+
70
+ describe('URL 规范化 / 域名 / robots', () => {
71
+ test('canonicalizeUrl 去 hash/tracking、host 小写、去末尾斜杠', () => {
72
+ expect(canonicalizeUrl('HTTPS://Site.TEST/a/?utm_source=x&q=1#frag')).toBe(
73
+ 'https://site.test/a?q=1',
74
+ );
75
+ expect(canonicalizeUrl('https://site.test/')).toBe('https://site.test/'); // 根保留斜杠
76
+ });
77
+
78
+ test('canonicalizeUrl 去重等价:带不带 tracking/hash 归一', () => {
79
+ expect(canonicalizeUrl('https://s.test/p?utm_medium=ad')).toBe(
80
+ canonicalizeUrl('https://s.test/p#top'),
81
+ );
82
+ });
83
+
84
+ test('shouldFollow sameOrigin / allowDomains', () => {
85
+ expect(shouldFollow('https://s.test/x', { origin: 'https://s.test', sameOrigin: true })).toBe(true);
86
+ expect(shouldFollow('https://o.test/x', { origin: 'https://s.test', sameOrigin: true })).toBe(false);
87
+ expect(shouldFollow('https://sub.s.test/x', { allowDomains: ['s.test'] })).toBe(true);
88
+ expect(shouldFollow('https://o.test/x', { allowDomains: ['s.test'] })).toBe(false);
89
+ });
90
+
91
+ test('parseRobots + isAllowedByRobots', () => {
92
+ const rb = parseRobots('User-agent: *\nDisallow: /private\nCrawl-delay: 2\n# c\nDisallow: /tmp');
93
+ expect(rb.disallow).toEqual(['/private', '/tmp']);
94
+ expect(rb.crawlDelay).toBe(2);
95
+ expect(isAllowedByRobots('https://s.test/ok', rb)).toBe(true);
96
+ expect(isAllowedByRobots('https://s.test/private/x', rb)).toBe(false);
97
+ });
98
+
99
+ test('looksJsRendered:空壳 + app root → true;有正文 → false', () => {
100
+ expect(looksJsRendered('<div id="root"></div><script src="app.js"></script>')).toBe(true);
101
+ expect(
102
+ looksJsRendered(`<body><article>${'实质正文内容。'.repeat(40)}</article></body>`),
103
+ ).toBe(false);
104
+ });
105
+ });
106
+
107
+ // DI 假依赖:内存抓取 + 内存写盘
108
+ function memDeps(pages) {
109
+ const writes = new Map();
110
+ return {
111
+ writes,
112
+ deps: {
113
+ renderImpl: async () => ({ ok: false, html: '' }),
114
+ mkdirImpl: async () => {},
115
+ writeFileImpl: async (p, c) => {
116
+ writes.set(p.replace(/\\/g, '/'), c);
117
+ },
118
+ fetchImpl: async (url) => {
119
+ const body = pages[url] ?? pages[url.replace(/\/$/, '')] ?? null;
120
+ if (body == null) return { ok: false, status: 404, body: '', url };
121
+ return { ok: true, status: 200, contentType: 'text/html', body, url };
122
+ },
123
+ },
124
+ };
125
+ }
126
+
127
+ describe('crawl 引擎(DI,不连真网)', () => {
128
+ test('single 模式:1 页、产 manifest + markdown', async () => {
129
+ const { writes, deps } = memDeps({
130
+ 'https://s.test/': '<title>Home</title><h1>Home</h1><p>hi</p><a href="/a">A</a>',
131
+ });
132
+ const m = await crawl({ url: 'https://s.test/', mode: 'single', out: '/o', render: 'off' }, deps);
133
+ expect(m.mode).toBe('single');
134
+ expect(m.generated_pages).toBe(1);
135
+ expect(m.pages[0].title).toBe('Home');
136
+ expect([...writes.keys()]).toContain('/o/crawl.json');
137
+ expect([...writes.keys()].some((k) => /page_001\.md$/.test(k))).toBe(true);
138
+ });
139
+
140
+ test('site 模式:BFS + 去重 + 同域过滤 + depth/maxPages', async () => {
141
+ const { deps } = memDeps({
142
+ 'https://s.test/':
143
+ '<title>Home</title><a href="/a">A</a><a href="/b">B</a><a href="https://o.test/x">ext</a>',
144
+ 'https://s.test/a': '<title>A</title><a href="/b">B-dup</a><a href="/c">C</a>',
145
+ 'https://s.test/b': '<title>B</title><p>leaf</p>',
146
+ 'https://s.test/c': '<title>C</title><p>leaf</p>',
147
+ 'https://s.test/robots.txt': 'User-agent: *\nDisallow: /private',
148
+ });
149
+ const m = await crawl(
150
+ {
151
+ url: 'https://s.test/',
152
+ mode: 'site',
153
+ out: '/o',
154
+ depth: 2,
155
+ maxPages: 20,
156
+ sameOrigin: true,
157
+ render: 'off',
158
+ delay: 0,
159
+ },
160
+ deps,
161
+ );
162
+ const urls = m.pages.filter((p) => p.file).map((p) => p.url).sort();
163
+ expect(urls).toEqual([
164
+ 'https://s.test/',
165
+ 'https://s.test/a',
166
+ 'https://s.test/b',
167
+ 'https://s.test/c',
168
+ ]);
169
+ // 外域不爬、/b 去重(只出现一次)
170
+ expect(urls.filter((u) => u === 'https://s.test/b').length).toBe(1);
171
+ expect(urls).not.toContain('https://o.test/x');
172
+ });
173
+
174
+ test('site 模式:maxPages 截断', async () => {
175
+ const { deps } = memDeps({
176
+ 'https://s.test/': '<a href="/a">A</a><a href="/b">B</a><a href="/c">C</a>',
177
+ 'https://s.test/a': '<p>a</p>',
178
+ 'https://s.test/b': '<p>b</p>',
179
+ 'https://s.test/c': '<p>c</p>',
180
+ 'https://s.test/robots.txt': '',
181
+ });
182
+ const m = await crawl(
183
+ { url: 'https://s.test/', mode: 'site', out: '/o', depth: 2, maxPages: 2, sameOrigin: true, render: 'off', delay: 0 },
184
+ deps,
185
+ );
186
+ expect(m.generated_pages).toBe(2);
187
+ });
188
+
189
+ test('site 模式:robots Disallow 跳过', async () => {
190
+ const { deps } = memDeps({
191
+ 'https://s.test/': '<a href="/private/secret">P</a><a href="/ok">OK</a>',
192
+ 'https://s.test/ok': '<p>ok</p>',
193
+ 'https://s.test/private/secret': '<p>secret</p>',
194
+ 'https://s.test/robots.txt': 'User-agent: *\nDisallow: /private',
195
+ });
196
+ const m = await crawl(
197
+ { url: 'https://s.test/', mode: 'site', out: '/o', depth: 2, maxPages: 20, sameOrigin: true, render: 'off', delay: 0 },
198
+ deps,
199
+ );
200
+ const skipped = m.pages.find((p) => p.skipped === 'robots');
201
+ expect(skipped?.url).toBe('https://s.test/private/secret');
202
+ expect(m.pages.filter((p) => p.file).map((p) => p.url)).toContain('https://s.test/ok');
203
+ });
204
+ });
@@ -73,6 +73,10 @@ A user may ask you to create, edit, or analyze the contents of an .xlsx file. Yo
73
73
 
74
74
  **LibreOffice Required for Formula Recalculation**: You can assume LibreOffice is installed for recalculating formula values using the `scripts/recalc.py` script. The script automatically configures LibreOffice on first run, including in sandboxed environments where Unix sockets are restricted (handled by `scripts/office/soffice.py`)
75
75
 
76
+ **OPC 合规是交付硬门槛**:.xlsx 本质是 OPC zip 包,Excel 打开时做严格 OPC 校验。交付前用
77
+ `scripts/opc_check.py`(仅依赖 Python 标准库)做忠实合规校验(见 Common Workflow 第 7 步)——
78
+ **不要**只用「openpyxl 能否重新打开」判断通过,那类库太宽容会放过损坏包。
79
+
76
80
  ## Reading and analyzing data
77
81
 
78
82
  ### Data analysis with pandas
@@ -147,6 +151,14 @@ This applies to ALL calculations - totals, percentages, ratios, differences, etc
147
151
  - `#DIV/0!`: Division by zero
148
152
  - `#VALUE!`: Wrong data type in formula
149
153
  - `#NAME?`: Unrecognized formula name
154
+ 7. **OPC 合规校验(硬门槛)**: 确认产物是干净的 OPC 包,Excel 能干净打开
155
+ ```bash
156
+ python scripts/opc_check.py output.xlsx
157
+ ```
158
+ openpyxl 从零创建通常产出合规包,但 recalc(经 LibreOffice)或第三方/Bun 工具链改写过的
159
+ 文件可能引入不合规。**「某个库能 load/import ≠ Excel 能干净打开」**——openpyxl/pandas 很宽容,
160
+ 会忽略 zip 目录条目等问题照样加载成功,**绝不要**拿「openpyxl 能否重新 load」当验证通过的判据。
161
+ 退出码非 0 即不合规,按提示一步修回:`python scripts/opc_check.py output.xlsx --repack output.fixed.xlsx`。
150
162
 
151
163
  ### Creating new Excel files
152
164
 
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ opc_check.py —— OOXML(pptx/docx/xlsx 本质都是 OPC zip 包)的「忠实合规校验 + 重打包」工具。
5
+
6
+ 为什么需要这个脚本?
7
+ ====================
8
+ pptx/docx/xlsx 文件本质是一个遵循 OPC(Open Packaging Conventions, ECMA-376 第 2 部分)
9
+ 规范的 zip 包。PowerPoint / Word / Excel 打开文件时会做**严格的 OPC 校验**,任何一处不合规
10
+ 都可能被判「需要修复」、修复后内容丢失。
11
+
12
+ 但是——「某个库能 load/import ≠ 目标程序(PowerPoint/Word/Excel)能干净打开」。
13
+ python-pptx / python-docx / openpyxl 这类库非常宽容:它们会忽略 zip 里的目录条目、不在乎
14
+ [Content_Types].xml 是不是第一个条目,照样 load 成功。于是「用 python-pptx 重新打开来验证」
15
+ 会**误报通过**,让损坏的包溜过验收闸门(这正是本仓库踩过的坑:PptxGenJS 在 Bun 下用 JSZip
16
+ 产出含 19 个目录条目 + STORE 无压缩的非合规包,python-pptx 照常加载,PowerPoint 却判损坏)。
17
+
18
+ 因此本脚本**绝不依赖任何宽容的高层库**,只用标准库(zipfile / xml.etree / posixpath / re),
19
+ 直接从 zip 字节层面 + OPC 规范层面做校验,忠实于最终消费者(Office)。
20
+
21
+ 校验项(任一不合规 → 非零退出码 + 打印具体问题)
22
+ ================================================
23
+ 1. zip 里没有目录条目(条目名以 "/" 结尾的应为 0 个)。
24
+ —— Office 严格 OPC 校验不接受目录条目;JSZip/Bun 常错误地写入它们。
25
+ 2. [Content_Types].xml 是 zip 的第一个条目。
26
+ —— OPC 规范虽未强制顺序,但 Office 的快速解析依赖它在前;不在首位常触发「需要修复」。
27
+ 3. 所有 .rels 里的内部 Target(TargetMode != External)都能解析到真实存在的部件。
28
+ —— 悬空关系(指向不存在的部件)是最常见的「文件损坏」根因。
29
+ 4. 没有 0 字节的媒体文件(ppt/media/、word/media/、xl/media/ 下)。
30
+ —— 空图片 = 渲染失败 / 修复时被丢弃。
31
+ 5. 所有 .xml / .rels 都是良构(well-formed)XML。
32
+ —— 坏 XML 直接导致 Office 拒绝加载该部件。
33
+
34
+ 用法
35
+ ====
36
+ python opc_check.py <file> # 校验,合规退出 0,不合规退出 1
37
+ python opc_check.py <file> --verbose # 同上,额外打印每项 PASSED 明细
38
+ python opc_check.py --repack <in> <out> # OPC 重打包:把不合规包修回合规
39
+
40
+ 重打包(--repack)做了什么?
41
+ ===========================
42
+ 读入源包里所有**非目录**条目,按「[Content_Types].xml 第一、_rels/.rels 第二、其余其后」的
43
+ 顺序,用 DEFLATE 压缩重写一个干净 zip。这样即便生成器(如 Bun 下的 PptxGenJS)产出不合规包,
44
+ 也能一步修回合规——重打包后再跑一次校验即应全绿。
45
+ """
46
+
47
+ import argparse
48
+ import posixpath
49
+ import re
50
+ import sys
51
+ import zipfile
52
+ import xml.etree.ElementTree as ET
53
+
54
+ # OPC 关系命名空间:.rels 文件里 <Relationship> 元素所在的命名空间,固定不变。
55
+ RELATIONSHIPS_NS = "http://schemas.openxmlformats.org/package/2006/relationships"
56
+
57
+ # 三大 OOXML 家族放媒体(图片等)的目录前缀,用于「0 字节媒体」检查。
58
+ MEDIA_DIRS = ("ppt/media/", "word/media/", "xl/media/")
59
+
60
+ CONTENT_TYPES = "[Content_Types].xml"
61
+
62
+
63
+ def _normalize_part_target(rels_name: str, target: str) -> str:
64
+ """
65
+ 把一条关系里的 Target 解析成「包内绝对部件名」(不带前导 /)。
66
+
67
+ OPC 里 .rels 的 Target 是相对于「该 .rels 所描述部件所在目录」的相对路径:
68
+ - 根关系 _rels/.rels 描述的是包根 → 基准目录是包根 ""。
69
+ - 部件关系 <dir>/_rels/<part>.xml.rels 描述的是 <dir>/<part>.xml → 基准目录是 <dir>。
70
+ Target 也可能以 "/" 开头表示「相对包根的绝对路径」。
71
+ 返回归一化后的 posix 路径(用 posixpath,因为 zip 内部一律用正斜杠)。
72
+ """
73
+ if target.startswith("/"):
74
+ # 绝对路径:直接去掉前导斜杠即为部件名。
75
+ return target.lstrip("/")
76
+
77
+ # 求 .rels 的基准目录。rels_name 形如 "_rels/.rels" 或 "ppt/_rels/presentation.xml.rels"。
78
+ # 基准目录 = .rels 所在目录的父目录(因为 _rels 子目录描述的是其上一级里的部件)。
79
+ rels_dir = posixpath.dirname(rels_name) # 如 "ppt/_rels"
80
+ base_dir = posixpath.dirname(rels_dir) # 如 "ppt"(根关系时为 "")
81
+ joined = posixpath.join(base_dir, target) if base_dir else target
82
+ # posixpath.normpath 处理 "../" 等相对段,得到干净的部件名。
83
+ return posixpath.normpath(joined)
84
+
85
+
86
+ def check_compliance(path: str, verbose: bool = False) -> list:
87
+ """
88
+ 对 OOXML 文件做忠实 OPC 合规校验,返回「问题列表」(空列表 = 合规)。
89
+ 每个问题是一段人话描述的字符串,方便直接打印给用户/喂回 LLM 排障。
90
+ """
91
+ problems = []
92
+
93
+ # 先确认它确实是个 zip(OOXML 包损坏到连 zip 都不是时,这里就拦下)。
94
+ if not zipfile.is_zipfile(path):
95
+ return [f"不是合法的 zip 包(OOXML 文件本质必须是 zip):{path}"]
96
+
97
+ with zipfile.ZipFile(path, "r") as zf:
98
+ infos = zf.infolist()
99
+ # namelist 保持 zip 物理顺序,用于「第一个条目」判定。
100
+ names = [i.filename for i in infos]
101
+
102
+ # 现存的所有「真实部件」名集合(排除目录条目),供关系 Target 解析时核对存在性。
103
+ present_parts = {i.filename for i in infos if not i.filename.endswith("/")}
104
+
105
+ # ---- 校验项 1:没有目录条目 ----
106
+ dir_entries = [n for n in names if n.endswith("/")]
107
+ if dir_entries:
108
+ problems.append(
109
+ "zip 含目录条目(OPC 不允许,Office 会判损坏):"
110
+ + ", ".join(dir_entries)
111
+ )
112
+ elif verbose:
113
+ print("PASSED [1] 无目录条目")
114
+
115
+ # ---- 校验项 2:[Content_Types].xml 是第一个条目 ----
116
+ if CONTENT_TYPES not in present_parts:
117
+ problems.append(f"缺少 {CONTENT_TYPES}(OPC 必备部件)")
118
+ elif names and names[0] != CONTENT_TYPES:
119
+ problems.append(
120
+ f"{CONTENT_TYPES} 不是 zip 的第一个条目(首位是 '{names[0]}');"
121
+ "Office 严格解析依赖它在最前,可用 --repack 修复"
122
+ )
123
+ elif verbose:
124
+ print(f"PASSED [2] {CONTENT_TYPES} 位于首位")
125
+
126
+ # ---- 校验项 5(先做,因为 3 要解析 .rels XML):所有 .xml/.rels 良构 ----
127
+ # 把良构检查提前,能在解析 .rels 之前就把坏 XML 的部件挑出来,避免下面重复报错。
128
+ malformed = set()
129
+ for info in infos:
130
+ n = info.filename
131
+ if n.endswith("/"):
132
+ continue
133
+ if n.lower().endswith(".xml") or n.lower().endswith(".rels"):
134
+ try:
135
+ ET.fromstring(zf.read(n))
136
+ except ET.ParseError as e:
137
+ malformed.add(n)
138
+ problems.append(f"XML 不良构(Office 无法加载该部件):{n} —— {e}")
139
+ if verbose and not malformed:
140
+ print("PASSED [5] 所有 .xml/.rels 均为良构 XML")
141
+
142
+ # ---- 校验项 3:所有 .rels 内部 Target 都能解析到真实部件 ----
143
+ dangling = []
144
+ for info in infos:
145
+ n = info.filename
146
+ if not n.lower().endswith(".rels") or n in malformed:
147
+ continue
148
+ try:
149
+ root = ET.fromstring(zf.read(n))
150
+ except ET.ParseError:
151
+ continue # 已在校验项 5 报过
152
+ for rel in root.iter("{%s}Relationship" % RELATIONSHIPS_NS):
153
+ # 外部关系(TargetMode="External",如 URL/超链接)不要求包内存在,跳过。
154
+ if rel.get("TargetMode") == "External":
155
+ continue
156
+ target = rel.get("Target")
157
+ if not target:
158
+ continue
159
+ part = _normalize_part_target(n, target)
160
+ if part not in present_parts:
161
+ dangling.append(f"{n} → Target '{target}'(解析为部件 '{part}',不存在)")
162
+ if dangling:
163
+ problems.append(
164
+ "存在悬空关系(.rels 指向不存在的部件,Office 会判损坏):\n "
165
+ + "\n ".join(dangling)
166
+ )
167
+ elif verbose:
168
+ print("PASSED [3] 所有内部关系 Target 均指向存在的部件")
169
+
170
+ # ---- 校验项 4:没有 0 字节媒体文件 ----
171
+ empty_media = [
172
+ i.filename
173
+ for i in infos
174
+ if (not i.filename.endswith("/"))
175
+ and i.file_size == 0
176
+ and any(i.filename.startswith(d) for d in MEDIA_DIRS)
177
+ ]
178
+ if empty_media:
179
+ problems.append(
180
+ "存在 0 字节媒体文件(空图片,渲染/修复时会被丢弃):"
181
+ + ", ".join(empty_media)
182
+ )
183
+ elif verbose:
184
+ print("PASSED [4] 媒体文件均非空")
185
+
186
+ return problems
187
+
188
+
189
+ def repack(src: str, dst: str) -> None:
190
+ """
191
+ OPC 重打包:读入源包所有非目录条目,按 OPC 友好顺序用 DEFLATE 重写干净 zip。
192
+ 顺序约定:[Content_Types].xml 第一 → _rels/.rels 第二 → 其余其后(保持源相对顺序)。
193
+ """
194
+ with zipfile.ZipFile(src, "r") as zin:
195
+ # 只取真实部件,丢弃所有目录条目(这正是修复非合规包的关键一步)。
196
+ parts = [(i.filename, zin.read(i.filename)) for i in zin.infolist()
197
+ if not i.filename.endswith("/")]
198
+
199
+ def sort_key(item):
200
+ name = item[0]
201
+ if name == CONTENT_TYPES:
202
+ return (0, name) # Content_Types 永远第一
203
+ if name == "_rels/.rels":
204
+ return (1, name) # 根关系第二
205
+ return (2, name) # 其余按名次序其后
206
+
207
+ parts.sort(key=sort_key)
208
+
209
+ with zipfile.ZipFile(dst, "w", compression=zipfile.ZIP_DEFLATED) as zout:
210
+ for name, data in parts:
211
+ # 显式指定 DEFLATE,杜绝源包里 STORE(无压缩)残留——Office 对 STORE 的兼容性更差。
212
+ zout.writestr(name, data, compress_type=zipfile.ZIP_DEFLATED)
213
+
214
+
215
+ def main() -> int:
216
+ parser = argparse.ArgumentParser(
217
+ description="OOXML(pptx/docx/xlsx)忠实 OPC 合规校验 + 重打包工具(仅依赖标准库)"
218
+ )
219
+ parser.add_argument("file", help="要校验的 OOXML 文件(或 --repack 模式下的输入文件)")
220
+ parser.add_argument(
221
+ "--repack",
222
+ metavar="OUT",
223
+ help="重打包模式:把 file 修回合规并写到 OUT(例:--repack fixed.pptx)",
224
+ )
225
+ parser.add_argument("-v", "--verbose", action="store_true", help="打印每项 PASSED 明细")
226
+ args = parser.parse_args()
227
+
228
+ if args.repack:
229
+ repack(args.file, args.repack)
230
+ # 重打包后立刻自检,确保产物确实合规,否则别假装成功。
231
+ problems = check_compliance(args.repack, verbose=args.verbose)
232
+ if problems:
233
+ print(f"重打包到 {args.repack} 后仍存在 {len(problems)} 个问题:")
234
+ for p in problems:
235
+ print(" - " + p)
236
+ return 1
237
+ print(f"重打包完成且合规:{args.repack}")
238
+ return 0
239
+
240
+ problems = check_compliance(args.file, verbose=args.verbose)
241
+ if problems:
242
+ print(f"OPC 合规校验 FAILED —— {args.file} 存在 {len(problems)} 类问题:")
243
+ for p in problems:
244
+ print(" - " + p)
245
+ print("提示:可用 `python opc_check.py <in> --repack <out>` 一步修回合规。")
246
+ return 1
247
+
248
+ print(f"OPC 合规校验 PASSED —— {args.file} 是干净的 OPC 包,可被 Office 安全打开。")
249
+ return 0
250
+
251
+
252
+ if __name__ == "__main__":
253
+ sys.exit(main())
@@ -0,0 +1,22 @@
1
+ /**
2
+ * ============================================================
3
+ * src/advisorState.ts —— 顾问模型(Advisor 工具)开关状态
4
+ * ------------------------------------------------------------
5
+ * 模块级单例,镜像 askBridge.ts::setAskHandler 的简单风格——这不是按请求
6
+ * 隔离的状态(不需要 AsyncLocalStorage),TUI 单进程单会话场景下一个
7
+ * 进程内全局开关就够用。
8
+ *
9
+ * 默认 true:用户配置了 5 个 ADVISOR_AGENT_* 环境变量本身就是"我要用"的
10
+ * 信号,/advisor off 是需要时的临时关闭手段。
11
+ *
12
+ * 纯内存,不落盘——进程重启即重置为默认值(与 sessionState.ts 的
13
+ * "运行时态、不持久化"定位一致)。
14
+ * ============================================================
15
+ */
16
+ let enabled = true;
17
+ export function isAdvisorEnabled() {
18
+ return enabled;
19
+ }
20
+ export function setAdvisorEnabled(value) {
21
+ enabled = value;
22
+ }
package/src/askBridge.js CHANGED
@@ -21,7 +21,10 @@
21
21
  * 绝不挂起(保 `-p` 单行 JSON 契约 + 子进程不卡死)。
22
22
  * ============================================================
23
23
  */
24
+ import { AsyncLocalStorage } from 'node:async_hooks';
24
25
  let handler = null;
26
+ const askHandlerScope = new AsyncLocalStorage();
27
+ const askSourceScope = new AsyncLocalStorage();
25
28
  /**
26
29
  * 注册 / 注销 UI 处理器。
27
30
  * TUI 的 useChat 在 mount 时注册、unmount 时传 null 注销。
@@ -30,9 +33,79 @@ let handler = null;
30
33
  export function setAskHandler(h) {
31
34
  handler = h;
32
35
  }
36
+ /** 在某个异步调用链里临时覆盖 Ask handler(Web/Gateway 多 turn 并发宿主用)。 */
37
+ export function withAskHandler(h, fn) {
38
+ return askHandlerScope.run(h, fn);
39
+ }
40
+ /** 给嵌套 Agent 的提问附上来源,并允许执行器暂停/恢复自身超时预算。 */
41
+ export function withAskSource(source, lifecycle, fn) {
42
+ return askSourceScope.run({ source, ...lifecycle }, fn);
43
+ }
44
+ /**
45
+ * 把任意宿主 handler 包装为单活跃 FIFO 队列。队列实例必须按顶层回合创建,
46
+ * 不能做模块级单例,否则不同用户/Session 会互相阻塞。
47
+ */
48
+ export function createAskCoordinator(base, onStatus) {
49
+ const queue = [];
50
+ let active = false;
51
+ const refreshQueuedPositions = () => {
52
+ queue.forEach((entry, index) => onStatus?.({
53
+ source: entry.source,
54
+ state: 'queued',
55
+ queuePosition: index + 1,
56
+ queuedCount: queue.length,
57
+ }));
58
+ };
59
+ const pump = () => {
60
+ if (active)
61
+ return;
62
+ const entry = queue.shift();
63
+ if (!entry)
64
+ return;
65
+ active = true;
66
+ entry.abort?.();
67
+ onStatus?.({ source: entry.source, state: 'waiting', queuedCount: queue.length });
68
+ refreshQueuedPositions();
69
+ void base(entry.questions, entry.signal, entry.source, entry.control).then((answers) => {
70
+ onStatus?.({ source: entry.source, state: 'answered', queuedCount: queue.length });
71
+ entry.resolve(answers);
72
+ }, (error) => {
73
+ onStatus?.({ source: entry.source, state: 'cancelled', queuedCount: queue.length });
74
+ entry.reject(error instanceof Error ? error : new Error(String(error)));
75
+ }).finally(() => {
76
+ active = false;
77
+ pump();
78
+ });
79
+ };
80
+ return (questions, signal, source = { kind: 'main' }, control) => new Promise((resolve, reject) => {
81
+ if (signal?.aborted) {
82
+ reject(new DOMException('aborted', 'AbortError'));
83
+ return;
84
+ }
85
+ const entry = { questions, signal, source, control, resolve, reject };
86
+ const onAbort = () => {
87
+ const index = queue.indexOf(entry);
88
+ if (index < 0)
89
+ return; // active entry is cancelled by the base handler's signal
90
+ queue.splice(index, 1);
91
+ onStatus?.({ source, state: 'cancelled', queuedCount: queue.length });
92
+ reject(new DOMException('aborted', 'AbortError'));
93
+ refreshQueuedPositions();
94
+ };
95
+ if (signal) {
96
+ signal.addEventListener('abort', onAbort, { once: true });
97
+ entry.abort = () => signal.removeEventListener('abort', onAbort);
98
+ }
99
+ queue.push(entry);
100
+ if (active) {
101
+ onStatus?.({ source, state: 'queued', queuePosition: queue.length, queuedCount: queue.length });
102
+ }
103
+ pump();
104
+ });
105
+ }
33
106
  /** 是否有交互 UI 在场(测试 / 诊断用)。 */
34
107
  export function hasAskHandler() {
35
- return handler !== null;
108
+ return askHandlerScope.getStore() !== undefined || handler !== null;
36
109
  }
37
110
  /**
38
111
  * 向用户提问,返回答案。
@@ -41,11 +114,32 @@ export function hasAskHandler() {
41
114
  * - 无 handler(非交互宿主)→ 自动选每题首项(约定=推荐),stderr 注记,不挂起。
42
115
  */
43
116
  export async function requestUserChoice(questions, signal) {
44
- if (handler)
45
- return handler(questions, signal);
117
+ const scope = askSourceScope.getStore();
118
+ const scoped = askHandlerScope.getStore();
119
+ if (scoped || handler) {
120
+ scope?.onWaitStart?.();
121
+ try {
122
+ return await (scoped ?? handler)(questions, signal, scope?.source, {
123
+ cancelSource: () => scope?.onCancel?.(),
124
+ });
125
+ }
126
+ finally {
127
+ scope?.onWaitEnd?.();
128
+ }
129
+ }
46
130
  // 非交互兜底
47
131
  return questions.map((q) => {
48
132
  const pick = q.options[0]?.label ?? '';
133
+ // 约定校验:askBridge.ts:25 JSDoc 说"把推荐项放在第一个,并在 label 末尾标「(推荐)」。
134
+ // 若首项 label 不含 "(推荐)",stderr 注记一下——上游作者违反约定
135
+ // (用户被自动套上非推荐路径是糟糕的体验)
136
+ const firstOpt = q.options[0];
137
+ if (firstOpt && !firstOpt.label.includes('(推荐)')) {
138
+ try {
139
+ process.stderr.write(`[AskUserQuestion·非交互] "${q.header}" 首项 label "${firstOpt.label}" 不含「(推荐)」标记——非交互宿主会按首项兜底,违反 promptAssembly 里的提问克制约定\n`);
140
+ }
141
+ catch { /* ignore */ }
142
+ }
49
143
  try {
50
144
  process.stderr.write(`[AskUserQuestion·非交互] "${q.header}" 无 UI,自动选推荐项:"${pick}"\n`);
51
145
  }
@@ -11,8 +11,10 @@
11
11
  * - 环境变量覆盖 MINIMAL_AGENT_CWD:测试 / docker / 显式 cd 场景
12
12
  * ============================================================
13
13
  */
14
+ import { AsyncLocalStorage } from 'node:async_hooks';
14
15
  import { resolve } from 'node:path';
15
16
  let _workingDir = null;
17
+ const workingDirScope = new AsyncLocalStorage();
16
18
  /** 首次调用锁定 cwd(解析 MINIMAL_AGENT_CWD),重复调用幂等。 */
17
19
  export function initWorkingDir() {
18
20
  if (_workingDir !== null)
@@ -23,7 +25,16 @@ export function initWorkingDir() {
23
25
  }
24
26
  /** 读取已锁定的工作目录;尚未 init 时退到 lazy init(主要给测试)。 */
25
27
  export function getWorkingDir() {
26
- return _workingDir ?? initWorkingDir();
28
+ return workingDirScope.getStore() ?? _workingDir ?? initWorkingDir();
29
+ }
30
+ /** TUI idle-only workspace switch. Callers must reject switching during a turn. */
31
+ export function setWorkingDir(cwd) {
32
+ _workingDir = resolve(cwd);
33
+ return _workingDir;
34
+ }
35
+ /** 在同一进程的某个异步调用链里临时覆盖工作目录(HTTP/Gateway 多工作区宿主用)。 */
36
+ export function withWorkingDir(cwd, fn) {
37
+ return workingDirScope.run(resolve(cwd), fn);
27
38
  }
28
39
  /** 仅供测试:重置 module-level state */
29
40
  export function _resetWorkingDir() {