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,93 @@
1
+ import { realpath } from 'node:fs/promises';
2
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
3
+ const active = [];
4
+ const waiters = [];
5
+ let running = 0;
6
+ function overlaps(a, b) {
7
+ return a === b || a.startsWith(`${b}${sep}`) || b.startsWith(`${a}${sep}`);
8
+ }
9
+ function conflicts(cwd, scopes) {
10
+ return active.some((lease) => lease.cwd === cwd && lease.scopes.some((left) => scopes.some((right) => overlaps(left, right))));
11
+ }
12
+ function publishQueue() {
13
+ waiters.forEach((waiter, index) => waiter.onStatus?.({
14
+ phase: 'queued',
15
+ queuePosition: index + 1,
16
+ activeCount: running,
17
+ maxConcurrent: waiter.maxConcurrent,
18
+ }));
19
+ }
20
+ function drain() {
21
+ for (let i = 0; i < waiters.length;) {
22
+ const waiter = waiters[i];
23
+ if (running >= waiter.maxConcurrent) {
24
+ i++;
25
+ continue;
26
+ }
27
+ if (conflicts(waiter.cwd, waiter.scopes)) {
28
+ i++;
29
+ continue;
30
+ }
31
+ waiters.splice(i, 1);
32
+ if (waiter.onAbort)
33
+ waiter.signal?.removeEventListener('abort', waiter.onAbort);
34
+ running++;
35
+ const lease = { cwd: waiter.cwd, scopes: waiter.scopes };
36
+ active.push(lease);
37
+ waiter.onStatus?.({ phase: 'acquired', activeCount: running, maxConcurrent: waiter.maxConcurrent });
38
+ waiter.resolve(() => {
39
+ const index = active.indexOf(lease);
40
+ if (index >= 0)
41
+ active.splice(index, 1);
42
+ running = Math.max(0, running - 1);
43
+ drain();
44
+ });
45
+ }
46
+ publishQueue();
47
+ }
48
+ export async function normalizeWriteScopes(cwd, rawScopes) {
49
+ const lexicalRoot = resolve(cwd);
50
+ const physicalRoot = await realpath(lexicalRoot).catch(() => lexicalRoot);
51
+ if (!rawScopes?.length)
52
+ return [physicalRoot];
53
+ const normalized = new Set();
54
+ for (const raw of rawScopes) {
55
+ const value = raw.trim();
56
+ if (!value || isAbsolute(value))
57
+ throw new Error(`write_scopes 必须是工作目录内的相对路径:${raw}`);
58
+ const lexical = resolve(lexicalRoot, value);
59
+ const rel = relative(lexicalRoot, lexical);
60
+ if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel))
61
+ throw new Error(`write_scopes 越过工作目录:${raw}`);
62
+ const physical = await realpath(lexical).catch(async () => {
63
+ const parent = await realpath(resolve(lexical, '..')).catch(() => physicalRoot);
64
+ return resolve(parent, lexical.split(sep).at(-1) ?? '');
65
+ });
66
+ const physicalRel = relative(physicalRoot, physical);
67
+ if (physicalRel === '..' || physicalRel.startsWith(`..${sep}`) || isAbsolute(physicalRel))
68
+ throw new Error(`write_scopes 经过符号链接越过工作目录:${raw}`);
69
+ normalized.add(physical);
70
+ }
71
+ return [...normalized].sort();
72
+ }
73
+ export async function acquireSubagentLease(cwd, scopes, maxConcurrent = 3, options = {}) {
74
+ const safeMax = Math.max(1, Math.min(8, maxConcurrent));
75
+ return new Promise((resolveLease, reject) => {
76
+ if (options.signal?.aborted)
77
+ return reject(new DOMException('Aborted', 'AbortError'));
78
+ const waiter = { cwd, scopes, maxConcurrent: safeMax, onStatus: options.onStatus, signal: options.signal, resolve: resolveLease, reject };
79
+ waiter.onAbort = () => {
80
+ const index = waiters.indexOf(waiter);
81
+ if (index >= 0)
82
+ waiters.splice(index, 1);
83
+ publishQueue();
84
+ reject(new DOMException('Aborted', 'AbortError'));
85
+ };
86
+ options.signal?.addEventListener('abort', waiter.onAbort, { once: true });
87
+ waiters.push(waiter);
88
+ // 不在这里 publishQueue:立即拿到租约的 waiter 会被 drain 直接报 'acquired',
89
+ // 抢先报一次 'queued' 会让每个子任务都闪一下幻影「排队中」;真排队者由 drain
90
+ // 结尾的 publishQueue 覆盖。
91
+ drain();
92
+ });
93
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * ============================================================
3
+ * src/tools/agent/worktree.ts —— 子 agent 的 git worktree 隔离助手
4
+ * ------------------------------------------------------------
5
+ * 搬自 Claude Code 官方 utils/worktree.ts 的核心路径(createAgentWorktree
6
+ * 那条**无全局状态**的路径——并行子 agent 不能共享单例 session 状态),
7
+ * 砍掉 settings/hooks/tmux/sparse-checkout/symlink 等锦上添花件。
8
+ *
9
+ * 保留的官方实战经验(每条都对应真实事故教训):
10
+ * - slug 白名单校验:slug 会被拼进目录路径与分支名,防路径穿越。
11
+ * - `git worktree add -B`(大写 B):上次目录被删但分支残留时强制重置,
12
+ * 免去额外的 git branch -D 前置清理。
13
+ * - fail-closed 改动判定:任何 git 命令失败一律视为「有改动」,宁可多留
14
+ * 一个 worktree,绝不误删并行子 agent 的工作成果。
15
+ * - 清理序列:worktree remove --force → 等 100ms 锁释放 → branch -D;
16
+ * 清理失败只记日志不抛——绝不让清理问题卡死主流程。
17
+ * - GIT_TERMINAL_PROMPT=0 + stdin ignore:防 git/ssh 弹交互式凭证输入挂死。
18
+ *
19
+ * 目录选址 `<gitRoot>/.agent-worktrees/`:刻意避开 `.minimal-agent*` 保留前缀
20
+ * (/new 会按前缀整目录 rm -rf,直接删 worktree 目录会留下悬空的 git 注册项);
21
+ * 首次创建时追加进 `<gitdir>/info/exclude`(仓库本地 ignore,不污染共享 .gitignore)。
22
+ * ============================================================
23
+ */
24
+ import { spawn } from 'node:child_process';
25
+ import { appendFile, readFile, writeFile, unlink } from 'node:fs/promises';
26
+ import { join } from 'node:path';
27
+ import { getWorkingDir } from '../../bootstrap/workingDir.js';
28
+ import { dlog } from '../../utils/debugLog.js';
29
+ const WORKTREES_DIR = '.agent-worktrees';
30
+ /** 跑一条 git 命令(防挂死 env + stdin ignore),spawn 失败也归一成 ok:false。 */
31
+ async function runGit(args, cwd) {
32
+ return new Promise((resolve) => {
33
+ const child = spawn('git', args, {
34
+ cwd,
35
+ stdio: ['ignore', 'pipe', 'pipe'],
36
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: '' },
37
+ });
38
+ let stdout = '';
39
+ let stderr = '';
40
+ child.stdout.on('data', (d) => { stdout += d.toString('utf8'); });
41
+ child.stderr.on('data', (d) => { stderr += d.toString('utf8'); });
42
+ child.on('error', (e) => resolve({ ok: false, stdout, stderr: String(e) }));
43
+ child.on('close', (code) => resolve({ ok: code === 0, stdout: stdout.trim(), stderr: stderr.trim() }));
44
+ });
45
+ }
46
+ /** slug 白名单(官方同款语义):会拼进路径和分支名,只放行安全字符。 */
47
+ export function validateSlug(slug) {
48
+ return /^[a-zA-Z0-9._-]{1,64}$/.test(slug) && slug !== '.' && slug !== '..';
49
+ }
50
+ /** 把 worktree 目录加进仓库本地 ignore(best-effort,失败静默——只是降噪)。 */
51
+ async function ensureLocalExclude(cwd) {
52
+ try {
53
+ const r = await runGit(['rev-parse', '--path-format=absolute', '--git-common-dir'], cwd);
54
+ if (!r.ok || !r.stdout)
55
+ return;
56
+ const excludePath = join(r.stdout, 'info', 'exclude');
57
+ const line = `${WORKTREES_DIR}/`;
58
+ const existing = await readFile(excludePath, 'utf8').catch(() => '');
59
+ if (existing.split(/\r?\n/).includes(line))
60
+ return;
61
+ await appendFile(excludePath, `${existing.endsWith('\n') || existing === '' ? '' : '\n'}${line}\n`);
62
+ }
63
+ catch {
64
+ /* ignore 失败只影响 git status 观感,不影响功能 */
65
+ }
66
+ }
67
+ /**
68
+ * 在当前仓库创建一个子 agent 专用 worktree(基于 HEAD 的独立分支)。
69
+ * 非 git 仓库 / 无提交仓库返回 ok:false——把决策权交回主 agent(降级无隔离或串行)。
70
+ */
71
+ export async function createAgentWorktree(slug, fromCwd = getWorkingDir()) {
72
+ if (!validateSlug(slug)) {
73
+ return { ok: false, error: `非法的 worktree 名称「${slug}」(只允许字母数字._-,≤64 字符)` };
74
+ }
75
+ const root = await runGit(['rev-parse', '--show-toplevel'], fromCwd);
76
+ if (!root.ok || !root.stdout) {
77
+ return { ok: false, error: '当前目录不是 git 仓库,无法创建 worktree 隔离' };
78
+ }
79
+ const gitRoot = root.stdout;
80
+ const head = await runGit(['rev-parse', 'HEAD'], gitRoot);
81
+ if (!head.ok || !head.stdout) {
82
+ return { ok: false, error: '仓库还没有任何提交(HEAD 不存在),无法创建 worktree——先 git commit 一次' };
83
+ }
84
+ const baseSha = head.stdout;
85
+ const path = join(gitRoot, WORKTREES_DIR, slug);
86
+ const branch = `agent/${slug}`;
87
+ await ensureLocalExclude(gitRoot);
88
+ const add = await runGit(['worktree', 'add', '-B', branch, path, 'HEAD'], gitRoot);
89
+ if (!add.ok) {
90
+ return { ok: false, error: `git worktree add 失败:${add.stderr || add.stdout}` };
91
+ }
92
+ return { ok: true, path, branch, baseSha, gitRoot };
93
+ }
94
+ /**
95
+ * worktree 相对基线是否有改动(未提交文件 或 新增提交)。
96
+ * fail-closed:任何 git 命令失败一律返回 true(有改动)——宁多留不误删。
97
+ */
98
+ export async function hasWorktreeChanges(path, baseSha) {
99
+ const status = await runGit(['status', '--porcelain'], path);
100
+ if (!status.ok)
101
+ return true;
102
+ if (status.stdout.length > 0)
103
+ return true;
104
+ const revList = await runGit(['rev-list', '--count', `${baseSha}..HEAD`], path);
105
+ if (!revList.ok)
106
+ return true;
107
+ return Number.parseInt(revList.stdout, 10) > 0;
108
+ }
109
+ /** 相对基线的 diff 摘要(best-effort,失败返回空串)——给主 agent 收口审阅用。 */
110
+ export async function worktreeDiffStat(path, baseSha) {
111
+ const diff = await runGit(['diff', '--stat', baseSha], path);
112
+ const parts = [];
113
+ if (diff.ok && diff.stdout)
114
+ parts.push(diff.stdout);
115
+ // diff --stat 看不到未跟踪的新文件,用 status 补上
116
+ const status = await runGit(['status', '--porcelain'], path);
117
+ if (status.ok && status.stdout) {
118
+ const untracked = status.stdout
119
+ .split('\n')
120
+ .filter((l) => l.startsWith('??'))
121
+ .map((l) => ` 新文件(未跟踪): ${l.slice(3)}`);
122
+ if (untracked.length > 0)
123
+ parts.push(untracked.join('\n'));
124
+ }
125
+ return parts.join('\n');
126
+ }
127
+ /**
128
+ * 把 worktree 里的未提交改动确定性提交到临时分支(幂等:无改动直接 ok)。
129
+ * 为什么需要:子 agent 用 Edit/Write 改文件后通常**不会** git commit——
130
+ * 分支上没有提交的话,主 agent 收口时 `git merge` 合不到任何东西(改动只活在
131
+ * worktree 工作区里)。收尾时由引擎代提交,不依赖模型自觉。
132
+ * -c 内联身份兜底:机器没配 user.name/email 也能提交(squash 合并时最终
133
+ * 提交人仍是用户自己,临时分支上的作者名无所谓)。
134
+ */
135
+ export async function commitWorktreeChanges(path, message) {
136
+ const status = await runGit(['status', '--porcelain'], path);
137
+ if (status.ok && status.stdout.length === 0)
138
+ return { ok: true };
139
+ if (status.ok) {
140
+ const sensitive = status.stdout.split('\n').map((line) => line.slice(3).trim()).filter(isSensitiveWorktreePath);
141
+ if (sensitive.length > 0) {
142
+ return { ok: false, error: `拒绝自动提交敏感文件:${sensitive.join(', ')}` };
143
+ }
144
+ }
145
+ const add = await runGit(['add', '-A'], path);
146
+ if (!add.ok)
147
+ return { ok: false, error: add.stderr || add.stdout };
148
+ const commit = await runGit([
149
+ '-c', 'user.name=minimal-agent',
150
+ '-c', 'user.email=agent@minimal-agent.local',
151
+ 'commit', '-m', message,
152
+ ], path);
153
+ if (!commit.ok)
154
+ return { ok: false, error: commit.stderr || commit.stdout };
155
+ return { ok: true };
156
+ }
157
+ function isSensitiveWorktreePath(path) {
158
+ const normalized = path.replaceAll('\\', '/').toLowerCase();
159
+ const base = normalized.split('/').at(-1) ?? normalized;
160
+ return base === '.env' || base.startsWith('.env.') || normalized.includes('/.ssh/') ||
161
+ /(^|\/)(credentials?|secrets?)(\.|\/|$)/.test(normalized) ||
162
+ /\.(pem|key|p12|pfx|jks|keystore)$/.test(base) || base === 'id_rsa' || base === 'id_ed25519';
163
+ }
164
+ export async function worktreeChangedFiles(path, baseSha) {
165
+ const result = await runGit(['diff', '--name-only', baseSha], path);
166
+ const status = await runGit(['status', '--porcelain'], path);
167
+ const files = new Set();
168
+ if (result.ok)
169
+ for (const row of result.stdout.split('\n'))
170
+ if (row.trim())
171
+ files.add(row.trim());
172
+ if (status.ok)
173
+ for (const row of status.stdout.split('\n'))
174
+ if (row.length > 3)
175
+ files.add(row.slice(3).trim());
176
+ return [...files].sort();
177
+ }
178
+ /**
179
+ * 清理 worktree 目录 + 临时分支。尽力而为:失败只记 debug 日志,不抛——
180
+ * 清理残留可由用户随时 `git worktree remove` / `git branch -D` 手动处理。
181
+ */
182
+ export async function removeAgentWorktree(path, branch, gitRoot) {
183
+ const rm = await runGit(['worktree', 'remove', '--force', path], gitRoot);
184
+ if (!rm.ok)
185
+ dlog('agent', 'worktree remove 失败', { path, err: rm.stderr });
186
+ // 官方经验:remove 后立即 branch -D 偶发撞锁,等 100ms 释放
187
+ await new Promise((r) => setTimeout(r, 100));
188
+ const del = await runGit(['branch', '-D', branch], gitRoot);
189
+ if (!del.ok)
190
+ dlog('agent', 'worktree 分支删除失败', { branch, err: del.stderr });
191
+ }
192
+ async function resolveManagedWorktree(cwd, id) {
193
+ if (!validateSlug(id))
194
+ return { ok: false, error: '非法的子任务 ID' };
195
+ const root = await runGit(['rev-parse', '--show-toplevel'], cwd);
196
+ if (!root.ok || !root.stdout)
197
+ return { ok: false, error: '当前目录不是 Git 仓库' };
198
+ const path = join(root.stdout, WORKTREES_DIR, id);
199
+ const branch = `agent/${id}`;
200
+ const base = await runGit(['merge-base', branch, 'HEAD'], root.stdout);
201
+ if (!base.ok || !base.stdout)
202
+ return { ok: false, error: '未找到该隔离子任务' };
203
+ return { ok: true, path, branch, baseSha: base.stdout, gitRoot: root.stdout };
204
+ }
205
+ export async function inspectAgentWorktree(cwd, id) {
206
+ const wt = await resolveManagedWorktree(cwd, id);
207
+ if (!wt.ok)
208
+ return { error: wt.error };
209
+ const diff = await runGit(['diff', '--binary', wt.baseSha, wt.branch], wt.gitRoot);
210
+ if (!diff.ok)
211
+ return { error: diff.stderr || '读取差异失败' };
212
+ const status = await runGit(['status', '--short'], wt.path);
213
+ const pending = status.ok && status.stdout
214
+ ? `\n\n尚未提交的隔离工作区改动(不会自动应用):\n${status.stdout}`
215
+ : '';
216
+ return {
217
+ id,
218
+ branch: wt.branch,
219
+ baseSha: wt.baseSha,
220
+ diff: `${diff.stdout}${pending}`.trim(),
221
+ changedFiles: await worktreeChangedFiles(wt.path, wt.baseSha),
222
+ };
223
+ }
224
+ export async function applyAgentWorktree(cwd, id) {
225
+ const wt = await resolveManagedWorktree(cwd, id);
226
+ if (!wt.ok)
227
+ return wt;
228
+ const worktreeStatus = await runGit(['status', '--porcelain'], wt.path);
229
+ if (!worktreeStatus.ok)
230
+ return { ok: false, error: '无法确认隔离工作区状态,已停止自动应用' };
231
+ if (worktreeStatus.stdout) {
232
+ return {
233
+ ok: false,
234
+ error: '隔离工作区仍有未提交改动(可能包含被安全策略拒绝提交的文件),为防止丢失,不能自动应用或清理',
235
+ };
236
+ }
237
+ const head = await runGit(['rev-parse', 'HEAD'], wt.gitRoot);
238
+ if (!head.ok || head.stdout !== wt.baseSha)
239
+ return { ok: false, error: '当前 HEAD 已变化,不能安全应用;请先人工审阅和同步分支' };
240
+ const diff = await runGit(['diff', '--binary', wt.baseSha, wt.branch], wt.gitRoot);
241
+ if (!diff.ok)
242
+ return { ok: false, error: diff.stderr || '生成差异失败' };
243
+ if (!diff.stdout) {
244
+ await removeAgentWorktree(wt.path, wt.branch, wt.gitRoot);
245
+ return { ok: true };
246
+ }
247
+ const patchFile = join(wt.gitRoot, '.git', `minimal-agent-${process.pid}-${Date.now()}.patch`);
248
+ try {
249
+ // runGit 会 trim 文本输出;git apply 要求补丁保留终止换行。
250
+ await writeFile(patchFile, `${diff.stdout}\n`, { mode: 0o600 });
251
+ const check = await runGit(['apply', '--check', patchFile], wt.gitRoot);
252
+ if (!check.ok)
253
+ return { ok: false, error: `变更检查失败:${check.stderr || check.stdout}` };
254
+ const apply = await runGit(['apply', patchFile], wt.gitRoot);
255
+ if (!apply.ok)
256
+ return { ok: false, error: `应用变更失败:${apply.stderr || apply.stdout}` };
257
+ await removeAgentWorktree(wt.path, wt.branch, wt.gitRoot);
258
+ return { ok: true };
259
+ }
260
+ finally {
261
+ await unlink(patchFile).catch(() => { });
262
+ }
263
+ }
264
+ export async function discardAgentWorktree(cwd, id) {
265
+ const wt = await resolveManagedWorktree(cwd, id);
266
+ if (!wt.ok)
267
+ return wt;
268
+ await removeAgentWorktree(wt.path, wt.branch, wt.gitRoot);
269
+ return { ok: true };
270
+ }
@@ -46,6 +46,8 @@ const inputSchema = z.object({
46
46
  questions: z
47
47
  .array(questionSchema)
48
48
  .min(1)
49
+ .max(4) // 与工具描述/UI 上限硬对齐:超发在参数校验层被拒(模型拿 Zod 错误自纠),
50
+ // 避免「TUI 静默截断到 4 题 vs 非交互全部自动作答」的行为分叉
49
51
  .describe('1-4 个问题(通常 1 个;确需一次问多件事时才多个)'),
50
52
  });
51
53
  // ---------------- 2. JSON Schema(由 Zod 自动派生) ----------------
@@ -72,8 +74,10 @@ async function call(input, signal) {
72
74
  return { ok: true, content: lines.join('\n').trimEnd() };
73
75
  }
74
76
  catch (e) {
75
- // 用户 ESC / Ctrl+C 取消提问 → 当普通工具失败喂回(主循环让模型换思路;
76
- // workflow tool 步骤则按 onError 处理)。
77
+ // 提问被 reject → 当普通工具失败喂回。注意:TUI 里 ESC 经 App 的
78
+ // onCancel={chat.abort} 中断**整轮**(喂回后 loop 见 signal.aborted 收口
79
+ // interrupted,模型不会继续);「让模型换思路」只在宿主 reject 单个提问
80
+ // 而不中断整轮时成立(workflow onError / 非 TUI 宿主自定义 handler)。
77
81
  return { ok: false, error: `提问被取消 / 中断:${e.message}` };
78
82
  }
79
83
  }
@@ -86,5 +90,6 @@ export const askUserQuestionTool = {
86
90
  isReadOnly: true,
87
91
  isConcurrencySafe: false,
88
92
  maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
93
+ interruptBehavior: () => 'cancel',
89
94
  call,
90
95
  };
@@ -41,15 +41,63 @@
41
41
  * ============================================================
42
42
  */
43
43
  import { spawn } from 'node:child_process';
44
+ import { existsSync } from 'node:fs';
45
+ import { join } from 'node:path';
44
46
  import { z } from 'zod';
45
47
  import { getWorkingDir } from '../../bootstrap/workingDir.js';
48
+ import { sanitizeCredentialEnv } from '../../config/configService.js';
46
49
  import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
50
+ import { killTree } from '../shared/killTree.js';
47
51
  import { toToolParameters } from '../../utils/zodToJson.js';
48
52
  import { interpretCommandResult } from './semantics.js';
49
53
  import { scanDestructiveCommand } from './warnings.js';
50
54
  // ---------------- 0. 常量 ----------------
51
55
  const DEFAULT_TIMEOUT_MS = 120_000; // 2 min
52
56
  const MAX_TIMEOUT_MS = 600_000; // 10 min
57
+ // ---------------- 0b. Windows shell 探测(模块加载时一次性) ----------------
58
+ //
59
+ // 根因(2026-06-12 实跑事故):本工具 description 宣称 bash,但 win32 下
60
+ // spawn({shell:true}) 实际是 cmd.exe —— 模型写的 `mkdir -p doc/scripts`(完全正确的
61
+ // bash 语法)被 cmd 直接报语法错误;且中文 Windows 下 cmd 错误输出是 GBK 编码、
62
+ // 被 setEncoding('utf8') 解码成乱码,模型既看不懂错误也换不对路,瞎试几轮后停摆,
63
+ // 工作流执行步因此在第一条命令上整体卡死。
64
+ //
65
+ // 对策(三层):
66
+ // ① 优先探测真 bash(Git Bash)——POSIX 语法直接成立,description 不再撒谎;
67
+ // 排除 System32\bash.exe(WSL:跑在 Linux 环境,cwd/路径语义完全不同,不可用)。
68
+ // ② 找不到才回落 cmd,命令前缀 `chcp 65001 >nul & ` 强制 UTF-8 输出(根治乱码)。
69
+ // ③ description 运行时声明实际 shell(见下),模型按真实语法写命令。
70
+ // 可用 MINIMAL_AGENT_BASH_PATH 显式指定(绿色版 U 盘机器上 Git 安装位置不可预知)。
71
+ function detectWindowsBash() {
72
+ if (process.platform !== 'win32')
73
+ return null;
74
+ const explicit = process.env.MINIMAL_AGENT_BASH_PATH;
75
+ if (explicit && existsSync(explicit))
76
+ return explicit;
77
+ const pf = process.env.ProgramFiles ?? 'C:\\Program Files';
78
+ const pf86 = process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)';
79
+ const local = process.env.LOCALAPPDATA ?? '';
80
+ const candidates = [
81
+ join(pf, 'Git', 'bin', 'bash.exe'),
82
+ join(pf, 'Git', 'usr', 'bin', 'bash.exe'),
83
+ join(pf86, 'Git', 'bin', 'bash.exe'),
84
+ local ? join(local, 'Programs', 'Git', 'bin', 'bash.exe') : '',
85
+ ].filter(Boolean);
86
+ for (const c of candidates) {
87
+ if (existsSync(c))
88
+ return c;
89
+ }
90
+ // PATH 兜底扫描(跳过 System32 的 WSL bash)
91
+ for (const dir of (process.env.PATH ?? '').split(';')) {
92
+ if (!dir || /\\system32\\?/i.test(dir))
93
+ continue;
94
+ const p = join(dir, 'bash.exe');
95
+ if (existsSync(p))
96
+ return p;
97
+ }
98
+ return null;
99
+ }
100
+ const WIN_BASH = detectWindowsBash();
53
101
  /**
54
102
  * G1:stdout / stderr 即时缓冲上限(各自)。命令狂输出(如 cat 大文件 / yes)时,
55
103
  * 在最终统一截断之前先把内存封顶,防 OOM。取最终展示截断阈值的 2 倍 —— 正常输出不受影响,
@@ -99,8 +147,15 @@ const FORBIDDEN_PATTERNS = [
99
147
  reason: '禁止重定向到裸磁盘设备',
100
148
  },
101
149
  // ---- 关机 / 重启 ----
150
+ // 只匹配「命令位置」的关机词:串首 / 换行 / ; & | ` ( { / $( / sudo·doas·exec·nohup / systemctl 之后。
151
+ // 事故(2026-07-02):此前 /\b(?:shutdown|…)\b/ 对整段命令裸词扫描(注释也算),
152
+ // ppt.yaml stage_export 的 bash 注释「硬门 halt」被误杀 → 整步执行前被拒 →
153
+ // onError:halt → 确定性死端(resume 原样命令原样再拒,永久卡死)。
154
+ // "halt" 是工作流引擎自身词汇(onError: halt),注释/echo 文案里出现属正常写作。
155
+ // 已知窄化(防猪队友层可接受):env/timeout 等间接调用不再拦;注释里 ASCII "; halt" 仍会误伤
156
+ // (workflows 全库有 bashBlacklist.sweep.test.ts 兜底扫描)。
102
157
  {
103
- pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/i,
158
+ pattern: /(?:^|[\n;&|`({]|\$\(|\b(?:sudo|doas|exec|nohup)\s+|\bsystemctl\s+)\s*(?:shutdown|reboot|halt|poweroff)\b/i,
104
159
  reason: '禁止关机/重启命令',
105
160
  },
106
161
  {
@@ -161,12 +216,21 @@ export function checkForbidden(command) {
161
216
  * 导出供测试断言。
162
217
  */
163
218
  export function sanitizedChildEnv(base = process.env) {
164
- const env = { ...base };
219
+ const env = sanitizeCredentialEnv(base);
165
220
  for (const key of Object.keys(env)) {
166
221
  if (key.startsWith('MINIMAL_AGENT_') || key.startsWith('TAVILY_')) {
167
222
  delete env[key];
168
223
  }
169
224
  }
225
+ // 保证子进程里的 Python 用 UTF-8 解码(PEP 540 UTF-8 Mode)。否则中文 Windows 上
226
+ // locale.getpreferredencoding() = cp936/GBK,`subprocess.run(text=True)` / `open()`
227
+ // 默认就用 GBK 解码子进程的 UTF-8 输出 → 撞非法字节必崩(曾致 ppt 工作流 build_probe
228
+ // `UnicodeDecodeError: 'gbk' codec can't decode byte 0xa8` 直接 halt 整条工作流)。
229
+ // 这是 Bash 工具「保证 UTF-8 世界」既有立场(cmd 回落已 chcp 65001)的加性延伸:
230
+ // 一处覆盖所有 bash 步 spawn 的 python(内联 heredoc + 技能脚本),现在与将来。
231
+ // 跨平台安全:类 Unix 本就 UTF-8 → no-op;顺带治 POSIX/C locale(ASCII) 同类坑。
232
+ env.PYTHONUTF8 = '1';
233
+ env.PYTHONIOENCODING = 'utf-8';
170
234
  return env;
171
235
  }
172
236
  // ---------------- 1. Zod 输入 schema ----------------
@@ -245,7 +309,21 @@ The following command patterns are blocked at the tool level and will fail befor
245
309
 
246
310
  If you have a legitimate use case that requires one of the above patterns, ask the user to run the command themselves in their terminal — do not try to bypass the check.
247
311
 
248
- Separately, Bash scans for common destructive-but-recoverable patterns (\`git reset --hard\`, \`git push -f\` / \`--force\` / \`--force-with-lease\`, \`git checkout .\`, \`git restore .\`, \`git clean -f\`, \`git stash drop/clear\`, \`git branch -D\`, \`git commit --amend\` / \`--no-verify\`, \`rm -rf <path>\`, \`DROP TABLE\`, \`TRUNCATE\`, \`DELETE FROM\`, \`kubectl delete\`, \`terraform destroy\`, etc.) and prepends a \`⚠️ 警告:\` line to the output. These commands are NOT blocked — the warning is informational. Treat it as a signal to double-check intent and surface the warning to the user when relevant.`;
312
+ Separately, Bash scans for common destructive-but-recoverable patterns (\`git reset --hard\`, \`git push -f\` / \`--force\` / \`--force-with-lease\`, \`git checkout .\`, \`git restore .\`, \`git clean -f\`, \`git stash drop/clear\`, \`git branch -D\`, \`git commit --amend\` / \`--no-verify\`, \`rm -rf <path>\`, \`DROP TABLE\`, \`TRUNCATE\`, \`DELETE FROM\`, \`kubectl delete\`, \`terraform destroy\`, etc.) and prepends a \`⚠️ 警告:\` line to the output. These commands are NOT blocked — the warning is informational. Treat it as a signal to double-check intent and surface the warning to the user when relevant.${process.platform !== 'win32'
313
+ ? ''
314
+ : WIN_BASH
315
+ ? `
316
+
317
+ # Shell (this machine)
318
+ Commands run in a real bash (Git Bash at ${WIN_BASH}). POSIX syntax works: \`mkdir -p\`, \`$VAR\`, \`$(...)\`, \`&&\`, \`[ -f x ]\` are all fine. Note the working directory is a Windows path — both \`D:\\path\` and \`/d/path\` forms work in this bash.`
319
+ : `
320
+
321
+ # Shell (this machine) — IMPORTANT
322
+ No bash found on this Windows machine: commands run in cmd.exe (UTF-8 enforced). POSIX syntax does NOT work here:
323
+ - NO \`mkdir -p\` — cmd's \`mkdir a\\b\\c\` creates intermediate directories by default; better: the Write tool auto-creates parent directories
324
+ - NO \`$VAR\` / \`$(...)\` / \`[ -f x ]\` / \`export\` — use \`%VAR%\`, \`if exist\`, \`set\`
325
+ - Use \`dir\` / \`copy\` / \`del\` / \`type\` instead of \`ls\` / \`cp\` / \`rm\` / \`cat\`
326
+ \`&&\` and \`;\`-style chaining: \`&&\` works in cmd; \`;\` does not (use \`&\`).`}`;
249
327
  // ---------------- 4. call() —— 真正干活 ----------------
250
328
  async function call(input, signal) {
251
329
  const command = input.command;
@@ -261,9 +339,10 @@ async function call(input, signal) {
261
339
  // 4a-2. 破坏性命令告警扫描(仅警告,不阻塞)
262
340
  // 命中后把警告挂到结果输出顶部,让模型/用户看见苗头。
263
341
  const destructiveWarning = scanDestructiveCommand(command);
264
- // 4b. spawn —— shell:true 让平台自己挑 shell
265
- // Unix: /bin/sh -c <command>
266
- // Windows: cmd.exe /d /s /c "<command>"
342
+ // 4b. spawn —— 平台分派(见 0b 探测注释):
343
+ // Unix: /bin/sh -c <command>(shell:true)
344
+ // Windows + Git Bash: bash.exe -c <command>(POSIX 语法成立)
345
+ // Windows 无 bash: cmd.exe,命令前缀 chcp 65001 强制 UTF-8 输出
267
346
  let stdout = '';
268
347
  let stderr = '';
269
348
  let exitCode = null;
@@ -279,13 +358,32 @@ async function call(input, signal) {
279
358
  }, timeoutMs);
280
359
  try {
281
360
  await new Promise((resolveP, rejectP) => {
282
- const child = spawn(command, {
283
- shell: true,
361
+ const spawnOpts = {
284
362
  cwd: getWorkingDir(),
285
- signal: ac.signal,
286
363
  env: sanitizedChildEnv(),
287
364
  windowsHide: true,
288
- });
365
+ // POSIX:detached 自成进程组,中断时 killTree 的 kill(-pid) 才能掀整组;win32 不 detached(taskkill /T 掀树)。
366
+ detached: process.platform !== 'win32',
367
+ };
368
+ const child = WIN_BASH
369
+ ? spawn(WIN_BASH, ['-c', command], spawnOpts)
370
+ : spawn(process.platform === 'win32'
371
+ ? `chcp 65001 >nul & ${command}`
372
+ : command, { ...spawnOpts, shell: true });
373
+ // 中断/超时收尾:杀**整棵进程树**(不是只杀直接 shell)。否则 win32 上 shell 被杀后,在飞的
374
+ // 孙进程(bun test / 构建 / 起服务…)变孤儿继续跑、还占着 stdout 管道让 'close' 永不触发 ——
375
+ // 表现为「点了停止、agent 停了、活儿还在后台跑」的"停不下来"(与 2026-07-02 生图事故同源)。
376
+ // 不再把 signal 交给 spawn 自己 kill(那只杀直接子进程,还会抢在 killTree 前把 shell 杀掉、
377
+ // 让 taskkill /T 够不到已成孤儿的孙进程);改由这里统一 killTree + 立即 resolve,不空等 'close'。
378
+ const onKill = () => {
379
+ killedBySignal = true;
380
+ killTree(child);
381
+ resolveP();
382
+ };
383
+ if (ac.signal.aborted)
384
+ onKill();
385
+ else
386
+ ac.signal.addEventListener('abort', onKill, { once: true });
289
387
  child.stdout?.setEncoding('utf8');
290
388
  child.stderr?.setEncoding('utf8');
291
389
  child.stdout?.on('data', (chunk) => {
@@ -299,16 +397,11 @@ async function call(input, signal) {
299
397
  stderr += chunk;
300
398
  });
301
399
  child.on('error', (e) => {
302
- // signal abort 会先触发 error('AbortError');把这种 case 转成正常分支
303
- if (e.code === 'ABORT_ERR') {
304
- killedBySignal = true;
305
- resolveP();
306
- }
307
- else {
308
- rejectP(e);
309
- }
400
+ ac.signal.removeEventListener('abort', onKill);
401
+ rejectP(e);
310
402
  });
311
403
  child.on('close', (code, sig) => {
404
+ ac.signal.removeEventListener('abort', onKill);
312
405
  exitCode = code;
313
406
  if (sig)
314
407
  killedBySignal = true;
@@ -378,5 +471,6 @@ export const bashTool = {
378
471
  isReadOnly: false,
379
472
  isConcurrencySafe: false, // 默认按"会改状态"算;保守不并发
380
473
  maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
474
+ interruptBehavior: () => 'block',
381
475
  call,
382
476
  };