crewswarm 0.8.1-beta
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/.env.example +155 -0
- package/LICENSE +21 -0
- package/README.md +316 -0
- package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js +1 -0
- package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +1 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-CSUb80ze.js +1 -0
- package/apps/dashboard/dist/assets/components-CSUb80ze.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +1 -0
- package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css +1 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-Px49zu76.js +2 -0
- package/apps/dashboard/dist/assets/index-Px49zu76.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js +1 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js +1 -0
- package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js +1 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-DfCuAClu.js +1 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js +1 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js +1 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js +1 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js +1 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js +1 -0
- package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js +1 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js +1 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js +1 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js +1 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js +1 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js +1 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js +1 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js +1 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js +1 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js +1 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js +1 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js.br +0 -0
- package/apps/dashboard/dist/favicon.png +0 -0
- package/apps/dashboard/dist/index.html +6466 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/dist/signup.html +446 -0
- package/apps/dashboard/index.html +6442 -0
- package/apps/dashboard/package.json +15 -0
- package/apps/dashboard/src/app.js +2823 -0
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +1847 -0
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +327 -0
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +208 -0
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +175 -0
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +18 -0
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +220 -0
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +91 -0
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +134 -0
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +127 -0
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +555 -0
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +2085 -0
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +2237 -0
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +229 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +955 -0
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +654 -0
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +175 -0
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +182 -0
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +441 -0
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +185 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +663 -0
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +160 -0
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +202 -0
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +803 -0
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +284 -0
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +173 -0
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +660 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +538 -0
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +390 -0
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +238 -0
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +747 -0
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +249 -0
- package/apps/vibe/.crew/cost.json +17 -0
- package/apps/vibe/.crew/json-parse-metrics.jsonl +22 -0
- package/apps/vibe/.crew/pipeline-metrics.jsonl +22 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +1 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +2 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +5 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +5 -0
- package/apps/vibe/.crew/sandbox.json +7 -0
- package/apps/vibe/.crew/session.json +285 -0
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +37 -0
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +12 -0
- package/apps/vibe/.studio-data/project-messages/general.jsonl +54 -0
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +10 -0
- package/apps/vibe/ARCHITECTURE.md +3393 -0
- package/apps/vibe/QUICK-REFERENCE.md +211 -0
- package/apps/vibe/README.md +76 -0
- package/apps/vibe/ROADMAP.md +41 -0
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +35 -0
- package/apps/vibe/VISUAL-GUIDE.md +378 -0
- package/apps/vibe/capture-demo.mjs +160 -0
- package/apps/vibe/capture-vibe-assets.mjs +71 -0
- package/apps/vibe/capture-vibe-video.mjs +260 -0
- package/apps/vibe/check-buttons.js +41 -0
- package/apps/vibe/diagnose.html +106 -0
- package/apps/vibe/fix-buttons.js +103 -0
- package/apps/vibe/index.html +3401 -0
- package/apps/vibe/package-lock.json +920 -0
- package/apps/vibe/package.json +31 -0
- package/apps/vibe/public/favicon.png +0 -0
- package/apps/vibe/scripts/studio-pty-host.py +117 -0
- package/apps/vibe/server.mjs +1835 -0
- package/apps/vibe/src/main.js +2846 -0
- package/apps/vibe/src/register-all-languages.js +98 -0
- package/apps/vibe/start-studio.sh +11 -0
- package/apps/vibe/test/accessibility-tests.js +77 -0
- package/apps/vibe/test/browser-performance-audit.mjs +205 -0
- package/apps/vibe/test/performance-tests.js +120 -0
- package/apps/vibe/test/security-tests.js +213 -0
- package/apps/vibe/tests/e2e.local.mjs +54 -0
- package/apps/vibe/tests/server.smoke.mjs +106 -0
- package/apps/vibe/update_website.mjs +74 -0
- package/apps/vibe/vite.config.js +19 -0
- package/apps/vibe/watch-server.mjs +108 -0
- package/contrib/openclaw-plugin/README.md +199 -0
- package/contrib/openclaw-plugin/index.ts +306 -0
- package/contrib/openclaw-plugin/openclaw.plugin.json +41 -0
- package/contrib/openclaw-plugin/package.json +27 -0
- package/contrib/openclaw-plugin/skills/crewswarm/SKILL.md +88 -0
- package/crew-lead.mjs +649 -0
- package/engines/claude-code.json +36 -0
- package/engines/codex.json +37 -0
- package/engines/crew-cli.json +42 -0
- package/engines/cursor.json +40 -0
- package/engines/docker-sandbox.json +38 -0
- package/engines/gemini-cli.json +75 -0
- package/engines/opencode.json +31 -0
- package/gateway-bridge.mjs +1575 -0
- package/install.sh +738 -0
- package/lib/agent-registry.mjs +232 -0
- package/lib/agents/daemon.mjs +121 -0
- package/lib/agents/dispatch.mjs +225 -0
- package/lib/agents/permissions.mjs +90 -0
- package/lib/agents/platform-formatting.mjs +102 -0
- package/lib/agents/registry.mjs +81 -0
- package/lib/agents/tool-instructions.mjs +257 -0
- package/lib/agents/validation.mjs +75 -0
- package/lib/approval/policy-manager.mjs +221 -0
- package/lib/autoharness/index.mjs +391 -0
- package/lib/bridges/cli-executor.mjs +332 -0
- package/lib/bridges/gateway-ws.mjs +345 -0
- package/lib/bridges/integration.mjs +229 -0
- package/lib/bridges/rag-helper.mjs +90 -0
- package/lib/browser/opencode-passthrough-filter.js +44 -0
- package/lib/browser/passthrough-stderr.js +109 -0
- package/lib/chat/autonomous-mentions.mjs +373 -0
- package/lib/chat/history.mjs +82 -0
- package/lib/chat/mention-routing-intent.mjs +136 -0
- package/lib/chat/participants.mjs +95 -0
- package/lib/chat/project-messages-rag.mjs +265 -0
- package/lib/chat/project-messages.mjs +479 -0
- package/lib/chat/shared-chat-prompt-overlay.mjs +52 -0
- package/lib/chat/thread-binding.mjs +34 -0
- package/lib/chat/unified-history.mjs +223 -0
- package/lib/chat/unified-wrapper.mjs +41 -0
- package/lib/cli-process-tracker.mjs +228 -0
- package/lib/collections/index.mjs +433 -0
- package/lib/contacts/identity-linker.mjs +248 -0
- package/lib/contacts/index.mjs +341 -0
- package/lib/crew-judge/PROMPT.md +93 -0
- package/lib/crew-judge/judge.mjs +260 -0
- package/lib/crew-lead/agent-manager.mjs +125 -0
- package/lib/crew-lead/background.mjs +270 -0
- package/lib/crew-lead/brain.mjs +110 -0
- package/lib/crew-lead/chat-handler.mjs +2603 -0
- package/lib/crew-lead/chat-handler.mjs.bak +1274 -0
- package/lib/crew-lead/classifier.mjs +83 -0
- package/lib/crew-lead/http-server.mjs +4824 -0
- package/lib/crew-lead/intent.mjs +102 -0
- package/lib/crew-lead/interval-manager.mjs +41 -0
- package/lib/crew-lead/llm-caller.mjs +544 -0
- package/lib/crew-lead/prompts.mjs +392 -0
- package/lib/crew-lead/retry-manager.mjs +118 -0
- package/lib/crew-lead/tools.mjs +318 -0
- package/lib/crew-lead/wave-dispatcher.mjs +798 -0
- package/lib/crew-lead/waves-config.json +73 -0
- package/lib/crew-lead/waves-loader.mjs +110 -0
- package/lib/crew-lead/ws-router.mjs +428 -0
- package/lib/dispatch/parsers.mjs +299 -0
- package/lib/domain-planning/detector.mjs +196 -0
- package/lib/domain-planning/prompts/crew-pm-cli.md +96 -0
- package/lib/domain-planning/prompts/crew-pm-core.md +122 -0
- package/lib/domain-planning/prompts/crew-pm-frontend.md +111 -0
- package/lib/engines/crew-cli-sandbox.mjs +422 -0
- package/lib/engines/crew-cli.mjs +155 -0
- package/lib/engines/cursor-launcher.mjs +110 -0
- package/lib/engines/engine-registry.mjs +253 -0
- package/lib/engines/llm-direct.mjs +184 -0
- package/lib/engines/opencode.mjs +256 -0
- package/lib/engines/ouroboros.mjs +114 -0
- package/lib/engines/rt-envelope.mjs +1643 -0
- package/lib/engines/rt-envelope.mjs.backup-current +870 -0
- package/lib/engines/runners.mjs +1367 -0
- package/lib/gemini-cli-passthrough-noise.mjs +37 -0
- package/lib/integrations/code-search.mjs +259 -0
- package/lib/integrations/greptile.mjs +148 -0
- package/lib/integrations/multimodal.mjs +313 -0
- package/lib/integrations/telegram-streaming.mjs +153 -0
- package/lib/integrations/tts.mjs +312 -0
- package/lib/integrations/twitter-links.mjs +294 -0
- package/lib/memory/shared-adapter.mjs +296 -0
- package/lib/pipeline/manager.mjs +539 -0
- package/lib/preferences/extractor.mjs +347 -0
- package/lib/project-dir.mjs +20 -0
- package/lib/runtime/config.mjs +388 -0
- package/lib/runtime/dlq.mjs +170 -0
- package/lib/runtime/log-rotation.mjs +82 -0
- package/lib/runtime/logger.mjs +58 -0
- package/lib/runtime/memory.mjs +421 -0
- package/lib/runtime/paths.mjs +76 -0
- package/lib/runtime/project-dir.mjs +127 -0
- package/lib/runtime/spending.mjs +204 -0
- package/lib/runtime/startup-guard.mjs +291 -0
- package/lib/runtime/task-lease.mjs +234 -0
- package/lib/runtime/telemetry-schema.mjs +208 -0
- package/lib/runtime/telemetry.mjs +101 -0
- package/lib/runtime/utils.mjs +64 -0
- package/lib/skills/index.mjs +265 -0
- package/lib/tools/browser.mjs +135 -0
- package/lib/tools/executor.mjs +913 -0
- package/lib/types.d.ts +57 -0
- package/package.json +106 -0
- package/pm-loop.mjs +1626 -0
- package/prompts/coder-back.md +27 -0
- package/prompts/coder-front.md +27 -0
- package/prompts/coder.md +28 -0
- package/prompts/copywriter.md +17 -0
- package/prompts/fixer.md +39 -0
- package/prompts/frontend.md +23 -0
- package/prompts/github.md +24 -0
- package/prompts/main.md +39 -0
- package/prompts/pm-cli.md +95 -0
- package/prompts/pm-core.md +121 -0
- package/prompts/pm-frontend.md +110 -0
- package/prompts/pm.md +234 -0
- package/prompts/qa.md +44 -0
- package/prompts/security.md +19 -0
- package/scripts/build-crew-chat.sh +28 -0
- package/scripts/build-llms-full.mjs +52 -0
- package/scripts/chatmock-login.sh +16 -0
- package/scripts/chatmock-serve.sh +16 -0
- package/scripts/check-dashboard.mjs +88 -0
- package/scripts/crew-scribe.mjs +326 -0
- package/scripts/dashboard-helpers.mjs +391 -0
- package/scripts/dashboard-validation.mjs +198 -0
- package/scripts/dashboard.mjs +9717 -0
- package/scripts/dlq-replay.mjs +61 -0
- package/scripts/doctor.mjs +196 -0
- package/scripts/file-lock.mjs +186 -0
- package/scripts/fresh-machine-smoke.sh +323 -0
- package/scripts/generate-changelog.mjs +227 -0
- package/scripts/generate-openapi.mjs +334 -0
- package/scripts/health-check.mjs +229 -0
- package/scripts/install-docker.sh +213 -0
- package/scripts/mcp-server.mjs +1625 -0
- package/scripts/opencrew-rt-daemon.mjs +568 -0
- package/scripts/openswitchctl +646 -0
- package/scripts/refactor-configs.mjs +39 -0
- package/scripts/release-check.sh +46 -0
- package/scripts/resolve-node-bin.sh +25 -0
- package/scripts/restart-all-from-repo.sh +329 -0
- package/scripts/restart-crew-lead.sh +98 -0
- package/scripts/restart-dashboard.sh +104 -0
- package/scripts/restart-service.sh +274 -0
- package/scripts/run-accessibility-audit.mjs +356 -0
- package/scripts/run-integration-bounded.mjs +188 -0
- package/scripts/run-scheduled-pipeline.mjs +230 -0
- package/scripts/run.mjs +41 -0
- package/scripts/scan-skills.mjs +79 -0
- package/scripts/setup-firewall.sh +128 -0
- package/scripts/smoke-dispatch.mjs +149 -0
- package/scripts/smoke.sh +163 -0
- package/scripts/start-crew.mjs +328 -0
- package/scripts/start.mjs +146 -0
- package/scripts/swiftbar-restart-service.sh +19 -0
- package/scripts/sync-agents.mjs +152 -0
- package/scripts/sync-prompts.mjs +79 -0
- package/scripts/validate-config.mjs +337 -0
- package/scripts/wow.mjs +89 -0
- package/telegram-bridge.mjs +2421 -0
- package/unified-orchestrator.mjs +519 -0
- package/whatsapp-bridge.mjs +1481 -0
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool executor — extracted from gateway-bridge.mjs
|
|
3
|
+
* Parses and executes @@TOOL markers from agent LLM replies.
|
|
4
|
+
*
|
|
5
|
+
* Supported: @@WRITE_FILE, @@APPEND_FILE, @@READ_FILE, @@MKDIR, @@RUN_CMD,
|
|
6
|
+
* @@BROWSER, @@WEB_SEARCH, @@WEB_FETCH, @@TELEGRAM,
|
|
7
|
+
* @@SKILL, @@DEFINE_SKILL
|
|
8
|
+
*
|
|
9
|
+
* Inject: initTools({ resolveConfig, resolveTelegramBridgeConfig,
|
|
10
|
+
* loadAgentList, getOpencodeProjectDir,
|
|
11
|
+
* loadSkillDef, loadPendingSkills, savePendingSkills,
|
|
12
|
+
* notifyTelegramSkillApproval, executeSkill })
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import os from "os";
|
|
18
|
+
import {
|
|
19
|
+
evaluateHarnessAction,
|
|
20
|
+
recordToolTrace,
|
|
21
|
+
} from "../autoharness/index.mjs";
|
|
22
|
+
import {
|
|
23
|
+
browserClick,
|
|
24
|
+
browserNavigate,
|
|
25
|
+
browserScreenshot,
|
|
26
|
+
browserType,
|
|
27
|
+
} from "./browser.mjs";
|
|
28
|
+
|
|
29
|
+
const CREWSWARM_DIR = path.join(os.homedir(), ".crewswarm");
|
|
30
|
+
const LEGACY_STATE_DIR = path.join(os.homedir(), ".openclaw");
|
|
31
|
+
const SKILLS_DIR = path.join(os.homedir(), ".crewswarm", "skills");
|
|
32
|
+
|
|
33
|
+
let _resolveConfig = () => ({});
|
|
34
|
+
let _resolveTelegramBridgeConfig = () => ({});
|
|
35
|
+
let _loadAgentList = () => [];
|
|
36
|
+
let _getOpencodeProjectDir = () => null;
|
|
37
|
+
let _loadSkillDef = () => null;
|
|
38
|
+
let _loadPendingSkills = () => ({});
|
|
39
|
+
let _savePendingSkills = () => {};
|
|
40
|
+
let _notifyTelegramSkillApproval = async () => {};
|
|
41
|
+
let _executeSkill = async () => ({});
|
|
42
|
+
|
|
43
|
+
export function initTools({ resolveConfig, resolveTelegramBridgeConfig, loadAgentList, getOpencodeProjectDir, loadSkillDef, loadPendingSkills, savePendingSkills, notifyTelegramSkillApproval, executeSkill } = {}) {
|
|
44
|
+
if (resolveConfig) _resolveConfig = resolveConfig;
|
|
45
|
+
if (resolveTelegramBridgeConfig) _resolveTelegramBridgeConfig = resolveTelegramBridgeConfig;
|
|
46
|
+
if (loadAgentList) _loadAgentList = loadAgentList;
|
|
47
|
+
if (getOpencodeProjectDir) _getOpencodeProjectDir = getOpencodeProjectDir;
|
|
48
|
+
if (loadSkillDef) _loadSkillDef = loadSkillDef;
|
|
49
|
+
if (loadPendingSkills) _loadPendingSkills = loadPendingSkills;
|
|
50
|
+
if (savePendingSkills) _savePendingSkills = savePendingSkills;
|
|
51
|
+
if (notifyTelegramSkillApproval) _notifyTelegramSkillApproval = notifyTelegramSkillApproval;
|
|
52
|
+
if (executeSkill) _executeSkill = executeSkill;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Agent Tool Execution ───────────────────────────────────────────────────
|
|
56
|
+
// Agents embed tool calls in their LLM reply using these markers.
|
|
57
|
+
// gateway-bridge parses and executes them, returning a summary of actions.
|
|
58
|
+
//
|
|
59
|
+
// Supported tools:
|
|
60
|
+
// @@WRITE_FILE /absolute/path/to/file
|
|
61
|
+
// <file contents>
|
|
62
|
+
// @@END_FILE
|
|
63
|
+
//
|
|
64
|
+
// @@READ_FILE /absolute/path/to/file
|
|
65
|
+
//
|
|
66
|
+
// @@MKDIR /absolute/path/to/dir
|
|
67
|
+
//
|
|
68
|
+
// @@RUN_CMD <shell command> (whitelist-controlled)
|
|
69
|
+
//
|
|
70
|
+
|
|
71
|
+
// Agents that auto-approve @@RUN_CMD without requiring user confirmation
|
|
72
|
+
const _AUTO_APPROVE_STATIC = new Set(["crew-fixer", "crew-github", "crew-pm"]);
|
|
73
|
+
const _AUTO_APPROVE_ROLES = new Set(["coder", "ops", "generalist"]);
|
|
74
|
+
|
|
75
|
+
export function isAutoApproveAgent(agentId) {
|
|
76
|
+
if (_AUTO_APPROVE_STATIC.has(agentId)) return true;
|
|
77
|
+
const agents = _loadAgentList();
|
|
78
|
+
const cfg = agents.find(a => a.id === agentId);
|
|
79
|
+
if (cfg?.tools?.autoApproveCmd) return true;
|
|
80
|
+
return cfg?._role ? _AUTO_APPROVE_ROLES.has(cfg._role) : false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Pending command approvals: approvalId → { resolve, timer }
|
|
84
|
+
export const pendingCmdApprovals = new Map();
|
|
85
|
+
|
|
86
|
+
// Module-level RT client ref so executeToolCalls can publish approval requests
|
|
87
|
+
export let _rtClientForApprovals = null;
|
|
88
|
+
export function setRtClient(rt) { _rtClientForApprovals = rt; }
|
|
89
|
+
|
|
90
|
+
// Per-role tool defaults — used when agent has no explicit alsoAllow in config
|
|
91
|
+
export const AGENT_TOOL_ROLE_DEFAULTS = {
|
|
92
|
+
'crew-qa': new Set(['read_file','skill']),
|
|
93
|
+
'crew-coder': new Set(['write_file','read_file','mkdir','run_cmd','skill','define_skill','browser']),
|
|
94
|
+
'crew-coder-front': new Set(['write_file','read_file','mkdir','run_cmd','skill','browser']),
|
|
95
|
+
'crew-coder-back': new Set(['write_file','read_file','mkdir','run_cmd','skill','browser']),
|
|
96
|
+
'crew-frontend': new Set(['write_file','read_file','mkdir','run_cmd','skill']),
|
|
97
|
+
'crew-fixer': new Set(['write_file','read_file','mkdir','run_cmd','skill','browser']),
|
|
98
|
+
'crew-github': new Set(['read_file','run_cmd','git','skill']),
|
|
99
|
+
'crew-pm': new Set(['read_file','write_file','mkdir','dispatch','skill']),
|
|
100
|
+
'crew-main': new Set(['read_file','write_file','run_cmd','dispatch','skill','define_skill']),
|
|
101
|
+
'crew-security': new Set(['read_file','run_cmd']),
|
|
102
|
+
'crew-copywriter': new Set(['write_file','read_file','skill']),
|
|
103
|
+
'crew-telegram': new Set(['telegram','read_file']),
|
|
104
|
+
'crew-lead': new Set(['read_file','write_file','mkdir','run_cmd','web_search','web_fetch','skill','define_skill','dispatch','telegram','whatsapp']),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// crewswarm @@TOOL permission names — distinct from legacy gateway tool names
|
|
108
|
+
const CREWSWARM_TOOL_NAMES = new Set(['write_file','append_file','read_file','mkdir','run_cmd','git','dispatch','skill','define_skill','telegram','web_search','web_fetch','browser']);
|
|
109
|
+
|
|
110
|
+
function getRoleDefaultPermissions(agentId) {
|
|
111
|
+
if (AGENT_TOOL_ROLE_DEFAULTS[agentId]) return AGENT_TOOL_ROLE_DEFAULTS[agentId];
|
|
112
|
+
for (const [key, val] of Object.entries(AGENT_TOOL_ROLE_DEFAULTS)) {
|
|
113
|
+
if (agentId.startsWith(key)) return val;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function loadAgentToolPermissions(agentId) {
|
|
119
|
+
// Check config files for explicit crewswarm-style tool permissions.
|
|
120
|
+
// tools.alsoAllow in crewswarm.json may contain legacy gateway tool names (exec, web_search, etc.)
|
|
121
|
+
// — only use it if it contains at least one crewswarm @@TOOL name.
|
|
122
|
+
try {
|
|
123
|
+
const cfgPaths = [
|
|
124
|
+
path.join(os.homedir(), ".crewswarm", "crewswarm.json"),
|
|
125
|
+
path.join(os.homedir(), ".crewswarm", "crewswarm.json"),
|
|
126
|
+
path.join(os.homedir(), ".openclaw", "openclaw.json"),
|
|
127
|
+
];
|
|
128
|
+
for (const p of cfgPaths) {
|
|
129
|
+
if (!fs.existsSync(p)) continue;
|
|
130
|
+
const cfg = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
131
|
+
const agents = Array.isArray(cfg.agents) ? cfg.agents : (cfg.agents?.list || []);
|
|
132
|
+
const crewId = agentId.startsWith("crew-") ? agentId : `crew-${agentId}`;
|
|
133
|
+
const bareId = agentId.startsWith("crew-") ? agentId.slice(5) : agentId;
|
|
134
|
+
const agent = agents.find(a => a.id === agentId || a.id === crewId || a.id === bareId);
|
|
135
|
+
// Only accept if the list contains crewswarm-style tool names, not just legacy gateway names
|
|
136
|
+
const allow = agent?.tools?.crewswarmAllow || agent?.tools?.alsoAllow || [];
|
|
137
|
+
const crewswarmTools = allow.filter(t => CREWSWARM_TOOL_NAMES.has(t));
|
|
138
|
+
if (crewswarmTools.length > 0) {
|
|
139
|
+
return new Set([...(getRoleDefaultPermissions(agentId) || []), ...crewswarmTools]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
// Fall back to role defaults (covers crew-coder, crew-qa, crew-fixer, etc.)
|
|
144
|
+
const roleDefaults = getRoleDefaultPermissions(agentId);
|
|
145
|
+
if (roleDefaults) return roleDefaults;
|
|
146
|
+
// Dynamic agents: derive tools from _role in crewswarm.json
|
|
147
|
+
try {
|
|
148
|
+
const agents = _loadAgentList();
|
|
149
|
+
const cfg = agents.find(a => a.id === agentId);
|
|
150
|
+
if (cfg?._role) {
|
|
151
|
+
const ROLE_TOOL_DEFAULTS = {
|
|
152
|
+
coder: new Set(['write_file','read_file','mkdir','run_cmd','skill','browser']),
|
|
153
|
+
researcher: new Set(['read_file','web_search','web_fetch','skill']),
|
|
154
|
+
writer: new Set(['write_file','read_file','web_search','web_fetch','skill']),
|
|
155
|
+
auditor: new Set(['read_file','run_cmd','skill']),
|
|
156
|
+
ops: new Set(['read_file','write_file','mkdir','run_cmd','git','skill']),
|
|
157
|
+
generalist: new Set(['read_file','write_file','mkdir','run_cmd','dispatch','skill']),
|
|
158
|
+
};
|
|
159
|
+
if (ROLE_TOOL_DEFAULTS[cfg._role]) return ROLE_TOOL_DEFAULTS[cfg._role];
|
|
160
|
+
// Custom role: check crewswarm.json top-level roleToolDefaults map
|
|
161
|
+
// e.g. "roleToolDefaults": { "analyst": ["read_file","web_search","skill"] }
|
|
162
|
+
try {
|
|
163
|
+
const rootCfg = JSON.parse(fs.readFileSync(path.join(os.homedir(), ".crewswarm", "crewswarm.json"), "utf8"));
|
|
164
|
+
const customTools = rootCfg.roleToolDefaults?.[cfg._role];
|
|
165
|
+
if (Array.isArray(customTools) && customTools.length > 0) return new Set(customTools);
|
|
166
|
+
} catch {}
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
// Unknown agent — allow read/write/mkdir/run by default
|
|
170
|
+
return new Set(['read_file','write_file','mkdir','run_cmd']);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function buildToolInstructions(allowed) {
|
|
174
|
+
const projectDir = _getOpencodeProjectDir() || process.cwd();
|
|
175
|
+
const tools = [];
|
|
176
|
+
if (allowed.has('write_file')) tools.push(`### Write (overwrite) a file to disk:
|
|
177
|
+
@@WRITE_FILE ${projectDir}/file.html
|
|
178
|
+
<!DOCTYPE html>
|
|
179
|
+
<html>...full file contents here...</html>
|
|
180
|
+
@@END_FILE
|
|
181
|
+
|
|
182
|
+
### Append content to an existing file (does NOT overwrite):
|
|
183
|
+
@@APPEND_FILE ${projectDir}/FIXES.md
|
|
184
|
+
|
|
185
|
+
## Session N (date)
|
|
186
|
+
- Fixed: ...
|
|
187
|
+
@@END_FILE`);
|
|
188
|
+
if (allowed.has('read_file')) tools.push(`### Read a file from disk:
|
|
189
|
+
@@READ_FILE /absolute/path/to/file.txt`);
|
|
190
|
+
if (allowed.has('mkdir')) tools.push(`### Create a directory:
|
|
191
|
+
@@MKDIR /absolute/path/to/directory`);
|
|
192
|
+
if (allowed.has('run_cmd') || allowed.has('git')) {
|
|
193
|
+
const gitNote = allowed.has('git') ? " Git commands (git status, git add, git commit, git push, git log) are also allowed." : "";
|
|
194
|
+
tools.push(`### Run a shell command (safe subset only — no rm, no sudo):${gitNote}
|
|
195
|
+
@@RUN_CMD ls /some/path`);
|
|
196
|
+
}
|
|
197
|
+
if (allowed.has('web_search')) tools.push(`### Search the web (Brave Search):
|
|
198
|
+
@@WEB_SEARCH your search query here
|
|
199
|
+
Returns top 5 results with title, URL, and snippet. Use this to research facts, find examples, or verify information before writing.`);
|
|
200
|
+
if (allowed.has('web_fetch')) tools.push(`### Fetch a URL and read its content:
|
|
201
|
+
@@WEB_FETCH https://example.com/page
|
|
202
|
+
Returns the page text (up to 8000 chars). Use to read docs, articles, or any URL before summarising or referencing.`);
|
|
203
|
+
if (allowed.has('browser')) tools.push(`### Drive a browser for lightweight web automation:
|
|
204
|
+
@@BROWSER navigate https://example.com
|
|
205
|
+
@@BROWSER screenshot https://example.com
|
|
206
|
+
@@BROWSER click https://example.com button[type="submit"]
|
|
207
|
+
@@BROWSER type https://example.com input[name="q"] "crewswarm"
|
|
208
|
+
Use quoted arguments when selectors or text contain spaces. Returns concise structured output plus a screenshot file path.`);
|
|
209
|
+
if (allowed.has('telegram')) tools.push(`### Send a Telegram message:
|
|
210
|
+
@@TELEGRAM your message text here
|
|
211
|
+
@@TELEGRAM @ContactName message text here
|
|
212
|
+
Sends a message to the configured Telegram chat (or to a contact by name if you use @Name). Contact names are set in Dashboard → Settings → Telegram → Contact names. Use to notify humans of task completion, errors, or important findings.`);
|
|
213
|
+
if (allowed.has('skill')) {
|
|
214
|
+
const skillList = (() => {
|
|
215
|
+
try {
|
|
216
|
+
if (!fs.existsSync(SKILLS_DIR)) return "(none installed yet)";
|
|
217
|
+
const entries = [];
|
|
218
|
+
// JSON skills
|
|
219
|
+
const jsonFiles = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith(".json"));
|
|
220
|
+
for (const f of jsonFiles) {
|
|
221
|
+
try {
|
|
222
|
+
const d = JSON.parse(fs.readFileSync(path.join(SKILLS_DIR, f), "utf8"));
|
|
223
|
+
const name = f.replace(".json","");
|
|
224
|
+
const approval = d.requiresApproval ? " ⚠️ requires-approval" : "";
|
|
225
|
+
const urlLine = d.url ? `\n URL: ${d.method||"POST"} ${d.url}` : "";
|
|
226
|
+
const notes = d.paramNotes ? `\n Params: ${d.paramNotes}` : "";
|
|
227
|
+
const defaults = d.defaultParams && Object.keys(d.defaultParams).length
|
|
228
|
+
? `\n Defaults: ${JSON.stringify(d.defaultParams)}` : "";
|
|
229
|
+
entries.push(` - ${name}${approval} — ${d.description || ""}${urlLine}${notes}${defaults}`);
|
|
230
|
+
} catch { entries.push(` - ${f.replace(".json","")}`); }
|
|
231
|
+
}
|
|
232
|
+
// SKILL.md skills (AgentSkills / ClawHub format)
|
|
233
|
+
const dirs = fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
234
|
+
.filter(e => e.isDirectory() && fs.existsSync(path.join(SKILLS_DIR, e.name, "SKILL.md")));
|
|
235
|
+
for (const dir of dirs) {
|
|
236
|
+
const md = loadSkillMd(dir.name);
|
|
237
|
+
if (md) {
|
|
238
|
+
const tag = md.url ? "" : " 📄 instruction-card";
|
|
239
|
+
entries.push(` - ${dir.name}${tag} — ${md.description}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Standalone .md skills
|
|
243
|
+
const mdFiles = fs.readdirSync(SKILLS_DIR).filter(f => f.endsWith(".md"));
|
|
244
|
+
for (const f of mdFiles) {
|
|
245
|
+
const name = f.replace(".md","");
|
|
246
|
+
if (!jsonFiles.some(j => j.replace(".json","") === name)) {
|
|
247
|
+
const md = loadSkillMd(name);
|
|
248
|
+
if (md) entries.push(` - ${name} 📄 — ${md.description}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return entries.length ? entries.join("\n") : "(none installed yet)";
|
|
252
|
+
} catch { return ""; }
|
|
253
|
+
})();
|
|
254
|
+
tools.push(`### Call an external skill (API integration):
|
|
255
|
+
@@SKILL skillname {"param":"value"}
|
|
256
|
+
Available skills:\n${skillList}
|
|
257
|
+
Replace skillname with the skill name. Include any required params as inline JSON on the same line.
|
|
258
|
+
Example: @@SKILL fly.deploy {"app":"myapp"}
|
|
259
|
+
Example: @@SKILL elevenlabs.tts {"text":"Hello world","voice_id":"21m00Tcm4TlvDq8ikWAM"}`);
|
|
260
|
+
if (allowed.has('define_skill')) {
|
|
261
|
+
tools.push(`### Define or update a skill (create a reusable API integration):
|
|
262
|
+
@@DEFINE_SKILL skillname
|
|
263
|
+
{
|
|
264
|
+
"description": "What this skill does",
|
|
265
|
+
"url": "https://api.example.com/endpoint/{param}",
|
|
266
|
+
"method": "POST",
|
|
267
|
+
"auth": {"type": "bearer", "keyFrom": "providers.PROVIDER.apiKey"},
|
|
268
|
+
"defaultParams": {"model": "default"},
|
|
269
|
+
"paramNotes": "Required: param1. Optional: param2 (default: x).",
|
|
270
|
+
"requiresApproval": false
|
|
271
|
+
}
|
|
272
|
+
@@END_SKILL
|
|
273
|
+
Use @@WEB_SEARCH and @@WEB_FETCH to research the API first, then define the skill.
|
|
274
|
+
Auth types: "bearer" (Authorization: Bearer <key>), "header" (custom header + "header" field).
|
|
275
|
+
keyFrom format: "providers.PROVIDER.apiKey" (reads from crewswarm.json) or "env.ENV_VAR_NAME".`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!tools.length) return ""; // agent has no tools — instructions not needed
|
|
279
|
+
|
|
280
|
+
const externalProjectHint =
|
|
281
|
+
projectDir === process.cwd()
|
|
282
|
+
? `- If the task refers to an external project by name (e.g. polymarket-ai-strat), its root is typically ${path.join(os.homedir(), "Desktop", "<project-name>")}, not under PROJECT DIRECTORY. Do not use paths like .../crewswarm/<project-name>/...; use .../Desktop/<project-name>/... instead.`
|
|
283
|
+
: "";
|
|
284
|
+
|
|
285
|
+
return `
|
|
286
|
+
## Agent Tools — ACTIVE for this session
|
|
287
|
+
|
|
288
|
+
When your task requires actions on disk or network, output the tool markers below directly in your reply.
|
|
289
|
+
The system detects and executes them automatically. ALWAYS use absolute paths.
|
|
290
|
+
|
|
291
|
+
PROJECT DIRECTORY (write all output files here): ${projectDir}
|
|
292
|
+
|
|
293
|
+
${tools.join("\n\n")}
|
|
294
|
+
|
|
295
|
+
CRITICAL RULES:
|
|
296
|
+
${externalProjectHint ? externalProjectHint + "\n" : ""}- Output the @@TOOL markers directly — do NOT describe or simulate what you would do.
|
|
297
|
+
- Use @@WRITE_FILE to write files — never just show code in markdown blocks.
|
|
298
|
+
- @@END_FILE MUST appear on its own line immediately after the last line of file content.
|
|
299
|
+
- ALL tool calls go in a SINGLE reply — do NOT stop after @@MKDIR and wait for results. Chain @@MKDIR then @@WRITE_FILE immediately in the same response.
|
|
300
|
+
- Do NOT write "**Tool execution results:**" — the system appends that automatically.
|
|
301
|
+
- Do NOT wrap file contents in markdown fences inside @@WRITE_FILE...@@END_FILE blocks.
|
|
302
|
+
- Write ALL output files under ${projectDir}/ unless the task explicitly specifies a different absolute path.
|
|
303
|
+
- Disabled tools: ${['write_file','read_file','mkdir','run_cmd','git'].filter(t => !allowed.has(t)).join(', ') || 'none'}
|
|
304
|
+
- To log a durable discovery to the shared knowledge base (brain.md), include this anywhere in your reply:
|
|
305
|
+
@@BRAIN: <one-line fact worth remembering for future tasks>
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Commands that are always blocked regardless of agent permissions or allowlist
|
|
310
|
+
const BLOCKED_CMD_PATTERNS = [
|
|
311
|
+
/\brm\s+-[rf]{1,2}f?\b/,
|
|
312
|
+
/\bsudo\b/,
|
|
313
|
+
/curl[^|\n]*\|\s*(bash|sh|zsh|fish)\b/i,
|
|
314
|
+
/wget[^|\n]*\|\s*(bash|sh|zsh|fish)\b/i,
|
|
315
|
+
/:\(\)\s*\{\s*:\|:&\s*\};?\s*:/, // fork bomb
|
|
316
|
+
/\bdd\s+if=/,
|
|
317
|
+
/\bmkfs\b/,
|
|
318
|
+
/\bfdisk\b/,
|
|
319
|
+
/\bchmod\s+[0-9]*7[0-9]*\s+\/\b/, // chmod 777 /...
|
|
320
|
+
/\bkillall\b/,
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const SAFE_GIT_CMD_WHITELIST = /^(git (status|log|diff|add|commit|push|pull|fetch|branch|checkout|show|rev-parse|remote|tag|stash))\b/;
|
|
324
|
+
|
|
325
|
+
// Allowlist — patterns stored in ~/.crewswarm/cmd-allowlist.json
|
|
326
|
+
const CMD_ALLOWLIST_FILE = path.join(os.homedir(), ".crewswarm", "cmd-allowlist.json");
|
|
327
|
+
|
|
328
|
+
export function loadCmdAllowlist() {
|
|
329
|
+
try { return JSON.parse(fs.readFileSync(CMD_ALLOWLIST_FILE, "utf8")); } catch { return []; }
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function isCommandBlocked(cmd) {
|
|
333
|
+
return BLOCKED_CMD_PATTERNS.some(re => re.test(cmd));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function isCommandAllowlisted(cmd) {
|
|
337
|
+
const list = loadCmdAllowlist();
|
|
338
|
+
return list.some(pattern => {
|
|
339
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
340
|
+
return new RegExp(`^${escaped}`, "i").test(cmd.trim());
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Sanitize paths from agent replies — strip markdown/hallucination (backticks, trailing punctuation)
|
|
345
|
+
export function sanitizeToolPath(raw) {
|
|
346
|
+
if (typeof raw !== "string") return "";
|
|
347
|
+
let s = raw.trim().replace(/\s+/g, " ").replace(/`/g, "");
|
|
348
|
+
while (s.length > 1 && (s.endsWith(".") || s.endsWith(","))) s = s.slice(0, -1).trim();
|
|
349
|
+
s = s.replace(/^~/, os.homedir());
|
|
350
|
+
// Resolve relative paths against the configured project dir so agents
|
|
351
|
+
// that output bare filenames don't accidentally write to the crewswarm root.
|
|
352
|
+
if (!path.isAbsolute(s)) {
|
|
353
|
+
const base = _getOpencodeProjectDir() || process.cwd();
|
|
354
|
+
s = path.join(base, s);
|
|
355
|
+
}
|
|
356
|
+
return s;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function tokenizeBrowserArgs(input) {
|
|
360
|
+
const tokens = [];
|
|
361
|
+
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
362
|
+
let match;
|
|
363
|
+
while ((match = re.exec(input)) !== null) {
|
|
364
|
+
const value = match[1] ?? match[2] ?? match[3] ?? "";
|
|
365
|
+
tokens.push(value.replace(/\\(["'])/g, "$1"));
|
|
366
|
+
}
|
|
367
|
+
return tokens;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function executeToolCalls(reply, agentId, { suppressWriteIfSearchPending = false, taskId = null, projectId = null } = {}) {
|
|
371
|
+
console.log(`[tool-executor] executeToolCalls START agent=${agentId} replyLen=${reply?.length || 0}`);
|
|
372
|
+
const allowed = loadAgentToolPermissions(agentId);
|
|
373
|
+
console.log(`[tool-executor] permissions loaded for ${agentId}: ${[...allowed].join(",")}`);
|
|
374
|
+
const results = [];
|
|
375
|
+
const harnessProjectId = projectId || _getOpencodeProjectDir?.() || "global";
|
|
376
|
+
|
|
377
|
+
// If the reply contains both @@WEB_SEARCH/@@WEB_FETCH and @@WRITE_FILE in the same
|
|
378
|
+
// message, the model is writing before it has seen real search results — suppress
|
|
379
|
+
// the write so the caller can do a follow-up call with actual search data.
|
|
380
|
+
const hasPendingSearches = /@@WEB_SEARCH[ \t]+\S|@@WEB_FETCH[ \t]+https?:\/\//.test(reply);
|
|
381
|
+
const hasWrite = /@@WRITE_FILE[ \t]+\S/.test(reply);
|
|
382
|
+
const blockWrite = suppressWriteIfSearchPending && hasPendingSearches && hasWrite;
|
|
383
|
+
|
|
384
|
+
// ── @@WRITE_FILE ──────────────────────────────────────────────────────────
|
|
385
|
+
const writeRe = /@@WRITE_FILE[ \t]+([^\n]+)\n([\s\S]*?)@@END_FILE/g;
|
|
386
|
+
let m;
|
|
387
|
+
while ((m = writeRe.exec(reply)) !== null) {
|
|
388
|
+
const rawPath = sanitizeToolPath(m[1]);
|
|
389
|
+
const absPath = path.resolve(rawPath);
|
|
390
|
+
const decision = evaluateHarnessAction(agentId, harnessProjectId, {
|
|
391
|
+
tool: "write_file",
|
|
392
|
+
target: absPath,
|
|
393
|
+
});
|
|
394
|
+
if (!decision.allowed) {
|
|
395
|
+
const reason = decision.rule?.reason || "Blocked by AutoHarness";
|
|
396
|
+
results.push(`[tool:write_file] ⛔ AutoHarness blocked ${absPath}: ${reason}`);
|
|
397
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "write_file", target: absPath, outcome: "blocked", reason });
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (blockWrite) {
|
|
401
|
+
results.push(`[tool:write_file] ⏸ Write suppressed — waiting for search results first`);
|
|
402
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "write_file", target: absPath, outcome: "blocked", reason: "Write suppressed pending search results" });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (!allowed.has('write_file')) {
|
|
406
|
+
results.push(`[tool:write_file] ⛔ ${agentId} does not have write_file permission`);
|
|
407
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "write_file", target: absPath, outcome: "rejected", reason: `${agentId} does not have write_file permission` });
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const contents = m[2];
|
|
411
|
+
try {
|
|
412
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
413
|
+
fs.writeFileSync(absPath, contents, "utf8");
|
|
414
|
+
const msg = `[tool:write_file] ✅ Wrote ${contents.length} bytes → ${absPath}`;
|
|
415
|
+
results.push(msg);
|
|
416
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "write_file", target: absPath, outcome: "succeeded", reason: null });
|
|
417
|
+
console.log(`[${agentId}] ${msg}`);
|
|
418
|
+
} catch (err) {
|
|
419
|
+
const msg = `[tool:write_file] ❌ Failed to write ${absPath}: ${err.message}`;
|
|
420
|
+
results.push(msg);
|
|
421
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "write_file", target: absPath, outcome: "failed", reason: err.message });
|
|
422
|
+
console.error(`[${agentId}] ${msg}`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── @@APPEND_FILE ─────────────────────────────────────────────────────────
|
|
427
|
+
const appendRe = /@@APPEND_FILE[ \t]+([^\n]+)\n([\s\S]*?)@@END_FILE/g;
|
|
428
|
+
while ((m = appendRe.exec(reply)) !== null) {
|
|
429
|
+
const rawPath = sanitizeToolPath(m[1]);
|
|
430
|
+
const absPath = path.resolve(rawPath);
|
|
431
|
+
const decision = evaluateHarnessAction(agentId, harnessProjectId, {
|
|
432
|
+
tool: "append_file",
|
|
433
|
+
target: absPath,
|
|
434
|
+
});
|
|
435
|
+
if (!decision.allowed) {
|
|
436
|
+
const reason = decision.rule?.reason || "Blocked by AutoHarness";
|
|
437
|
+
results.push(`[tool:append_file] ⛔ AutoHarness blocked ${absPath}: ${reason}`);
|
|
438
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "append_file", target: absPath, outcome: "blocked", reason });
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (!allowed.has('write_file')) {
|
|
442
|
+
results.push(`[tool:append_file] ⛔ ${agentId} does not have write_file permission`);
|
|
443
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "append_file", target: absPath, outcome: "rejected", reason: `${agentId} does not have write_file permission` });
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const contents = m[2];
|
|
447
|
+
try {
|
|
448
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
449
|
+
fs.appendFileSync(absPath, contents, "utf8");
|
|
450
|
+
const msg = `[tool:append_file] ✅ Appended ${contents.length} bytes → ${absPath}`;
|
|
451
|
+
results.push(msg);
|
|
452
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "append_file", target: absPath, outcome: "succeeded", reason: null });
|
|
453
|
+
console.log(`[${agentId}] ${msg}`);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
const msg = `[tool:append_file] ❌ Failed to append ${absPath}: ${err.message}`;
|
|
456
|
+
results.push(msg);
|
|
457
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "append_file", target: absPath, outcome: "failed", reason: err.message });
|
|
458
|
+
console.error(`[${agentId}] ${msg}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ── @@READ_FILE ───────────────────────────────────────────────────────────
|
|
463
|
+
// Path stops at newline or next @@ so multiple @@READ_FILE on one line are parsed separately
|
|
464
|
+
const readRe = /@@READ_FILE[ \t]+([^\n@@]+)/g;
|
|
465
|
+
while ((m = readRe.exec(reply)) !== null) {
|
|
466
|
+
const filePath = sanitizeToolPath(m[1]);
|
|
467
|
+
const decision = evaluateHarnessAction(agentId, harnessProjectId, {
|
|
468
|
+
tool: "read_file",
|
|
469
|
+
target: filePath,
|
|
470
|
+
});
|
|
471
|
+
if (!decision.allowed) {
|
|
472
|
+
const reason = decision.rule?.reason || "Blocked by AutoHarness";
|
|
473
|
+
results.push(`[tool:read_file] ⛔ AutoHarness blocked ${filePath}: ${reason}`);
|
|
474
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "read_file", target: filePath, outcome: "blocked", reason });
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (!allowed.has('read_file')) {
|
|
478
|
+
results.push(`[tool:read_file] ⛔ ${agentId} does not have read_file permission`);
|
|
479
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "read_file", target: filePath, outcome: "rejected", reason: `${agentId} does not have read_file permission` });
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
484
|
+
// Docs/briefs get a higher limit — they are reference material, not code blobs
|
|
485
|
+
const isDoc = /\.(md|txt|json|yaml|yml|toml)$/i.test(filePath);
|
|
486
|
+
const readLimit = isDoc ? 12000 : 4000;
|
|
487
|
+
const snippet = content.length > readLimit ? content.slice(0, readLimit) + "\n...[truncated]" : content;
|
|
488
|
+
results.push(`[tool:read_file] 📄 ${filePath} (${content.length} bytes):\n${snippet}`);
|
|
489
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "read_file", target: filePath, outcome: "succeeded", reason: null });
|
|
490
|
+
} catch (err) {
|
|
491
|
+
results.push(`[tool:read_file] ❌ Cannot read ${filePath}: ${err.message}`);
|
|
492
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "read_file", target: filePath, outcome: "failed", reason: err.message });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ── @@MKDIR ───────────────────────────────────────────────────────────────
|
|
497
|
+
const mkdirRe = /@@MKDIR[ \t]+([^\n@@]+)/g;
|
|
498
|
+
while ((m = mkdirRe.exec(reply)) !== null) {
|
|
499
|
+
const dirPath = sanitizeToolPath(m[1]);
|
|
500
|
+
const decision = evaluateHarnessAction(agentId, harnessProjectId, {
|
|
501
|
+
tool: "mkdir",
|
|
502
|
+
target: dirPath,
|
|
503
|
+
});
|
|
504
|
+
if (!decision.allowed) {
|
|
505
|
+
const reason = decision.rule?.reason || "Blocked by AutoHarness";
|
|
506
|
+
results.push(`[tool:mkdir] ⛔ AutoHarness blocked ${dirPath}: ${reason}`);
|
|
507
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "mkdir", target: dirPath, outcome: "blocked", reason });
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (!allowed.has('mkdir')) {
|
|
511
|
+
results.push(`[tool:mkdir] ⛔ ${agentId} does not have mkdir permission`);
|
|
512
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "mkdir", target: dirPath, outcome: "rejected", reason: `${agentId} does not have mkdir permission` });
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
517
|
+
results.push(`[tool:mkdir] ✅ Created directory: ${dirPath}`);
|
|
518
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "mkdir", target: dirPath, outcome: "succeeded", reason: null });
|
|
519
|
+
} catch (err) {
|
|
520
|
+
results.push(`[tool:mkdir] ❌ Failed: ${err.message}`);
|
|
521
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "mkdir", target: dirPath, outcome: "failed", reason: err.message });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ── @@RUN_CMD ─────────────────────────────────────────────────────────────
|
|
526
|
+
const cmdRe = /@@RUN_CMD[ \t]+([^\n]+)/g;
|
|
527
|
+
while ((m = cmdRe.exec(reply)) !== null) {
|
|
528
|
+
const cmd = m[1].trim();
|
|
529
|
+
const isGit = SAFE_GIT_CMD_WHITELIST.test(cmd);
|
|
530
|
+
const decision = evaluateHarnessAction(agentId, harnessProjectId, {
|
|
531
|
+
tool: "run_cmd",
|
|
532
|
+
command: cmd,
|
|
533
|
+
});
|
|
534
|
+
if (!decision.allowed) {
|
|
535
|
+
const reason = decision.rule?.reason || "Blocked by AutoHarness";
|
|
536
|
+
results.push(`[tool:run_cmd] ⛔ AutoHarness blocked command: ${cmd} (${reason})`);
|
|
537
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "blocked", reason });
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Hard block — dangerous patterns regardless of permissions
|
|
542
|
+
if (isCommandBlocked(cmd)) {
|
|
543
|
+
results.push(`[tool:run_cmd] ⛔ Blocked dangerous command: ${cmd}`);
|
|
544
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "blocked", reason: `Blocked dangerous command: ${cmd}` });
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (isGit && !allowed.has('git') && !allowed.has('run_cmd')) {
|
|
548
|
+
results.push(`[tool:run_cmd] ⛔ ${agentId} does not have git permission`);
|
|
549
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "rejected", reason: `${agentId} does not have git permission` });
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (!isGit && !allowed.has('run_cmd')) {
|
|
553
|
+
results.push(`[tool:run_cmd] ⛔ ${agentId} does not have run_cmd permission`);
|
|
554
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "rejected", reason: `${agentId} does not have run_cmd permission` });
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ── Approval gate — skip for git, auto-approved agents, or allowlisted commands ─
|
|
559
|
+
const needsApproval = !isGit && !isAutoApproveAgent(agentId) && !isCommandAllowlisted(cmd) && _rtClientForApprovals;
|
|
560
|
+
if (needsApproval) {
|
|
561
|
+
const approvalId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
562
|
+
try {
|
|
563
|
+
_rtClientForApprovals.publish({
|
|
564
|
+
channel: "events",
|
|
565
|
+
type: "cmd.needs_approval",
|
|
566
|
+
to: "broadcast",
|
|
567
|
+
payload: { approvalId, agent: agentId, cmd, ts: new Date().toISOString() },
|
|
568
|
+
});
|
|
569
|
+
} catch (pubErr) {
|
|
570
|
+
console.warn(`[${agentId}] Could not publish cmd.needs_approval: ${pubErr?.message}`);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
console.log(`[${agentId}] ⏳ Awaiting approval to run: ${cmd}`);
|
|
574
|
+
const approved = await new Promise((resolve) => {
|
|
575
|
+
const timer = setTimeout(() => {
|
|
576
|
+
pendingCmdApprovals.delete(approvalId);
|
|
577
|
+
console.warn(`[${agentId}] cmd approval timed out (60s): ${cmd}`);
|
|
578
|
+
resolve(false);
|
|
579
|
+
}, 60000);
|
|
580
|
+
pendingCmdApprovals.set(approvalId, { resolve, timer });
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (!approved) {
|
|
584
|
+
results.push(`[tool:run_cmd] ⛔ Command rejected or timed out: \`${cmd}\``);
|
|
585
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "rejected", reason: `Command rejected or timed out: ${cmd}` });
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
console.log(`[${agentId}] ✅ cmd approved, executing: ${cmd}`);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
try {
|
|
592
|
+
const { execSync } = await import("node:child_process");
|
|
593
|
+
const out = execSync(cmd, { timeout: 15000, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
594
|
+
results.push(`[tool:run_cmd] ✅ $ ${cmd}\n${out.slice(0, 2000)}`);
|
|
595
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "succeeded", reason: null });
|
|
596
|
+
} catch (err) {
|
|
597
|
+
results.push(`[tool:run_cmd] ❌ $ ${cmd}\n${err.message}`);
|
|
598
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "run_cmd", command: cmd, outcome: "failed", reason: err.message });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// ── @@BROWSER ─────────────────────────────────────────────────────────────
|
|
603
|
+
const browserRe = /@@BROWSER[ \t]+([^\n]+)/g;
|
|
604
|
+
while ((m = browserRe.exec(reply)) !== null) {
|
|
605
|
+
if (!allowed.has('browser')) {
|
|
606
|
+
results.push(`[tool:browser] ⛔ ${agentId} does not have browser permission`);
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
const rawCommand = m[1].trim();
|
|
610
|
+
const [action, ...args] = tokenizeBrowserArgs(rawCommand);
|
|
611
|
+
try {
|
|
612
|
+
let result;
|
|
613
|
+
switch (action) {
|
|
614
|
+
case "navigate": {
|
|
615
|
+
const [url] = args;
|
|
616
|
+
if (!url) throw new Error("navigate requires a URL");
|
|
617
|
+
result = await browserNavigate(url);
|
|
618
|
+
results.push(`[tool:browser] ✅ navigate ${result.url} title="${result.title}" screenshot=${result.screenshotPath}`);
|
|
619
|
+
break;
|
|
620
|
+
}
|
|
621
|
+
case "screenshot": {
|
|
622
|
+
const [url] = args;
|
|
623
|
+
if (!url) throw new Error("screenshot requires a URL");
|
|
624
|
+
result = await browserScreenshot(url);
|
|
625
|
+
results.push(`[tool:browser] ✅ screenshot ${result.url} title="${result.title}" file=${result.screenshotPath}`);
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
case "click": {
|
|
629
|
+
const [url, selector] = args;
|
|
630
|
+
if (!url || !selector) throw new Error("click requires a URL and selector");
|
|
631
|
+
result = await browserClick(url, selector);
|
|
632
|
+
results.push(`[tool:browser] ✅ click ${result.url} selector="${result.selector}" title="${result.title}" screenshot=${result.screenshotPath}`);
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
case "type": {
|
|
636
|
+
const [url, selector, ...textParts] = args;
|
|
637
|
+
const text = textParts.join(" ");
|
|
638
|
+
if (!url || !selector || !text) throw new Error("type requires a URL, selector, and text");
|
|
639
|
+
result = await browserType(url, selector, text);
|
|
640
|
+
results.push(`[tool:browser] ✅ type ${result.url} selector="${result.selector}" text="${result.text}" title="${result.title}" screenshot=${result.screenshotPath}`);
|
|
641
|
+
break;
|
|
642
|
+
}
|
|
643
|
+
default:
|
|
644
|
+
throw new Error(action ? `Unknown browser action: ${action}` : "Missing browser action");
|
|
645
|
+
}
|
|
646
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "browser", command: rawCommand, outcome: "succeeded", reason: null });
|
|
647
|
+
} catch (err) {
|
|
648
|
+
results.push(`[tool:browser] ❌ ${err.message}`);
|
|
649
|
+
recordToolTrace({ agentId, projectId: harnessProjectId, taskId, tool: "browser", command: rawCommand, outcome: "failed", reason: err.message });
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ── @@WEB_SEARCH ──────────────────────────────────────────────────────────
|
|
654
|
+
// Uses Brave search as primary (fast, accurate), Perplexity sonar as fallback
|
|
655
|
+
const webSearchRe = /@@WEB_SEARCH[ \t]+([^\n]+)/g;
|
|
656
|
+
while ((m = webSearchRe.exec(reply)) !== null) {
|
|
657
|
+
if (!allowed.has('web_search')) {
|
|
658
|
+
results.push(`[tool:web_search] ⛔ ${agentId} does not have web_search permission`);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const query = m[1].trim();
|
|
662
|
+
try {
|
|
663
|
+
// ── Try Brave search first (fast, direct results) ──
|
|
664
|
+
const braveKey = (() => {
|
|
665
|
+
// Check crewswarm.json first, then search-tools.json, then env var
|
|
666
|
+
try {
|
|
667
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(CREWSWARM_DIR, "crewswarm.json"), "utf8"));
|
|
668
|
+
const cfgKey = cfg?.providers?.brave?.apiKey;
|
|
669
|
+
if (cfgKey) return cfgKey;
|
|
670
|
+
} catch {}
|
|
671
|
+
const stPaths = [
|
|
672
|
+
path.join(CREWSWARM_DIR, "search-tools.json"),
|
|
673
|
+
path.join(LEGACY_STATE_DIR, "search-tools.json"),
|
|
674
|
+
];
|
|
675
|
+
for (const p of stPaths) {
|
|
676
|
+
try {
|
|
677
|
+
const st = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
678
|
+
if (st?.brave?.apiKey) return st.brave.apiKey;
|
|
679
|
+
} catch {}
|
|
680
|
+
}
|
|
681
|
+
return process.env.BRAVE_API_KEY || null;
|
|
682
|
+
})();
|
|
683
|
+
|
|
684
|
+
if (braveKey) {
|
|
685
|
+
const res = await fetch(
|
|
686
|
+
`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=5&text_decorations=false`,
|
|
687
|
+
{ headers: { Accept: "application/json", "X-Subscription-Token": braveKey }, signal: AbortSignal.timeout(10000) }
|
|
688
|
+
);
|
|
689
|
+
if (res.ok) {
|
|
690
|
+
const data = await res.json();
|
|
691
|
+
const hits = (data.web?.results || []).slice(0, 5);
|
|
692
|
+
if (hits.length) {
|
|
693
|
+
const formatted = hits.map((r, i) =>
|
|
694
|
+
`${i + 1}. **${r.title}** — ${r.url}\n ${r.description || ""}`
|
|
695
|
+
).join("\n");
|
|
696
|
+
results.push(`[tool:web_search] 🔍 Results for "${query}":\n${formatted}`);
|
|
697
|
+
console.log(`[${agentId}] web_search (brave): "${query}" → ${hits.length} results`);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// ── Fallback: Perplexity sonar (web-grounded LLM) ──
|
|
704
|
+
const perplexityKey = (() => {
|
|
705
|
+
try {
|
|
706
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(CREWSWARM_DIR, "crewswarm.json"), "utf8"));
|
|
707
|
+
return cfg?.providers?.perplexity?.apiKey || null;
|
|
708
|
+
} catch { return null; }
|
|
709
|
+
})();
|
|
710
|
+
|
|
711
|
+
if (perplexityKey) {
|
|
712
|
+
const pRes = await fetch("https://api.perplexity.ai/chat/completions", {
|
|
713
|
+
method: "POST",
|
|
714
|
+
headers: { "Authorization": `Bearer ${perplexityKey}`, "Content-Type": "application/json" },
|
|
715
|
+
body: JSON.stringify({
|
|
716
|
+
model: "sonar",
|
|
717
|
+
messages: [{ role: "user", content: `Search the web and return accurate, detailed results for: ${query}\n\nInclude: key facts, URLs of official sources, pricing if relevant, and any important technical details. Be specific and factual.` }],
|
|
718
|
+
max_tokens: 1024,
|
|
719
|
+
}),
|
|
720
|
+
signal: AbortSignal.timeout(20000),
|
|
721
|
+
});
|
|
722
|
+
if (pRes.ok) {
|
|
723
|
+
const pData = await pRes.json();
|
|
724
|
+
const answer = pData.choices?.[0]?.message?.content || "";
|
|
725
|
+
const citations = (pData.citations || []).map((u, i) => `[${i+1}] ${u}`).join("\n");
|
|
726
|
+
const out = answer + (citations ? `\n\nSources:\n${citations}` : "");
|
|
727
|
+
results.push(`[tool:web_search] 🔍 Results for "${query}":\n${out}`);
|
|
728
|
+
console.log(`[${agentId}] web_search (perplexity): "${query}" → ${answer.length} chars`);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ── No search provider available ──
|
|
734
|
+
results.push(`[tool:web_search] ❌ No search provider available (no Brave or Perplexity key in crewswarm.json or search-tools.json)`);
|
|
735
|
+
} catch (err) {
|
|
736
|
+
results.push(`[tool:web_search] ❌ Search failed: ${err.message}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// ── @@WEB_FETCH ───────────────────────────────────────────────────────────
|
|
741
|
+
const webFetchRe = /@@WEB_FETCH[ \t]+(https?:\/\/[^\n]+)/g;
|
|
742
|
+
while ((m = webFetchRe.exec(reply)) !== null) {
|
|
743
|
+
if (!allowed.has('web_fetch')) {
|
|
744
|
+
results.push(`[tool:web_fetch] ⛔ ${agentId} does not have web_fetch permission`);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const url = m[1].trim();
|
|
748
|
+
try {
|
|
749
|
+
const res = await fetch(url, {
|
|
750
|
+
headers: { "User-Agent": "crewswarm/1.0 (agent fetch)" },
|
|
751
|
+
signal: AbortSignal.timeout(12000),
|
|
752
|
+
});
|
|
753
|
+
if (!res.ok) {
|
|
754
|
+
results.push(`[tool:web_fetch] ❌ HTTP ${res.status} fetching: ${url}`);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const ct = res.headers.get("content-type") || "";
|
|
758
|
+
let text = await res.text();
|
|
759
|
+
// Strip HTML tags to extract readable text
|
|
760
|
+
if (ct.includes("html")) {
|
|
761
|
+
text = text
|
|
762
|
+
.replace(/<script[\s\S]*?<\/script>/gi, "")
|
|
763
|
+
.replace(/<style[\s\S]*?<\/style>/gi, "")
|
|
764
|
+
.replace(/<[^>]+>/g, " ")
|
|
765
|
+
.replace(/\s{2,}/g, " ")
|
|
766
|
+
.trim();
|
|
767
|
+
}
|
|
768
|
+
const snippet = text.length > 8000 ? text.slice(0, 8000) + "\n...[truncated]" : text;
|
|
769
|
+
results.push(`[tool:web_fetch] 🌐 ${url} (${text.length} chars):\n${snippet}`);
|
|
770
|
+
console.log(`[${agentId}] web_fetch: ${url} → ${text.length} chars`);
|
|
771
|
+
} catch (err) {
|
|
772
|
+
results.push(`[tool:web_fetch] ❌ Fetch failed for ${url}: ${err.message}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// ── @@TELEGRAM ────────────────────────────────────────────────────────────
|
|
777
|
+
// Supports: @@TELEGRAM message (default chat) or @@TELEGRAM @Name message (contact by name)
|
|
778
|
+
const telegramRe = /@@TELEGRAM[ \t]+([^\n]+)/g;
|
|
779
|
+
while ((m = telegramRe.exec(reply)) !== null) {
|
|
780
|
+
if (!allowed.has('telegram')) {
|
|
781
|
+
results.push(`[tool:telegram] ⛔ ${agentId} does not have telegram permission`);
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
let message = m[1].trim();
|
|
785
|
+
try {
|
|
786
|
+
const cfg = _resolveConfig();
|
|
787
|
+
const tgBridge = _resolveTelegramBridgeConfig();
|
|
788
|
+
const botToken = process.env.TELEGRAM_BOT_TOKEN || cfg?.env?.TELEGRAM_BOT_TOKEN || cfg?.TELEGRAM_BOT_TOKEN || tgBridge.token || "";
|
|
789
|
+
let chatId = process.env.TELEGRAM_CHAT_ID || cfg?.env?.TELEGRAM_CHAT_ID || cfg?.TELEGRAM_CHAT_ID
|
|
790
|
+
|| (Array.isArray(tgBridge.allowedChatIds) && tgBridge.allowedChatIds.length ? String(tgBridge.allowedChatIds[0]) : "")
|
|
791
|
+
|| tgBridge.defaultChatId || "";
|
|
792
|
+
const contactNames = tgBridge.contactNames || {};
|
|
793
|
+
const atNameMatch = message.match(/^@(\S+)\s+(.*)$/s);
|
|
794
|
+
if (atNameMatch) {
|
|
795
|
+
const name = atNameMatch[1];
|
|
796
|
+
message = atNameMatch[2].trim();
|
|
797
|
+
const nameLower = name.toLowerCase();
|
|
798
|
+
const found = Object.entries(contactNames).find(([, v]) => (v || "").toLowerCase() === nameLower);
|
|
799
|
+
if (found) chatId = found[0];
|
|
800
|
+
else {
|
|
801
|
+
results.push(`[tool:telegram] ❌ No contact named "${name}" in Settings → Telegram → Contact names`);
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
chatId = chatId.trim();
|
|
806
|
+
if (!botToken || !chatId) {
|
|
807
|
+
results.push(`[tool:telegram] ❌ TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID must be set in env, ~/.crewswarm/crewswarm.json, or ~/.crewswarm/telegram-bridge.json (token + allowedChatIds or defaultChatId)`);
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: { "Content-Type": "application/json" },
|
|
813
|
+
body: JSON.stringify({ chat_id: chatId, text: `[${agentId}] ${message}`, parse_mode: "Markdown" }),
|
|
814
|
+
signal: AbortSignal.timeout(8000),
|
|
815
|
+
});
|
|
816
|
+
const data = await res.json();
|
|
817
|
+
if (!data.ok) {
|
|
818
|
+
results.push(`[tool:telegram] ❌ Telegram error: ${data.description}`);
|
|
819
|
+
} else {
|
|
820
|
+
results.push(`[tool:telegram] ✅ Sent: ${message.slice(0, 80)}${message.length > 80 ? "…" : ""}`);
|
|
821
|
+
console.log(`[${agentId}] telegram: sent message`);
|
|
822
|
+
}
|
|
823
|
+
} catch (err) {
|
|
824
|
+
results.push(`[tool:telegram] ❌ Send failed: ${err.message}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// ── @@SKILL ───────────────────────────────────────────────────────────────
|
|
829
|
+
// Format: @@SKILL skillname {"param":"value"}
|
|
830
|
+
const skillRe = /@@SKILL[ \t]+([a-zA-Z0-9_\-\.]+)[ \t]*(\{[^\n]*\})?/g;
|
|
831
|
+
while ((m = skillRe.exec(reply)) !== null) {
|
|
832
|
+
if (!allowed.has('skill')) {
|
|
833
|
+
results.push(`[tool:skill] ⛔ ${agentId} does not have skill permission`);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const skillName = m[1].trim();
|
|
837
|
+
let params = {};
|
|
838
|
+
if (m[2]) {
|
|
839
|
+
try { params = JSON.parse(m[2]); } catch { results.push(`[tool:skill] ❌ ${skillName}: bad JSON params — ${m[2].slice(0, 100)}`); continue; }
|
|
840
|
+
}
|
|
841
|
+
const skillDef = _loadSkillDef(skillName);
|
|
842
|
+
if (!skillDef) {
|
|
843
|
+
results.push(`[tool:skill] ❌ Skill "${skillName}" not found in ${SKILLS_DIR}`);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
// Merge defaults
|
|
847
|
+
const merged = { ...(skillDef.defaultParams || {}), ...params };
|
|
848
|
+
// Check requiresApproval
|
|
849
|
+
if (skillDef.requiresApproval) {
|
|
850
|
+
const crypto = await import("crypto");
|
|
851
|
+
const approvalId = crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36);
|
|
852
|
+
const pending = _loadPendingSkills();
|
|
853
|
+
pending[approvalId] = { agentId, skillName, params: merged, skillDef, createdAt: Date.now() };
|
|
854
|
+
_savePendingSkills(pending);
|
|
855
|
+
await _notifyTelegramSkillApproval(agentId, skillName, merged, approvalId);
|
|
856
|
+
results.push(`[tool:skill] 🔔 "${skillName}" requires approval. Approval ID: ${approvalId}. Approve via POST /api/skills/approve {"approvalId":"${approvalId}"} or Telegram.`);
|
|
857
|
+
console.log(`[${agentId}] skill:${skillName} awaiting approval (${approvalId})`);
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
try {
|
|
861
|
+
console.log(`[${agentId}] skill:${skillName} → ${skillDef.url?.slice(0, 60)}`);
|
|
862
|
+
const result = await _executeSkill(skillDef, merged);
|
|
863
|
+
let preview;
|
|
864
|
+
const isBenchmark = skillName === "zeroeval.benchmark" || skillName === "benchmark" || skillName === "benchmarks";
|
|
865
|
+
if (isBenchmark && Array.isArray(result) && result.length) {
|
|
866
|
+
const list = result.slice(0, 30).map(b => typeof b === "object" ? b.benchmark_id : b).join(", ");
|
|
867
|
+
preview = `${result.length} benchmarks (sample): ${list}${result.length > 30 ? ` … +${result.length - 30} more` : ""}`;
|
|
868
|
+
} else if (isBenchmark && result?.models?.length) {
|
|
869
|
+
const top = result.models.slice(0, 5).map(m => `${m.model_name}: ${((m.normalized_score ?? m.score ?? 0) * 100).toFixed(1)}%`);
|
|
870
|
+
preview = `${result.name || "Benchmark"} — top 5: ${top.join("; ")}`;
|
|
871
|
+
} else {
|
|
872
|
+
preview = typeof result === "string" ? result : JSON.stringify(result);
|
|
873
|
+
if (preview.length > 400) preview = preview.slice(0, 400) + "…";
|
|
874
|
+
}
|
|
875
|
+
results.push(`[tool:skill] ✅ ${skillName}: ${preview}`);
|
|
876
|
+
} catch (err) {
|
|
877
|
+
results.push(`[tool:skill] ❌ ${skillName} failed: ${err.message.slice(0, 200)}`);
|
|
878
|
+
console.error(`[${agentId}] skill:${skillName} error: ${err.message}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// ── @@DEFINE_SKILL ────────────────────────────────────────────────────────
|
|
883
|
+
// Format: @@DEFINE_SKILL skillname\n{json}\n@@END_SKILL
|
|
884
|
+
const defineSkillRe = /@@DEFINE_SKILL[ \t]+([a-zA-Z0-9_\-\.]+)\n([\s\S]*?)@@END_SKILL/g;
|
|
885
|
+
while ((m = defineSkillRe.exec(reply)) !== null) {
|
|
886
|
+
if (!allowed.has('define_skill')) {
|
|
887
|
+
results.push(`[tool:define_skill] ⛔ ${agentId} does not have define_skill permission`);
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
const skillName = m[1].trim();
|
|
891
|
+
const rawJson = m[2].trim();
|
|
892
|
+
let def;
|
|
893
|
+
try { def = JSON.parse(rawJson); } catch(e) {
|
|
894
|
+
results.push(`[tool:define_skill] ❌ ${skillName}: invalid JSON — ${e.message}`);
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
try {
|
|
898
|
+
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
899
|
+
const outPath = path.join(SKILLS_DIR, skillName + ".json");
|
|
900
|
+
fs.writeFileSync(outPath, JSON.stringify(def, null, 2), "utf8");
|
|
901
|
+
results.push(`[tool:define_skill] ✅ Skill "${skillName}" saved to ${outPath}`);
|
|
902
|
+
console.log(`[${agentId}] define_skill:${skillName} → ${outPath}`);
|
|
903
|
+
} catch(e) {
|
|
904
|
+
results.push(`[tool:define_skill] ❌ Failed to save skill "${skillName}": ${e.message}`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
console.log(`[tool-executor] executeToolCalls END agent=${agentId} results=${results.length}`);
|
|
909
|
+
return results;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Coding tool IDs — used for tool classification only.
|
|
913
|
+
const OPENCODE_CODING_TOOLS = new Set(["write_file"]);
|