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.
- package/README.md +14 -12
- package/package.json +34 -1
- package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
- package/plugins/cron-runner/commands/cron.md +26 -0
- package/plugins/cron-runner/commands/crons.md +7 -0
- package/plugins/cron-runner/plugin.js +34 -0
- package/plugins/cron-runner/src/commands.js +348 -0
- package/plugins/cron-runner/src/cronTime.js +148 -0
- package/plugins/cron-runner/src/lock.js +68 -0
- package/plugins/cron-runner/src/runs.js +89 -0
- package/plugins/cron-runner/src/scheduleEngine.js +72 -0
- package/plugins/cron-runner/src/scheduler.js +355 -0
- package/plugins/cron-runner/src/schema.js +140 -0
- package/plugins/cron-runner/src/service.js +167 -0
- package/plugins/cron-runner/src/store.js +298 -0
- package/plugins/cron-runner/src/types.js +1 -0
- package/plugins/workflow-runner/commands/workflow.md +4 -4
- package/plugins/workflow-runner/commands/workflows.md +1 -1
- package/plugins/workflow-runner/plugin.js +3 -1
- package/plugins/workflow-runner/src/agentCommon.js +249 -51
- package/plugins/workflow-runner/src/dslManifest.js +72 -0
- package/plugins/workflow-runner/src/expressions.js +198 -15
- package/plugins/workflow-runner/src/index.js +40 -8
- package/plugins/workflow-runner/src/jsonPayload.js +1 -1
- package/plugins/workflow-runner/src/loader.js +286 -72
- package/plugins/workflow-runner/src/multimodal.js +37 -0
- package/plugins/workflow-runner/src/recovery.js +49 -39
- package/plugins/workflow-runner/src/runner.js +802 -287
- package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
- package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
- package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
- package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
- package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
- package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
- package/plugins/workflow-runner/src/taskIdentity.js +296 -0
- package/plugins/workflow-runner/src/types.js +19 -9
- package/plugins/workflow-runner/src/workflowState.js +516 -33
- package/skills/adversarial-review/SKILL.md +0 -1
- package/skills/ai-image/SKILL.md +89 -0
- package/skills/ai-image/scripts/generate.mjs +497 -0
- package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
- package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
- package/skills/ai-image/scripts/generate_test.mjs +74 -0
- package/skills/browser-automation/SKILL.md +47 -0
- package/skills/docx/SKILL.md +25 -3
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/docx/scripts/opc_check.py +253 -0
- package/skills/image-gen-openrouter/SKILL.md +10 -2
- package/skills/image-stats/SKILL.md +65 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/image_stats.py +368 -0
- package/skills/image-stats/scripts/image_stats_test.py +181 -0
- package/skills/pptx/SKILL.md +123 -95
- package/skills/pptx/design.md +82 -0
- package/skills/pptx/examples/README.md +29 -0
- package/skills/pptx/examples/hybrid_example.mjs +107 -0
- package/skills/pptx/examples/hybrid_example.pptx +0 -0
- package/skills/pptx/pptxgenjs.md +41 -0
- package/skills/pptx/scripts/deck_helpers.mjs +224 -0
- package/skills/pptx/scripts/layout_lint.py +293 -0
- package/skills/pptx/scripts/layout_lint_test.py +160 -0
- package/skills/pptx/scripts/opc_check.py +339 -0
- package/skills/pptx/scripts/svg_fallback.mjs +145 -0
- package/skills/pptx/svg.md +67 -0
- package/skills/tts-zh/SKILL.md +19 -2
- package/skills/ui-design/SKILL.md +158 -0
- package/skills/ui-design/claude_design.md +309 -0
- package/skills/ui-design/evals/evals.json +254 -0
- package/skills/ui-design/references/aesthetic-direction.md +83 -0
- package/skills/ui-design/references/claude-design-protocol.md +36 -0
- package/skills/ui-design/references/design-context.md +53 -0
- package/skills/ui-design/references/design-execution-playbook.md +82 -0
- package/skills/ui-design/references/export-pptx.md +124 -0
- package/skills/ui-design/references/layout-composition.md +654 -0
- package/skills/ui-design/references/preview-feedback.md +37 -0
- package/skills/ui-design/references/source-image-layout.md +189 -0
- package/skills/ui-design/references/starter-components.md +78 -0
- package/skills/ui-design/references/vi-aegis-vector.md +144 -0
- package/skills/ui-design/references/vi-amber-prism.md +133 -0
- package/skills/ui-design/references/vi-aqua-glass.md +200 -0
- package/skills/ui-design/references/vi-comfort-vision.md +147 -0
- package/skills/ui-design/references/vi-ember-prism.md +140 -0
- package/skills/ui-design/references/vi-nebula-console.md +151 -0
- package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
- package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
- package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
- package/skills/ui-design/references/vi-precision-horizon.md +215 -0
- package/skills/ui-design/references/vi-solar-relay.md +130 -0
- package/skills/ui-design/references/vi-template-workflow.md +48 -0
- package/skills/ui-design/references/vi-verdant-grid.md +141 -0
- package/skills/ui-design/references/vi-violet-meridian.md +172 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/assemble_material.py +217 -0
- package/skills/ui-design/scripts/assemble_material_test.py +149 -0
- package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
- package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
- package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
- package/skills/ui-design/scripts/check_language_policy.py +351 -0
- package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
- package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
- package/skills/ui-design/scripts/deck_pages_check.py +158 -0
- package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
- package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
- package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
- package/skills/ui-design/scripts/design_budget_check.py +271 -0
- package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
- package/skills/ui-design/scripts/design_repair_hints.py +162 -0
- package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
- package/skills/ui-design/scripts/design_style_check.py +397 -0
- package/skills/ui-design/scripts/design_style_check_test.py +233 -0
- package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
- package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
- package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
- package/skills/ui-design/scripts/extract_source_assets.py +607 -0
- package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
- package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
- package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
- package/skills/ui-design/scripts/image_plan_lint.py +571 -0
- package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
- package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
- package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
- package/skills/ui-design/scripts/layout_balance.py +881 -0
- package/skills/ui-design/scripts/layout_balance_test.py +927 -0
- package/skills/ui-design/scripts/normalize_design_json.py +272 -0
- package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
- package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
- package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
- package/skills/ui-design/scripts/resolve_source_input.py +270 -0
- package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
- package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
- package/skills/ui-design/scripts/source_usage_check.py +581 -0
- package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
- package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
- package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
- package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
- package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
- package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
- package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
- package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
- package/skills/ui-design/tests/e2e/rubric.json +54 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
- package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
- package/skills/video-compose/SKILL.md +8 -1
- package/skills/web-crawler/SKILL.md +70 -0
- package/skills/web-crawler/scripts/crawl.mjs +538 -0
- package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
- package/skills/xlsx/SKILL.md +12 -0
- package/skills/xlsx/scripts/opc_check.py +253 -0
- package/src/advisorState.js +22 -0
- package/src/askBridge.js +97 -3
- package/src/bootstrap/workingDir.js +12 -1
- package/src/cli/args.js +17 -1
- package/src/cli/configMenu.js +236 -0
- package/src/cli/configWizard.js +7 -5
- package/src/cli/print.js +37 -4
- package/src/cli/streamJson.js +77 -24
- package/src/cli/wizards/advisorWizard.js +147 -0
- package/src/cli/wizards/fieldForm.js +149 -0
- package/src/cli/wizards/imageWizard.js +60 -0
- package/src/cli/wizards/openRouterWizard.js +23 -0
- package/src/cli/wizards/providerPresets.js +95 -0
- package/src/cli/wizards/providerWizard.js +219 -0
- package/src/cli/wizards/searchWizard.js +23 -0
- package/src/cli/wizards/ttsWizard.js +34 -0
- package/src/config/configFile.js +441 -83
- package/src/config/configService.js +446 -0
- package/src/config.js +24 -64
- package/src/context/agentHome.js +9 -0
- package/src/context/archive.js +7 -2
- package/src/context/compact.js +8 -5
- package/src/context/contextLock.js +81 -0
- package/src/context/persistContext.js +30 -11
- package/src/context/reactiveCompact.js +4 -2
- package/src/context/recentDirs.js +21 -61
- package/src/context/sessionPath.js +97 -23
- package/src/context/sessionRegistry.js +303 -24
- package/src/context/tokenCounter.js +18 -0
- package/src/context/workspaceIndex.js +731 -0
- package/src/infra/verification.js +28 -15
- package/src/llm/client.js +394 -159
- package/src/llm/providers/anthropicFormat.js +241 -0
- package/src/llm/providers/openaiFormat.js +190 -0
- package/src/loop.js +219 -32
- package/src/main.js +61 -11
- package/src/messageQueue.js +97 -0
- package/src/multimodal.js +203 -0
- package/src/plugin-sdk.js +15 -0
- package/src/plugins/commandRouter.js +3 -0
- package/src/plugins/hookEngine.js +5 -1
- package/src/plugins/pluginLifecycle.js +28 -0
- package/src/plugins/pluginLoader.js +2 -1
- package/src/plugins/pluginRunner.js +49 -21
- package/src/plugins/transcript.js +4 -3
- package/src/prompts/pluginList.js +5 -5
- package/src/prompts/projectInstructions.js +3 -3
- package/src/prompts/promptAssembly.js +133 -144
- package/src/prompts/skillList.js +12 -22
- package/src/prompts/system.js +0 -1
- package/src/session/runAgentTurn.js +70 -0
- package/src/session/runTurn.js +41 -6
- package/src/toolGate.js +17 -0
- package/src/tools/advisor/advisor.js +379 -0
- package/src/tools/agent/agent.js +555 -0
- package/src/tools/agent/agentTypes.js +59 -0
- package/src/tools/agent/coordinator.js +93 -0
- package/src/tools/agent/worktree.js +270 -0
- package/src/tools/ask/ask.js +7 -2
- package/src/tools/bash/bash.js +112 -18
- package/src/tools/download/download.js +144 -0
- package/src/tools/edit/edit.js +1 -0
- package/src/tools/edit/multi-edit.js +1 -0
- package/src/tools/genimage/genimage.js +242 -0
- package/src/tools/genspeech/genspeech.js +67 -0
- package/src/tools/glob/glob.js +1 -0
- package/src/tools/grep/grep.js +3 -0
- package/src/tools/grep/rgPath.js +2 -0
- package/src/tools/index.js +25 -2
- package/src/tools/read/read.js +1 -0
- package/src/tools/shared/killTree.js +52 -0
- package/src/tools/toolProgressBridge.js +48 -0
- package/src/tools/webbrowser/agentBrowserPath.js +258 -0
- package/src/tools/webbrowser/browser.js +59 -42
- package/src/tools/webbrowser/browserControl.js +118 -0
- package/src/tools/webbrowser/browserDaemon.js +97 -0
- package/src/tools/webbrowser/browserService.js +27 -0
- package/src/tools/webbrowser/browserStream.js +119 -0
- package/src/tools/webbrowser/webbrowser.js +699 -130
- package/src/tools/webfetch/webfetch.js +1 -0
- package/src/tools/websearch/websearch.js +134 -51
- package/src/tools/write/write.js +1 -0
- package/src/ui/App.js +249 -22
- package/src/ui/AskQuestion.js +29 -10
- package/src/ui/InputBox.js +104 -12
- package/src/ui/LiveArea.js +112 -19
- package/src/ui/MessageList.js +100 -14
- package/src/ui/ModelPicker.js +56 -0
- package/src/ui/QueuedMessagesBanner.js +14 -0
- package/src/ui/Root.js +18 -10
- package/src/ui/SessionPicker.js +102 -0
- package/src/ui/StatusLine.js +13 -4
- package/src/ui/WorkspacePicker.js +55 -0
- package/src/ui/figures.js +44 -0
- package/src/ui/hooks/askSlot.js +82 -0
- package/src/ui/hooks/useChat.js +358 -64
- package/src/ui/hooks/usePasteHandler.js +11 -1
- package/src/ui/hooks/useTextBuffer.js +1 -3
- package/src/ui/liveViewport.js +5 -1
- package/src/ui/markdown.js +206 -0
- package/src/ui/textBuffer.js +0 -7
- package/src/ui/theme.js +32 -0
- package/src/utils/debugLog.js +0 -4
- package/src/utils/resourcePaths.js +2 -2
- package/src/utils/stripThink.js +50 -0
- package/src/web/browserBridge.js +447 -0
- package/src/web/protocol.js +19 -0
- package/workflows/adaptive-research.yaml +13 -5
- package/workflows/book-review-short.yaml +36 -10
- package/workflows/e2e-write-greet.yaml +6 -2
- package/workflows/illustrate.yaml +227 -0
- package/workflows/plan.yaml +460 -179
- package/workflows/ppt.yaml +1659 -0
- package/workflows/ralph-loop.yaml +199 -0
- package/workflows/schema.json +81 -23
- package/workflows/webppt.yaml +496 -0
- package/workflows/word.yaml +710 -0
- package/workflows/youtube-shorts.yaml +46 -15
- package/MINIMAL-TRACE.md +0 -77
- package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
- package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
- package/plugins/ralph-wiggum/README.md +0 -197
- package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
- package/plugins/ralph-wiggum/commands/help.md +0 -137
- package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
- package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
- package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
- package/plugins/ralph-wiggum/plugin.js +0 -280
- package/plugins/ralph-wiggum/src/goalState.js +0 -258
- package/plugins/ralph-wiggum/src/sentinels.js +0 -76
- package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
- package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
- package/plugins/workflow-runner/src/miniReAct.js +0 -223
- package/plugins/workflow-runner/src/voteHelpers.js +0 -110
- package/skills/config/SKILL.md +0 -123
- package/skills/frontend-design/LICENSE.txt +0 -177
- package/skills/frontend-design/SKILL.md +0 -42
- package/src/ui/ProgressPanel.js +0 -54
- package/src/ui/StreamingBlock.js +0 -11
- 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
|
+
}
|
package/src/tools/ask/ask.js
CHANGED
|
@@ -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
|
-
//
|
|
76
|
-
//
|
|
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
|
};
|
package/src/tools/bash/bash.js
CHANGED
|
@@ -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:
|
|
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 =
|
|
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 ——
|
|
265
|
-
// Unix:
|
|
266
|
-
// Windows:
|
|
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
|
|
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
|
-
|
|
303
|
-
|
|
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
|
};
|