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,1625 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* crewswarm MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes the entire crewswarm fleet as MCP tools ā 100% dynamic.
|
|
6
|
+
* Every agent and every skill discovered at runtime becomes a callable tool.
|
|
7
|
+
*
|
|
8
|
+
* Compatible with: Cursor, Claude Code CLI, OpenCode, Claude Desktop,
|
|
9
|
+
* Open WebUI, LM Studio, Aider, Continue.dev, any OpenAI-compatible tool
|
|
10
|
+
*
|
|
11
|
+
* Transports:
|
|
12
|
+
* HTTP (default) ā point your MCP client at http://localhost:5020/mcp
|
|
13
|
+
* stdio ā node scripts/mcp-server.mjs --stdio
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node scripts/mcp-server.mjs # HTTP on :5020
|
|
17
|
+
* node scripts/mcp-server.mjs --port 5021
|
|
18
|
+
* node scripts/mcp-server.mjs --stdio # for Claude Desktop / Claude Code
|
|
19
|
+
*
|
|
20
|
+
* Cursor .cursor/mcp.json:
|
|
21
|
+
* { "crewswarm": { "url": "http://localhost:5020/mcp" } }
|
|
22
|
+
*
|
|
23
|
+
* Claude Code (~/.claude/mcp.json or via --mcp-config):
|
|
24
|
+
* { "crewswarm": { "type": "http", "url": "http://localhost:5020/mcp" } }
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import http from "http";
|
|
28
|
+
import fs from "fs";
|
|
29
|
+
import path from "path";
|
|
30
|
+
import os from "os";
|
|
31
|
+
import { spawn } from "child_process";
|
|
32
|
+
import {
|
|
33
|
+
listProjectsWithMessages,
|
|
34
|
+
loadProjectMessages,
|
|
35
|
+
saveProjectMessage,
|
|
36
|
+
} from "../lib/chat/project-messages.mjs";
|
|
37
|
+
import { detectMentions } from "../lib/chat/autonomous-mentions.mjs";
|
|
38
|
+
|
|
39
|
+
// āā Config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
40
|
+
const CREW_LEAD_URL = process.env.CREW_LEAD_URL || "http://127.0.0.1:5010";
|
|
41
|
+
const PORT = parseInt(process.env.MCP_PORT || "5020");
|
|
42
|
+
const SKILLS_DIR = path.join(os.homedir(), ".crewswarm", "skills");
|
|
43
|
+
const CONFIG_PATH = path.join(os.homedir(), ".crewswarm", "crewswarm.json");
|
|
44
|
+
const CREWSWARM_CFG = path.join(os.homedir(), ".crewswarm", "crewswarm.json");
|
|
45
|
+
const STDIO_MODE = process.argv.includes("--stdio");
|
|
46
|
+
|
|
47
|
+
function getAuthToken() {
|
|
48
|
+
try {
|
|
49
|
+
return (
|
|
50
|
+
JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"))?.rt?.authToken || ""
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function crewHeaders() {
|
|
58
|
+
const token = getAuthToken();
|
|
59
|
+
return {
|
|
60
|
+
"content-type": "application/json",
|
|
61
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractMessageText(content) {
|
|
66
|
+
if (typeof content === "string") return content;
|
|
67
|
+
if (Array.isArray(content)) {
|
|
68
|
+
return content
|
|
69
|
+
.map((part) => {
|
|
70
|
+
if (!part) return "";
|
|
71
|
+
if (!part.type || part.type === "text") return String(part.text || "");
|
|
72
|
+
return "";
|
|
73
|
+
})
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join("\n");
|
|
76
|
+
}
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeOpenAIMessages(messages) {
|
|
81
|
+
if (!Array.isArray(messages)) return [];
|
|
82
|
+
return messages
|
|
83
|
+
.map((m) => ({
|
|
84
|
+
role: String(m?.role || "")
|
|
85
|
+
.trim()
|
|
86
|
+
.toLowerCase(),
|
|
87
|
+
text: extractMessageText(m?.content).trim(),
|
|
88
|
+
}))
|
|
89
|
+
.filter((m) => m.role && m.text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function composeChatPayloadFromOpenAI(
|
|
93
|
+
messages,
|
|
94
|
+
{ maxContextChars = 12000 } = {},
|
|
95
|
+
) {
|
|
96
|
+
const normalized = normalizeOpenAIMessages(messages);
|
|
97
|
+
const system = normalized
|
|
98
|
+
.filter((m) => m.role === "system")
|
|
99
|
+
.map((m) => m.text);
|
|
100
|
+
const assistant = normalized
|
|
101
|
+
.filter((m) => m.role === "assistant")
|
|
102
|
+
.map((m) => m.text);
|
|
103
|
+
const users = normalized.filter((m) => m.role === "user");
|
|
104
|
+
const lastUser = users.at(-1)?.text || "";
|
|
105
|
+
const priorUsers = users.slice(0, -1).map((m) => m.text);
|
|
106
|
+
const toolResults = normalized
|
|
107
|
+
.filter((m) => m.role === "tool")
|
|
108
|
+
.map((m) => m.text);
|
|
109
|
+
const historyTail = [...priorUsers, ...assistant].slice(-10);
|
|
110
|
+
|
|
111
|
+
const sections = [];
|
|
112
|
+
if (system.length > 0)
|
|
113
|
+
sections.push(`SYSTEM INSTRUCTIONS:\n${system.join("\n\n")}`);
|
|
114
|
+
if (historyTail.length > 0)
|
|
115
|
+
sections.push(`RECENT CONTEXT:\n${historyTail.join("\n\n")}`);
|
|
116
|
+
let context = sections.join("\n\n");
|
|
117
|
+
if (context.length > maxContextChars)
|
|
118
|
+
context = context.slice(context.length - maxContextChars);
|
|
119
|
+
if (toolResults.length > 0)
|
|
120
|
+
context += `${context ? "\n\n" : ""}TOOL RESULTS:\n${toolResults.join("\n\n")}`;
|
|
121
|
+
|
|
122
|
+
const inputChars = normalized.reduce((sum, m) => sum + m.text.length, 0);
|
|
123
|
+
return {
|
|
124
|
+
message: lastUser,
|
|
125
|
+
context,
|
|
126
|
+
messageCounts: {
|
|
127
|
+
system: system.length,
|
|
128
|
+
assistant: assistant.length,
|
|
129
|
+
user: users.length,
|
|
130
|
+
total: normalized.length,
|
|
131
|
+
},
|
|
132
|
+
inputChars,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function loadPipelineMetricsSummary(baseDir = process.cwd()) {
|
|
137
|
+
const file = path.join(baseDir, ".crew", "pipeline-metrics.jsonl");
|
|
138
|
+
try {
|
|
139
|
+
if (!fs.existsSync(file)) {
|
|
140
|
+
return {
|
|
141
|
+
runs: 0,
|
|
142
|
+
qaApproved: 0,
|
|
143
|
+
qaRejected: 0,
|
|
144
|
+
qaRoundsTotal: 0,
|
|
145
|
+
contextChunksUsed: 0,
|
|
146
|
+
contextCharsSaved: 0,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
150
|
+
const lines = raw
|
|
151
|
+
.split("\n")
|
|
152
|
+
.map((l) => l.trim())
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
let runs = 0;
|
|
155
|
+
let qaApproved = 0;
|
|
156
|
+
let qaRejected = 0;
|
|
157
|
+
let qaRoundsTotal = 0;
|
|
158
|
+
let contextChunksUsed = 0;
|
|
159
|
+
let contextCharsSaved = 0;
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
try {
|
|
162
|
+
const rec = JSON.parse(line);
|
|
163
|
+
runs += 1;
|
|
164
|
+
if (rec.qaApproved === true) qaApproved += 1;
|
|
165
|
+
if (rec.qaApproved === false) qaRejected += 1;
|
|
166
|
+
qaRoundsTotal += Number(rec.qaRounds || 0);
|
|
167
|
+
contextChunksUsed += Number(rec.contextChunksUsed || 0);
|
|
168
|
+
contextCharsSaved += Number(rec.contextCharsSaved || 0);
|
|
169
|
+
} catch {
|
|
170
|
+
// ignore malformed rows
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
runs,
|
|
175
|
+
qaApproved,
|
|
176
|
+
qaRejected,
|
|
177
|
+
qaRoundsTotal,
|
|
178
|
+
contextChunksUsed,
|
|
179
|
+
contextCharsSaved,
|
|
180
|
+
};
|
|
181
|
+
} catch {
|
|
182
|
+
return {
|
|
183
|
+
runs: 0,
|
|
184
|
+
qaApproved: 0,
|
|
185
|
+
qaRejected: 0,
|
|
186
|
+
qaRoundsTotal: 0,
|
|
187
|
+
contextChunksUsed: 0,
|
|
188
|
+
contextCharsSaved: 0,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function selectToolCallName(payload, userMessage) {
|
|
194
|
+
const tools = Array.isArray(payload?.tools) ? payload.tools : [];
|
|
195
|
+
if (tools.length === 0) return null;
|
|
196
|
+
const names = tools
|
|
197
|
+
.map((t) => String(t?.function?.name || "").trim())
|
|
198
|
+
.filter(Boolean);
|
|
199
|
+
if (names.length === 0) return null;
|
|
200
|
+
|
|
201
|
+
const choice = payload?.tool_choice;
|
|
202
|
+
if (choice === "none") return null;
|
|
203
|
+
if (choice && typeof choice === "object") {
|
|
204
|
+
const forced = String(choice?.function?.name || "").trim();
|
|
205
|
+
if (forced && names.includes(forced)) return forced;
|
|
206
|
+
}
|
|
207
|
+
if (choice === "required") return names[0];
|
|
208
|
+
if (choice && choice !== "auto") return null;
|
|
209
|
+
|
|
210
|
+
const lower = String(userMessage || "").toLowerCase();
|
|
211
|
+
const likelyAction =
|
|
212
|
+
/\b(build|implement|write|create|edit|refactor|fix|change|update|run|test|analyze)\b/.test(
|
|
213
|
+
lower,
|
|
214
|
+
);
|
|
215
|
+
return likelyAction ? names[0] : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildToolCallResponse({ model, stream, toolName, task }) {
|
|
219
|
+
const completionId = `chatcmpl-${Date.now().toString(36)}`;
|
|
220
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
221
|
+
const toolCall = {
|
|
222
|
+
id: `call_${Date.now().toString(36)}`,
|
|
223
|
+
type: "function",
|
|
224
|
+
function: {
|
|
225
|
+
name: toolName,
|
|
226
|
+
arguments: JSON.stringify({ task: String(task || "") }),
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (stream) {
|
|
231
|
+
return {
|
|
232
|
+
streamChunks: [
|
|
233
|
+
{
|
|
234
|
+
id: completionId,
|
|
235
|
+
object: "chat.completion.chunk",
|
|
236
|
+
created: ts,
|
|
237
|
+
model,
|
|
238
|
+
choices: [
|
|
239
|
+
{
|
|
240
|
+
index: 0,
|
|
241
|
+
delta: { role: "assistant", tool_calls: [toolCall] },
|
|
242
|
+
finish_reason: null,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: completionId,
|
|
248
|
+
object: "chat.completion.chunk",
|
|
249
|
+
created: ts,
|
|
250
|
+
model,
|
|
251
|
+
choices: [{ index: 0, delta: {}, finish_reason: "tool_calls" }],
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
json: {
|
|
259
|
+
id: completionId,
|
|
260
|
+
object: "chat.completion",
|
|
261
|
+
created: ts,
|
|
262
|
+
model,
|
|
263
|
+
choices: [
|
|
264
|
+
{
|
|
265
|
+
index: 0,
|
|
266
|
+
message: { role: "assistant", content: "", tool_calls: [toolCall] },
|
|
267
|
+
finish_reason: "tool_calls",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
usage: {
|
|
271
|
+
prompt_tokens: Math.ceil(String(task || "").length / 4),
|
|
272
|
+
completion_tokens: 1,
|
|
273
|
+
total_tokens: Math.ceil(String(task || "").length / 4) + 1,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// āā Dynamic agent list āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
280
|
+
function loadAgents() {
|
|
281
|
+
try {
|
|
282
|
+
const cfg = JSON.parse(fs.readFileSync(CREWSWARM_CFG, "utf8"));
|
|
283
|
+
return (cfg.agents || []).map((a) => ({
|
|
284
|
+
id: a.id,
|
|
285
|
+
name: a.identity?.name || a.name || a.id,
|
|
286
|
+
emoji: a.identity?.emoji || a.emoji || "",
|
|
287
|
+
role: a.identity?.theme || a._role || "",
|
|
288
|
+
model: a.model || "",
|
|
289
|
+
}));
|
|
290
|
+
} catch {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// āā Dynamic skill list āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
296
|
+
function loadSkills() {
|
|
297
|
+
const skills = [];
|
|
298
|
+
if (!fs.existsSync(SKILLS_DIR)) return skills;
|
|
299
|
+
try {
|
|
300
|
+
for (const entry of fs.readdirSync(SKILLS_DIR, { withFileTypes: true })) {
|
|
301
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
302
|
+
try {
|
|
303
|
+
const def = JSON.parse(
|
|
304
|
+
fs.readFileSync(path.join(SKILLS_DIR, entry.name), "utf8"),
|
|
305
|
+
);
|
|
306
|
+
skills.push({
|
|
307
|
+
name: entry.name.replace(".json", ""),
|
|
308
|
+
description: def.description || "",
|
|
309
|
+
type: "json",
|
|
310
|
+
});
|
|
311
|
+
} catch {}
|
|
312
|
+
}
|
|
313
|
+
if (entry.isDirectory()) {
|
|
314
|
+
const md = path.join(SKILLS_DIR, entry.name, "SKILL.md");
|
|
315
|
+
if (fs.existsSync(md)) {
|
|
316
|
+
const raw = fs.readFileSync(md, "utf8");
|
|
317
|
+
const descMatch = raw.match(/^description:\s*(.+)$/m);
|
|
318
|
+
skills.push({
|
|
319
|
+
name: entry.name,
|
|
320
|
+
description: descMatch?.[1]?.trim() || "",
|
|
321
|
+
type: "skill-md",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch {}
|
|
327
|
+
return skills;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// āā GitNexus MCP Bridge (external MCP server integration) āāāāāāāāāāāāāāāāāāāāā
|
|
331
|
+
let gitnexusProcess = null;
|
|
332
|
+
let gitnexusTools = [];
|
|
333
|
+
let gitnexusReady = false;
|
|
334
|
+
const GITNEXUS_ENABLED = /^(1|true|yes|on)$/i.test(
|
|
335
|
+
String(process.env.CREWSWARM_GITNEXUS_ENABLED || ""),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
async function initGitNexusBridge() {
|
|
339
|
+
if (!GITNEXUS_ENABLED) return false;
|
|
340
|
+
// Check if GitNexus is installed and has indexed repos
|
|
341
|
+
try {
|
|
342
|
+
const { execSync } = await import("child_process");
|
|
343
|
+
const statusOutput = execSync("npx gitnexus list 2>/dev/null", {
|
|
344
|
+
encoding: "utf8",
|
|
345
|
+
timeout: 3000,
|
|
346
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// If no repos indexed, skip
|
|
350
|
+
if (!statusOutput || statusOutput.includes("No repositories indexed")) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Start GitNexus MCP server in stdio mode
|
|
355
|
+
gitnexusProcess = spawn("npx", ["gitnexus", "mcp"], {
|
|
356
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
let initBuffer = "";
|
|
360
|
+
|
|
361
|
+
return new Promise((resolve) => {
|
|
362
|
+
const timeout = setTimeout(() => {
|
|
363
|
+
console.error("[GitNexus] MCP bridge timeout - continuing without it");
|
|
364
|
+
gitnexusProcess?.kill();
|
|
365
|
+
gitnexusProcess = null;
|
|
366
|
+
resolve(false);
|
|
367
|
+
}, 5000);
|
|
368
|
+
|
|
369
|
+
gitnexusProcess.stdout.on("data", (chunk) => {
|
|
370
|
+
initBuffer += chunk.toString();
|
|
371
|
+
const lines = initBuffer.split("\n");
|
|
372
|
+
initBuffer = lines.pop() || "";
|
|
373
|
+
|
|
374
|
+
for (const line of lines) {
|
|
375
|
+
if (!line.trim()) continue;
|
|
376
|
+
try {
|
|
377
|
+
const msg = JSON.parse(line);
|
|
378
|
+
// Handle initialize response to get tools/list
|
|
379
|
+
if (msg.id === "init" && msg.result) {
|
|
380
|
+
clearTimeout(timeout);
|
|
381
|
+
// Now request tools list
|
|
382
|
+
sendGitNexusMessage({
|
|
383
|
+
jsonrpc: "2.0",
|
|
384
|
+
id: "tools-list",
|
|
385
|
+
method: "tools/list",
|
|
386
|
+
})
|
|
387
|
+
.then((resp) => {
|
|
388
|
+
if (resp.result?.tools) {
|
|
389
|
+
gitnexusTools = resp.result.tools.map((t) => ({
|
|
390
|
+
...t,
|
|
391
|
+
name: `gitnexus_${t.name}`,
|
|
392
|
+
description: `[GitNexus] ${t.description}`,
|
|
393
|
+
}));
|
|
394
|
+
gitnexusReady = true;
|
|
395
|
+
console.log(
|
|
396
|
+
`[GitNexus] MCP bridge ready - ${gitnexusTools.length} tools available`,
|
|
397
|
+
);
|
|
398
|
+
resolve(true);
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
.catch(() => resolve(false));
|
|
402
|
+
}
|
|
403
|
+
} catch {}
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
gitnexusProcess.stderr.on("data", (chunk) => {
|
|
408
|
+
// Ignore stderr noise during init
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
gitnexusProcess.on("exit", () => {
|
|
412
|
+
gitnexusProcess = null;
|
|
413
|
+
gitnexusReady = false;
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Send initialize request
|
|
417
|
+
gitnexusProcess.stdin.write(
|
|
418
|
+
JSON.stringify({
|
|
419
|
+
jsonrpc: "2.0",
|
|
420
|
+
id: "init",
|
|
421
|
+
method: "initialize",
|
|
422
|
+
params: {
|
|
423
|
+
protocolVersion: "2024-11-05",
|
|
424
|
+
capabilities: {},
|
|
425
|
+
clientInfo: { name: "crewswarm-mcp-bridge", version: "1.0.0" },
|
|
426
|
+
},
|
|
427
|
+
}) + "\n",
|
|
428
|
+
);
|
|
429
|
+
});
|
|
430
|
+
} catch (e) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function sendGitNexusMessage(msg) {
|
|
436
|
+
return new Promise((resolve, reject) => {
|
|
437
|
+
if (!gitnexusProcess || !gitnexusReady) {
|
|
438
|
+
reject(new Error("GitNexus MCP not ready"));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let buffer = "";
|
|
443
|
+
const listener = (chunk) => {
|
|
444
|
+
buffer += chunk.toString();
|
|
445
|
+
const lines = buffer.split("\n");
|
|
446
|
+
buffer = lines.pop() || "";
|
|
447
|
+
|
|
448
|
+
for (const line of lines) {
|
|
449
|
+
if (!line.trim()) continue;
|
|
450
|
+
try {
|
|
451
|
+
const resp = JSON.parse(line);
|
|
452
|
+
if (resp.id === msg.id) {
|
|
453
|
+
gitnexusProcess.stdout.off("data", listener);
|
|
454
|
+
resolve(resp);
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
} catch {}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
gitnexusProcess.stdout.on("data", listener);
|
|
462
|
+
gitnexusProcess.stdin.write(JSON.stringify(msg) + "\n");
|
|
463
|
+
|
|
464
|
+
// Timeout after 30s
|
|
465
|
+
setTimeout(() => {
|
|
466
|
+
gitnexusProcess.stdout.off("data", listener);
|
|
467
|
+
reject(new Error("GitNexus MCP call timeout"));
|
|
468
|
+
}, 30000);
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function callGitNexusTool(toolName, args) {
|
|
473
|
+
const actualToolName = toolName.replace("gitnexus_", "");
|
|
474
|
+
try {
|
|
475
|
+
const resp = await sendGitNexusMessage({
|
|
476
|
+
jsonrpc: "2.0",
|
|
477
|
+
id: `call-${Date.now()}`,
|
|
478
|
+
method: "tools/call",
|
|
479
|
+
params: { name: actualToolName, arguments: args },
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
if (resp.result) {
|
|
483
|
+
return resp.result;
|
|
484
|
+
}
|
|
485
|
+
if (resp.error) {
|
|
486
|
+
return { error: resp.error.message || "GitNexus tool call failed" };
|
|
487
|
+
}
|
|
488
|
+
return { error: "Unknown GitNexus error" };
|
|
489
|
+
} catch (e) {
|
|
490
|
+
return { error: `GitNexus bridge error: ${e.message}` };
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// āā MCP tool definitions āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
495
|
+
function buildToolList() {
|
|
496
|
+
const agents = loadAgents();
|
|
497
|
+
const skills = loadSkills();
|
|
498
|
+
|
|
499
|
+
const tools = [
|
|
500
|
+
// āā Core tools āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
501
|
+
{
|
|
502
|
+
name: "dispatch_agent",
|
|
503
|
+
description: [
|
|
504
|
+
"Send a task to any specialist agent in the crewswarm fleet and wait for the result.",
|
|
505
|
+
"Use this when you need a specialist: security audit, complex code refactor, QA testing, PM planning, copywriting, data analysis.",
|
|
506
|
+
"Each agent runs its own LLM (may be different from yours) with a specialized system prompt.",
|
|
507
|
+
"Rate-limited on your main account? Route through crewswarm agents running on Groq, Mistral, DeepSeek, or local Ollama.",
|
|
508
|
+
"",
|
|
509
|
+
`Available agents: ${agents.map((a) => `${a.emoji} ${a.name} (${a.id}${a.role ? " Ā· " + a.role : ""})`).join(", ")}`,
|
|
510
|
+
].join("\n"),
|
|
511
|
+
inputSchema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
properties: {
|
|
514
|
+
agent: {
|
|
515
|
+
type: "string",
|
|
516
|
+
description: `Agent ID to dispatch to. Options: ${agents.map((a) => a.id).join(", ")}`,
|
|
517
|
+
enum: agents.map((a) => a.id),
|
|
518
|
+
},
|
|
519
|
+
task: {
|
|
520
|
+
type: "string",
|
|
521
|
+
description:
|
|
522
|
+
"The task for the agent. Be specific ā include file paths, requirements, and context.",
|
|
523
|
+
},
|
|
524
|
+
timeout_seconds: {
|
|
525
|
+
type: "number",
|
|
526
|
+
description:
|
|
527
|
+
"Max seconds to wait for agent reply (default 90, max 300)",
|
|
528
|
+
default: 90,
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
required: ["agent", "task"],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: "list_agents",
|
|
536
|
+
description:
|
|
537
|
+
"List all available crewswarm agents with their specialties, models, and current status.",
|
|
538
|
+
inputSchema: { type: "object", properties: {} },
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
name: "run_pipeline",
|
|
542
|
+
description: [
|
|
543
|
+
"Run a multi-agent pipeline where each stage passes output to the next.",
|
|
544
|
+
"Use for complex multi-step work: plan ā code ā test ā review.",
|
|
545
|
+
"Stages run sequentially; each stage's reply is injected as context into the next.",
|
|
546
|
+
].join("\n"),
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: "object",
|
|
549
|
+
properties: {
|
|
550
|
+
stages: {
|
|
551
|
+
type: "array",
|
|
552
|
+
description: "Pipeline stages to run in order",
|
|
553
|
+
items: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
agent: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "Agent ID for this stage",
|
|
559
|
+
},
|
|
560
|
+
task: { type: "string", description: "Task for this agent" },
|
|
561
|
+
},
|
|
562
|
+
required: ["agent", "task"],
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
required: ["stages"],
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "chat_stinki",
|
|
571
|
+
description: [
|
|
572
|
+
"Talk directly to Stinki (crew-lead) ā the crewswarm commander.",
|
|
573
|
+
"Use for: roadmap questions, dispatching complex multi-agent workflows, asking about the codebase, getting Stinki to coordinate the crew.",
|
|
574
|
+
"Stinki can read files, search the web, dispatch agents, and roast you if you're being stupid.",
|
|
575
|
+
].join("\n"),
|
|
576
|
+
inputSchema: {
|
|
577
|
+
type: "object",
|
|
578
|
+
properties: {
|
|
579
|
+
message: { type: "string", description: "Your message to Stinki" },
|
|
580
|
+
},
|
|
581
|
+
required: ["message"],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "crewswarm_status",
|
|
586
|
+
description:
|
|
587
|
+
"Get live status of all crewswarm agents ā which are running, their models, and recent task telemetry.",
|
|
588
|
+
inputSchema: { type: "object", properties: {} },
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "smart_dispatch",
|
|
592
|
+
description: [
|
|
593
|
+
"Analyze a task and get a multi-agent breakdown BEFORE executing ā returns the proposed plan without firing anything.",
|
|
594
|
+
"Use this when you're unsure how to break down a complex task. Returns score (1-5), suggested agents, and step-by-step breakdown.",
|
|
595
|
+
"After reviewing the plan, call run_pipeline with the returned stages to execute, or call dispatch_agent for a single agent.",
|
|
596
|
+
"Example: smart_dispatch('build auth system with JWT') ā { score: 4, agents: ['crew-pm','crew-coder-back','crew-qa'], breakdown: ['plan spec','build endpoints','write tests'] }",
|
|
597
|
+
].join("\n"),
|
|
598
|
+
inputSchema: {
|
|
599
|
+
type: "object",
|
|
600
|
+
properties: {
|
|
601
|
+
task: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description:
|
|
604
|
+
"The task to analyze and break down into a multi-agent plan",
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
required: ["task"],
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: "pipeline_metrics",
|
|
612
|
+
description:
|
|
613
|
+
"Return aggregated pipeline QA/context metrics from .crew/pipeline-metrics.jsonl.",
|
|
614
|
+
inputSchema: { type: "object", properties: {} },
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
name: "chat_send",
|
|
618
|
+
description:
|
|
619
|
+
"Write a message into the shared crewswarm channel history without dispatching work. Use this for real channel participation.",
|
|
620
|
+
inputSchema: {
|
|
621
|
+
type: "object",
|
|
622
|
+
properties: {
|
|
623
|
+
channel: {
|
|
624
|
+
type: "string",
|
|
625
|
+
description: "Channel/project id. Defaults to general.",
|
|
626
|
+
default: "general",
|
|
627
|
+
},
|
|
628
|
+
content: {
|
|
629
|
+
type: "string",
|
|
630
|
+
description: "Message content to post into the channel.",
|
|
631
|
+
},
|
|
632
|
+
actor: {
|
|
633
|
+
type: "string",
|
|
634
|
+
description: "Agent/client name to attribute the message to.",
|
|
635
|
+
default: "mcp",
|
|
636
|
+
},
|
|
637
|
+
threadId: {
|
|
638
|
+
type: "string",
|
|
639
|
+
description:
|
|
640
|
+
"Optional shared thread id for replying in an existing thread.",
|
|
641
|
+
},
|
|
642
|
+
parentId: {
|
|
643
|
+
type: "string",
|
|
644
|
+
description: "Optional parent message id for reply linkage.",
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
required: ["content"],
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: "chat_read",
|
|
652
|
+
description:
|
|
653
|
+
"Read recent shared channel messages from crewswarm history.",
|
|
654
|
+
inputSchema: {
|
|
655
|
+
type: "object",
|
|
656
|
+
properties: {
|
|
657
|
+
channel: {
|
|
658
|
+
type: "string",
|
|
659
|
+
description: "Channel/project id. Defaults to general.",
|
|
660
|
+
default: "general",
|
|
661
|
+
},
|
|
662
|
+
limit: {
|
|
663
|
+
type: "number",
|
|
664
|
+
description: "Maximum number of recent messages to return.",
|
|
665
|
+
default: 20,
|
|
666
|
+
},
|
|
667
|
+
threadId: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "Only return messages from a specific shared thread.",
|
|
670
|
+
},
|
|
671
|
+
mentionsFor: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description:
|
|
674
|
+
"Only return messages that explicitly mention this agent/client id.",
|
|
675
|
+
},
|
|
676
|
+
since: {
|
|
677
|
+
type: "number",
|
|
678
|
+
description: "Only return messages at or after this unix ms timestamp.",
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: "chat_channels",
|
|
685
|
+
description:
|
|
686
|
+
"List known shared channels/projects with recent activity.",
|
|
687
|
+
inputSchema: { type: "object", properties: {} },
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
name: "chat_who",
|
|
691
|
+
description:
|
|
692
|
+
"Show recent participants in a shared channel based on message history.",
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: "object",
|
|
695
|
+
properties: {
|
|
696
|
+
channel: {
|
|
697
|
+
type: "string",
|
|
698
|
+
description: "Channel/project id. Defaults to general.",
|
|
699
|
+
default: "general",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
];
|
|
705
|
+
|
|
706
|
+
// āā One tool per skill āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
707
|
+
for (const skill of skills) {
|
|
708
|
+
tools.push({
|
|
709
|
+
name: `skill_${skill.name.replace(/[^a-zA-Z0-9_]/g, "_")}`,
|
|
710
|
+
description: `Run crewswarm skill: ${skill.name}. ${skill.description}`,
|
|
711
|
+
inputSchema: {
|
|
712
|
+
type: "object",
|
|
713
|
+
properties: {
|
|
714
|
+
params: {
|
|
715
|
+
type: "object",
|
|
716
|
+
description: "Parameters to pass to the skill",
|
|
717
|
+
additionalProperties: true,
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// āā GitNexus MCP bridge tools āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
725
|
+
if (gitnexusReady && gitnexusTools.length > 0) {
|
|
726
|
+
tools.push(...gitnexusTools);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return tools;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// āā Tool execution āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
733
|
+
async function callTool(name, args) {
|
|
734
|
+
// dispatch_agent ā dispatch and poll for result
|
|
735
|
+
if (name === "dispatch_agent") {
|
|
736
|
+
const { agent, task, timeout_seconds = 90 } = args;
|
|
737
|
+
const maxMs = Math.min((timeout_seconds || 90) * 1000, 300_000);
|
|
738
|
+
try {
|
|
739
|
+
// Dispatch
|
|
740
|
+
const dispatchRes = await fetch(`${CREW_LEAD_URL}/api/dispatch`, {
|
|
741
|
+
method: "POST",
|
|
742
|
+
headers: crewHeaders(),
|
|
743
|
+
body: JSON.stringify({ agent, task, source: "mcp", via: "mcp-tool" }),
|
|
744
|
+
signal: AbortSignal.timeout(10_000),
|
|
745
|
+
});
|
|
746
|
+
const dispatched = await dispatchRes.json();
|
|
747
|
+
if (!dispatched.ok && !dispatched.taskId) {
|
|
748
|
+
return { error: dispatched.error || "Dispatch failed" };
|
|
749
|
+
}
|
|
750
|
+
const taskId = dispatched.taskId;
|
|
751
|
+
|
|
752
|
+
// Poll for result
|
|
753
|
+
const start = Date.now();
|
|
754
|
+
while (Date.now() - start < maxMs) {
|
|
755
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
756
|
+
try {
|
|
757
|
+
const statusRes = await fetch(
|
|
758
|
+
`${CREW_LEAD_URL}/api/status/${taskId}`,
|
|
759
|
+
{
|
|
760
|
+
headers: crewHeaders(),
|
|
761
|
+
signal: AbortSignal.timeout(5_000),
|
|
762
|
+
},
|
|
763
|
+
);
|
|
764
|
+
const status = await statusRes.json();
|
|
765
|
+
if (status.status === "completed" || status.reply) {
|
|
766
|
+
return {
|
|
767
|
+
agent,
|
|
768
|
+
task_id: taskId,
|
|
769
|
+
status: "completed",
|
|
770
|
+
reply: status.reply || status.result || "(no reply)",
|
|
771
|
+
model: status.model || "",
|
|
772
|
+
elapsed_ms: Date.now() - start,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
if (status.status === "failed" || status.error) {
|
|
776
|
+
return {
|
|
777
|
+
agent,
|
|
778
|
+
task_id: taskId,
|
|
779
|
+
status: "failed",
|
|
780
|
+
error: status.error || "Task failed",
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
} catch {}
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
agent,
|
|
787
|
+
task_id: taskId,
|
|
788
|
+
status: "timeout",
|
|
789
|
+
error: `Agent did not respond within ${timeout_seconds}s. Task is still running ā check dashboard.`,
|
|
790
|
+
};
|
|
791
|
+
} catch (e) {
|
|
792
|
+
return { error: `crew-lead unreachable: ${e.message}` };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// list_agents
|
|
797
|
+
if (name === "list_agents") {
|
|
798
|
+
try {
|
|
799
|
+
const res = await fetch(`${CREW_LEAD_URL}/api/agents`, {
|
|
800
|
+
headers: crewHeaders(),
|
|
801
|
+
signal: AbortSignal.timeout(8_000),
|
|
802
|
+
});
|
|
803
|
+
const d = await res.json();
|
|
804
|
+
const agents = (d.agents || loadAgents()).map((a) => ({
|
|
805
|
+
id: a.id,
|
|
806
|
+
name: a.identity?.name || a.name || a.id,
|
|
807
|
+
emoji: a.identity?.emoji || a.emoji || "",
|
|
808
|
+
model: a.model || "",
|
|
809
|
+
online: a.online ?? a.alive ?? null,
|
|
810
|
+
tools: a.tools || [],
|
|
811
|
+
}));
|
|
812
|
+
return { agents, count: agents.length };
|
|
813
|
+
} catch (e) {
|
|
814
|
+
return {
|
|
815
|
+
agents: loadAgents(),
|
|
816
|
+
note: "crew-lead offline ā showing config data only",
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// crewswarm_status
|
|
822
|
+
if (name === "crewswarm_status") {
|
|
823
|
+
try {
|
|
824
|
+
const res = await fetch(`${CREW_LEAD_URL}/api/health`, {
|
|
825
|
+
headers: crewHeaders(),
|
|
826
|
+
signal: AbortSignal.timeout(8_000),
|
|
827
|
+
});
|
|
828
|
+
const d = await res.json();
|
|
829
|
+
return {
|
|
830
|
+
ok: d.ok,
|
|
831
|
+
agents_online: (d.agents || []).filter((a) => a.online || a.alive)
|
|
832
|
+
.length,
|
|
833
|
+
agents_total: (d.agents || []).length,
|
|
834
|
+
agents: (d.agents || []).map((a) => ({
|
|
835
|
+
id: a.id,
|
|
836
|
+
name: a.identity?.name || a.name || a.id,
|
|
837
|
+
online: a.online ?? a.alive ?? false,
|
|
838
|
+
model: a.model || "",
|
|
839
|
+
})),
|
|
840
|
+
telemetry_recent: (d.telemetry || []).slice(-5),
|
|
841
|
+
};
|
|
842
|
+
} catch (e) {
|
|
843
|
+
return {
|
|
844
|
+
error: `crew-lead unreachable: ${e.message}`,
|
|
845
|
+
note: "Is crewswarm running? Try: npm run restart-all",
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// chat_stinki
|
|
851
|
+
if (name === "chat_stinki") {
|
|
852
|
+
const { message } = args;
|
|
853
|
+
try {
|
|
854
|
+
const res = await fetch(`${CREW_LEAD_URL}/chat`, {
|
|
855
|
+
method: "POST",
|
|
856
|
+
headers: crewHeaders(),
|
|
857
|
+
body: JSON.stringify({ message, sessionId: "mcp" }),
|
|
858
|
+
signal: AbortSignal.timeout(120_000),
|
|
859
|
+
});
|
|
860
|
+
const d = await res.json();
|
|
861
|
+
return { reply: d.reply || d.message || d.text || JSON.stringify(d) };
|
|
862
|
+
} catch (e) {
|
|
863
|
+
return { error: `crew-lead unreachable: ${e.message}` };
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// run_pipeline
|
|
868
|
+
if (name === "run_pipeline") {
|
|
869
|
+
const { stages } = args;
|
|
870
|
+
if (!Array.isArray(stages) || !stages.length)
|
|
871
|
+
return { error: "stages must be a non-empty array" };
|
|
872
|
+
const results = [];
|
|
873
|
+
let previousOutput = "";
|
|
874
|
+
for (const stage of stages) {
|
|
875
|
+
const task = previousOutput
|
|
876
|
+
? `${stage.task}\n\n[Previous step output]:\n${previousOutput.slice(0, 2000)}`
|
|
877
|
+
: stage.task;
|
|
878
|
+
const result = await callTool("dispatch_agent", {
|
|
879
|
+
agent: stage.agent,
|
|
880
|
+
task,
|
|
881
|
+
timeout_seconds: 120,
|
|
882
|
+
});
|
|
883
|
+
results.push({ agent: stage.agent, ...result });
|
|
884
|
+
previousOutput = result.reply || result.error || "";
|
|
885
|
+
if (result.status === "failed" || result.error) break;
|
|
886
|
+
}
|
|
887
|
+
return { stages_run: results.length, stages_total: stages.length, results };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// smart_dispatch ā get breakdown plan without executing
|
|
891
|
+
if (name === "smart_dispatch") {
|
|
892
|
+
const { task } = args;
|
|
893
|
+
if (!task) return { error: "task is required" };
|
|
894
|
+
try {
|
|
895
|
+
const res = await fetch(`${CREW_LEAD_URL}/api/classify`, {
|
|
896
|
+
method: "POST",
|
|
897
|
+
headers: crewHeaders(),
|
|
898
|
+
body: JSON.stringify({ task }),
|
|
899
|
+
signal: AbortSignal.timeout(10_000),
|
|
900
|
+
});
|
|
901
|
+
const plan = await res.json();
|
|
902
|
+
if (!plan.ok) return { error: plan.error || "classify failed" };
|
|
903
|
+
|
|
904
|
+
const { score, reason, agents = [], breakdown = [], skipped } = plan;
|
|
905
|
+
const complexity =
|
|
906
|
+
score <= 2 ? "simple" : score === 3 ? "moderate" : "complex";
|
|
907
|
+
|
|
908
|
+
// Build ready-to-use pipeline stages from the breakdown
|
|
909
|
+
const pipeline_stages =
|
|
910
|
+
agents.length > 0 && breakdown.length > 0
|
|
911
|
+
? breakdown.map((step, i) => ({
|
|
912
|
+
agent: agents[i] || agents[agents.length - 1],
|
|
913
|
+
task: step,
|
|
914
|
+
}))
|
|
915
|
+
: [];
|
|
916
|
+
|
|
917
|
+
return {
|
|
918
|
+
score,
|
|
919
|
+
complexity,
|
|
920
|
+
reason,
|
|
921
|
+
agents,
|
|
922
|
+
breakdown,
|
|
923
|
+
skipped: skipped || false,
|
|
924
|
+
pipeline_stages,
|
|
925
|
+
next_steps:
|
|
926
|
+
score >= 4
|
|
927
|
+
? `Call run_pipeline with pipeline_stages to execute, or customize the stages first.`
|
|
928
|
+
: score >= 3
|
|
929
|
+
? `Call dispatch_agent("${agents[0] || "crew-coder"}", task) to execute.`
|
|
930
|
+
: `Simple task ā call dispatch_agent or handle directly.`,
|
|
931
|
+
};
|
|
932
|
+
} catch (e) {
|
|
933
|
+
return { error: `classify failed: ${e.message}` };
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (name === "pipeline_metrics") {
|
|
938
|
+
const metrics = loadPipelineMetricsSummary(process.cwd());
|
|
939
|
+
const avgRounds =
|
|
940
|
+
metrics.runs > 0 ? metrics.qaRoundsTotal / metrics.runs : 0;
|
|
941
|
+
return {
|
|
942
|
+
content: [
|
|
943
|
+
{
|
|
944
|
+
type: "text",
|
|
945
|
+
text: JSON.stringify(
|
|
946
|
+
{
|
|
947
|
+
...metrics,
|
|
948
|
+
qaRoundsAvg: Number(avgRounds.toFixed(2)),
|
|
949
|
+
},
|
|
950
|
+
null,
|
|
951
|
+
2,
|
|
952
|
+
),
|
|
953
|
+
},
|
|
954
|
+
],
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (name === "chat_send") {
|
|
959
|
+
const channel = String(args.channel || "general").trim() || "general";
|
|
960
|
+
const actor = String(args.actor || "mcp").trim() || "mcp";
|
|
961
|
+
const content = String(args.content || "").trim();
|
|
962
|
+
const threadId = String(args.threadId || "").trim() || null;
|
|
963
|
+
const parentId = String(args.parentId || "").trim() || null;
|
|
964
|
+
if (!content) return { error: "content is required" };
|
|
965
|
+
const mentions = detectMentions(content);
|
|
966
|
+
const id = saveProjectMessage(channel, {
|
|
967
|
+
source: "agent",
|
|
968
|
+
role: "assistant",
|
|
969
|
+
content,
|
|
970
|
+
agent: actor,
|
|
971
|
+
threadId,
|
|
972
|
+
parentId,
|
|
973
|
+
metadata: {
|
|
974
|
+
agentName: actor,
|
|
975
|
+
via: "mcp",
|
|
976
|
+
channel,
|
|
977
|
+
...(mentions.length ? { mentions } : {}),
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
return { ok: true, channel, id, actor, threadId, parentId, mentions };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (name === "chat_read") {
|
|
984
|
+
const channel = String(args.channel || "general").trim() || "general";
|
|
985
|
+
const limit = Math.max(1, Math.min(Number(args.limit || 20), 200));
|
|
986
|
+
const threadId = String(args.threadId || "").trim() || null;
|
|
987
|
+
const mentionsFor = String(args.mentionsFor || "").trim() || null;
|
|
988
|
+
const since = Number(args.since || 0) || null;
|
|
989
|
+
const messages = loadProjectMessages(channel, {
|
|
990
|
+
limit,
|
|
991
|
+
...(threadId && { threadId }),
|
|
992
|
+
...(mentionsFor && { mentionedAgent: mentionsFor }),
|
|
993
|
+
...(since ? { since } : {}),
|
|
994
|
+
}).map((msg) => ({
|
|
995
|
+
id: msg.id,
|
|
996
|
+
ts: msg.ts,
|
|
997
|
+
source: msg.source,
|
|
998
|
+
role: msg.role,
|
|
999
|
+
content: msg.content,
|
|
1000
|
+
agent: msg.agent,
|
|
1001
|
+
threadId: msg.threadId || null,
|
|
1002
|
+
parentId: msg.parentId || null,
|
|
1003
|
+
mentions: msg.metadata?.mentions || [],
|
|
1004
|
+
}));
|
|
1005
|
+
return {
|
|
1006
|
+
ok: true,
|
|
1007
|
+
channel,
|
|
1008
|
+
count: messages.length,
|
|
1009
|
+
threadId,
|
|
1010
|
+
mentionsFor,
|
|
1011
|
+
messages,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (name === "chat_channels") {
|
|
1016
|
+
const projects = listProjectsWithMessages();
|
|
1017
|
+
const channels = [
|
|
1018
|
+
{ channel: "general", lastActivity: null, messageCount: 0 },
|
|
1019
|
+
...projects.map((project) => ({
|
|
1020
|
+
channel: project.projectId,
|
|
1021
|
+
lastActivity: project.lastActivity,
|
|
1022
|
+
messageCount: project.messageCount,
|
|
1023
|
+
})),
|
|
1024
|
+
].filter(
|
|
1025
|
+
(entry, index, arr) =>
|
|
1026
|
+
arr.findIndex((candidate) => candidate.channel === entry.channel) === index,
|
|
1027
|
+
);
|
|
1028
|
+
return { ok: true, channels };
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
if (name === "chat_who") {
|
|
1032
|
+
const channel = String(args.channel || "general").trim() || "general";
|
|
1033
|
+
const messages = loadProjectMessages(channel, { limit: 100 });
|
|
1034
|
+
const participants = new Map();
|
|
1035
|
+
for (const msg of messages) {
|
|
1036
|
+
const name =
|
|
1037
|
+
msg.metadata?.agentName ||
|
|
1038
|
+
msg.agent ||
|
|
1039
|
+
(msg.role === "user" ? "user" : msg.source || "assistant");
|
|
1040
|
+
participants.set(name, {
|
|
1041
|
+
name,
|
|
1042
|
+
source: msg.source,
|
|
1043
|
+
lastTs: msg.ts,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
return {
|
|
1047
|
+
ok: true,
|
|
1048
|
+
channel,
|
|
1049
|
+
participants: [...participants.values()].sort((a, b) => b.lastTs - a.lastTs),
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// skill_* tools
|
|
1054
|
+
if (name.startsWith("skill_")) {
|
|
1055
|
+
const skillName = name.replace(/^skill_/, "").replace(/_/g, "-");
|
|
1056
|
+
const { params = {} } = args;
|
|
1057
|
+
try {
|
|
1058
|
+
const res = await fetch(
|
|
1059
|
+
`${CREW_LEAD_URL}/api/skill/${encodeURIComponent(skillName)}`,
|
|
1060
|
+
{
|
|
1061
|
+
method: "POST",
|
|
1062
|
+
headers: crewHeaders(),
|
|
1063
|
+
body: JSON.stringify(params),
|
|
1064
|
+
signal: AbortSignal.timeout(30_000),
|
|
1065
|
+
},
|
|
1066
|
+
);
|
|
1067
|
+
const d = await res.json();
|
|
1068
|
+
return d;
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
return { error: `Skill ${skillName} failed: ${e.message}` };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// gitnexus_* tools (MCP bridge)
|
|
1075
|
+
if (name.startsWith("gitnexus_")) {
|
|
1076
|
+
if (!gitnexusReady) {
|
|
1077
|
+
return {
|
|
1078
|
+
error: "GitNexus not available",
|
|
1079
|
+
note: "GitNexus MCP bridge is not running. Run 'npx gitnexus analyze' to index the current repo, then restart the MCP server.",
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
return await callGitNexusTool(name, args);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
return { error: `Unknown tool: ${name}` };
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// āā MCP message handler āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1089
|
+
async function handleMcpMessage(msg) {
|
|
1090
|
+
const { id, method, params } = msg;
|
|
1091
|
+
|
|
1092
|
+
if (method === "initialize") {
|
|
1093
|
+
return {
|
|
1094
|
+
jsonrpc: "2.0",
|
|
1095
|
+
id,
|
|
1096
|
+
result: {
|
|
1097
|
+
protocolVersion: "2024-11-05",
|
|
1098
|
+
capabilities: { tools: {} },
|
|
1099
|
+
serverInfo: {
|
|
1100
|
+
name: "crewswarm",
|
|
1101
|
+
version: "1.0.0",
|
|
1102
|
+
description: "crewswarm multi-agent fleet as MCP tools",
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (method === "notifications/initialized" || method === "initialized") {
|
|
1109
|
+
// Notification - no response required
|
|
1110
|
+
return { _skip: true };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (method === "tools/list") {
|
|
1114
|
+
return {
|
|
1115
|
+
jsonrpc: "2.0",
|
|
1116
|
+
id,
|
|
1117
|
+
result: { tools: buildToolList() },
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (method === "tools/call") {
|
|
1122
|
+
const { name, arguments: toolArgs = {} } = params || {};
|
|
1123
|
+
try {
|
|
1124
|
+
const result = await callTool(name, toolArgs);
|
|
1125
|
+
return {
|
|
1126
|
+
jsonrpc: "2.0",
|
|
1127
|
+
id,
|
|
1128
|
+
result: {
|
|
1129
|
+
content: [
|
|
1130
|
+
{
|
|
1131
|
+
type: "text",
|
|
1132
|
+
text:
|
|
1133
|
+
typeof result === "string"
|
|
1134
|
+
? result
|
|
1135
|
+
: JSON.stringify(result, null, 2),
|
|
1136
|
+
},
|
|
1137
|
+
],
|
|
1138
|
+
isError: !!result?.error,
|
|
1139
|
+
},
|
|
1140
|
+
};
|
|
1141
|
+
} catch (e) {
|
|
1142
|
+
return {
|
|
1143
|
+
jsonrpc: "2.0",
|
|
1144
|
+
id,
|
|
1145
|
+
result: {
|
|
1146
|
+
content: [{ type: "text", text: `Error: ${e.message}` }],
|
|
1147
|
+
isError: true,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (method === "ping") {
|
|
1154
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
return {
|
|
1158
|
+
jsonrpc: "2.0",
|
|
1159
|
+
id,
|
|
1160
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// āā stdio transport (Claude Desktop / Claude Code) āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1165
|
+
if (STDIO_MODE) {
|
|
1166
|
+
let buf = "";
|
|
1167
|
+
process.stdin.setEncoding("utf8");
|
|
1168
|
+
process.stdin.on("data", async (chunk) => {
|
|
1169
|
+
buf += chunk;
|
|
1170
|
+
const lines = buf.split("\n");
|
|
1171
|
+
buf = lines.pop();
|
|
1172
|
+
for (const line of lines) {
|
|
1173
|
+
if (!line.trim()) continue;
|
|
1174
|
+
try {
|
|
1175
|
+
const msg = JSON.parse(line);
|
|
1176
|
+
const resp = await handleMcpMessage(msg);
|
|
1177
|
+
if (resp) process.stdout.write(JSON.stringify(resp) + "\n");
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
process.stdout.write(
|
|
1180
|
+
JSON.stringify({
|
|
1181
|
+
jsonrpc: "2.0",
|
|
1182
|
+
id: null,
|
|
1183
|
+
error: { code: -32700, message: "Parse error" },
|
|
1184
|
+
}) + "\n",
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
process.stderr.write(`[crewswarm-mcp] stdio transport ready\n`);
|
|
1190
|
+
process.stdin.on("end", () => process.exit(0));
|
|
1191
|
+
} else {
|
|
1192
|
+
// āā HTTP transport āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1193
|
+
const server = http.createServer(async (req, res) => {
|
|
1194
|
+
const corsHeaders = {
|
|
1195
|
+
"access-control-allow-origin": "*",
|
|
1196
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
1197
|
+
"access-control-allow-headers": "content-type, authorization",
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
if (req.method === "OPTIONS") {
|
|
1201
|
+
res.writeHead(204, corsHeaders);
|
|
1202
|
+
res.end();
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
1207
|
+
|
|
1208
|
+
// Health check
|
|
1209
|
+
if (url.pathname === "/health" || url.pathname === "/") {
|
|
1210
|
+
const metrics = loadPipelineMetricsSummary(process.cwd());
|
|
1211
|
+
const qaRoundsAvg =
|
|
1212
|
+
metrics.runs > 0
|
|
1213
|
+
? Number((metrics.qaRoundsTotal / metrics.runs).toFixed(2))
|
|
1214
|
+
: 0;
|
|
1215
|
+
res.writeHead(200, {
|
|
1216
|
+
"content-type": "application/json",
|
|
1217
|
+
...corsHeaders,
|
|
1218
|
+
});
|
|
1219
|
+
res.end(
|
|
1220
|
+
JSON.stringify({
|
|
1221
|
+
ok: true,
|
|
1222
|
+
server: "crewswarm-mcp",
|
|
1223
|
+
version: "1.0.0",
|
|
1224
|
+
agents: loadAgents().length,
|
|
1225
|
+
skills: loadSkills().length,
|
|
1226
|
+
pipeline: {
|
|
1227
|
+
runs: metrics.runs,
|
|
1228
|
+
qaApproved: metrics.qaApproved,
|
|
1229
|
+
qaRejected: metrics.qaRejected,
|
|
1230
|
+
qaRoundsAvg,
|
|
1231
|
+
contextChunksUsed: metrics.contextChunksUsed,
|
|
1232
|
+
contextCharsSavedEst: metrics.contextCharsSaved,
|
|
1233
|
+
},
|
|
1234
|
+
}),
|
|
1235
|
+
);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// āā OpenAI-compatible API (/v1/*) āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1240
|
+
// Lets any tool with a "custom base URL" setting (Open WebUI, LM Studio,
|
|
1241
|
+
// Aider, Continue.dev, Cursor, etc.) use crewswarm agents as models.
|
|
1242
|
+
// Set base URL to http://127.0.0.1:5020 ā each agent appears as a model.
|
|
1243
|
+
|
|
1244
|
+
if (url.pathname === "/v1/models") {
|
|
1245
|
+
const agents = loadAgents();
|
|
1246
|
+
const models = [
|
|
1247
|
+
// crew-lead (Stinki) ā the general-purpose commander
|
|
1248
|
+
{
|
|
1249
|
+
id: "crewswarm",
|
|
1250
|
+
object: "model",
|
|
1251
|
+
created: 1700000000,
|
|
1252
|
+
owned_by: "crewswarm",
|
|
1253
|
+
description: "š§ Stinki ā crew-lead, general purpose commander",
|
|
1254
|
+
capabilities: ["chat", "coordination", "dispatch"],
|
|
1255
|
+
mode: "chat",
|
|
1256
|
+
},
|
|
1257
|
+
// One model per agent
|
|
1258
|
+
...agents.map((a) => ({
|
|
1259
|
+
id: a.id,
|
|
1260
|
+
object: "model",
|
|
1261
|
+
created: 1700000000,
|
|
1262
|
+
owned_by: "crewswarm",
|
|
1263
|
+
description: `${a.emoji || ""} ${a.name} ā ${a.role || a.id}`.trim(),
|
|
1264
|
+
capabilities: ["dispatch", "tools"],
|
|
1265
|
+
mode: "agent",
|
|
1266
|
+
})),
|
|
1267
|
+
];
|
|
1268
|
+
res.writeHead(200, {
|
|
1269
|
+
"content-type": "application/json",
|
|
1270
|
+
...corsHeaders,
|
|
1271
|
+
});
|
|
1272
|
+
res.end(JSON.stringify({ object: "list", data: models }));
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
if (url.pathname === "/v1/chat/completions" && req.method === "POST") {
|
|
1277
|
+
let body = "";
|
|
1278
|
+
for await (const chunk of req) body += chunk;
|
|
1279
|
+
let payload;
|
|
1280
|
+
try {
|
|
1281
|
+
payload = JSON.parse(body);
|
|
1282
|
+
} catch {
|
|
1283
|
+
res.writeHead(400, {
|
|
1284
|
+
"content-type": "application/json",
|
|
1285
|
+
...corsHeaders,
|
|
1286
|
+
});
|
|
1287
|
+
res.end(
|
|
1288
|
+
JSON.stringify({
|
|
1289
|
+
error: { message: "Invalid JSON", type: "invalid_request_error" },
|
|
1290
|
+
}),
|
|
1291
|
+
);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const {
|
|
1296
|
+
model = "crewswarm",
|
|
1297
|
+
messages = [],
|
|
1298
|
+
stream = false,
|
|
1299
|
+
temperature,
|
|
1300
|
+
top_p,
|
|
1301
|
+
max_tokens,
|
|
1302
|
+
tool_choice,
|
|
1303
|
+
tools,
|
|
1304
|
+
} = payload;
|
|
1305
|
+
|
|
1306
|
+
const composed = composeChatPayloadFromOpenAI(messages);
|
|
1307
|
+
const task = composed.message;
|
|
1308
|
+
const contextPack = composed.context;
|
|
1309
|
+
|
|
1310
|
+
if (!task) {
|
|
1311
|
+
res.writeHead(400, {
|
|
1312
|
+
"content-type": "application/json",
|
|
1313
|
+
...corsHeaders,
|
|
1314
|
+
});
|
|
1315
|
+
res.end(
|
|
1316
|
+
JSON.stringify({
|
|
1317
|
+
error: {
|
|
1318
|
+
message: "No user message found",
|
|
1319
|
+
type: "invalid_request_error",
|
|
1320
|
+
},
|
|
1321
|
+
}),
|
|
1322
|
+
);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
const selectedTool = selectToolCallName({ tools, tool_choice }, task);
|
|
1327
|
+
if (selectedTool) {
|
|
1328
|
+
const toolResponse = buildToolCallResponse({
|
|
1329
|
+
model,
|
|
1330
|
+
stream: Boolean(stream),
|
|
1331
|
+
toolName: selectedTool,
|
|
1332
|
+
task,
|
|
1333
|
+
});
|
|
1334
|
+
if (toolResponse.streamChunks) {
|
|
1335
|
+
res.writeHead(200, {
|
|
1336
|
+
"content-type": "text/event-stream",
|
|
1337
|
+
"cache-control": "no-cache",
|
|
1338
|
+
...corsHeaders,
|
|
1339
|
+
});
|
|
1340
|
+
for (const chunk of toolResponse.streamChunks) {
|
|
1341
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
1342
|
+
}
|
|
1343
|
+
res.write("data: [DONE]\n\n");
|
|
1344
|
+
res.end();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
res.writeHead(200, {
|
|
1348
|
+
"content-type": "application/json",
|
|
1349
|
+
...corsHeaders,
|
|
1350
|
+
});
|
|
1351
|
+
res.end(JSON.stringify(toolResponse.json));
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const startedAt = Date.now();
|
|
1356
|
+
const runMeta = {
|
|
1357
|
+
source: "openai-wrapper",
|
|
1358
|
+
clientModel: model,
|
|
1359
|
+
temperature,
|
|
1360
|
+
top_p,
|
|
1361
|
+
max_tokens,
|
|
1362
|
+
hasTools: Array.isArray(tools) && tools.length > 0,
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
// Route: "crewswarm" / "crew-lead" ā /chat | anything else ā dispatch
|
|
1366
|
+
const isChatRoute = model === "crewswarm" || model === "crew-lead";
|
|
1367
|
+
let reply = "";
|
|
1368
|
+
try {
|
|
1369
|
+
if (isChatRoute) {
|
|
1370
|
+
const chatMessage = contextPack ? `${contextPack}\n\n${task}` : task;
|
|
1371
|
+
const r = await fetch(`${CREW_LEAD_URL}/chat`, {
|
|
1372
|
+
method: "POST",
|
|
1373
|
+
headers: crewHeaders(),
|
|
1374
|
+
body: JSON.stringify({
|
|
1375
|
+
message: chatMessage,
|
|
1376
|
+
context: contextPack,
|
|
1377
|
+
sessionId: `openai-compat-${Date.now()}`,
|
|
1378
|
+
metadata: runMeta,
|
|
1379
|
+
}),
|
|
1380
|
+
signal: AbortSignal.timeout(120_000),
|
|
1381
|
+
});
|
|
1382
|
+
const d = await r.json();
|
|
1383
|
+
reply = d.reply || d.message || "(no reply)";
|
|
1384
|
+
} else {
|
|
1385
|
+
const taskWithContext = contextPack
|
|
1386
|
+
? `${task}\n\n${contextPack}`
|
|
1387
|
+
: task;
|
|
1388
|
+
const r = await fetch(`${CREW_LEAD_URL}/api/dispatch`, {
|
|
1389
|
+
method: "POST",
|
|
1390
|
+
headers: crewHeaders(),
|
|
1391
|
+
body: JSON.stringify({
|
|
1392
|
+
agent: model,
|
|
1393
|
+
task: taskWithContext,
|
|
1394
|
+
context: contextPack,
|
|
1395
|
+
source: "openai-wrapper",
|
|
1396
|
+
clientModel: model,
|
|
1397
|
+
sessionId: `openai-compat-${Date.now()}`,
|
|
1398
|
+
metadata: runMeta,
|
|
1399
|
+
}),
|
|
1400
|
+
signal: AbortSignal.timeout(10_000),
|
|
1401
|
+
});
|
|
1402
|
+
const dispatched = await r.json();
|
|
1403
|
+
const taskId = dispatched.taskId;
|
|
1404
|
+
if (!taskId) throw new Error(dispatched.error || "dispatch failed");
|
|
1405
|
+
|
|
1406
|
+
// Poll for result (90s max)
|
|
1407
|
+
const start = Date.now();
|
|
1408
|
+
while (Date.now() - start < 90_000) {
|
|
1409
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1410
|
+
const sr = await fetch(`${CREW_LEAD_URL}/api/status/${taskId}`, {
|
|
1411
|
+
headers: crewHeaders(),
|
|
1412
|
+
signal: AbortSignal.timeout(5_000),
|
|
1413
|
+
});
|
|
1414
|
+
const s = await sr.json();
|
|
1415
|
+
if (s.status === "completed" || s.reply) {
|
|
1416
|
+
reply = s.reply || s.result || "(done)";
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
if (s.status === "failed") {
|
|
1420
|
+
reply = `Error: ${s.error || "task failed"}`;
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (!reply)
|
|
1425
|
+
reply = `Task dispatched to ${model} (taskId: ${taskId}) ā timed out waiting for reply.`;
|
|
1426
|
+
}
|
|
1427
|
+
} catch (e) {
|
|
1428
|
+
reply = `Error: ${e.message}`;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
console.log(
|
|
1432
|
+
`[openai-wrapper] model=${model} stream=${Boolean(stream)} route=${isChatRoute ? "chat" : "dispatch"} ` +
|
|
1433
|
+
`msgs=${composed.messageCounts.total} (sys:${composed.messageCounts.system},asst:${composed.messageCounts.assistant},usr:${composed.messageCounts.user}) ` +
|
|
1434
|
+
`contextChars=${contextPack.length} latencyMs=${Date.now() - startedAt}`,
|
|
1435
|
+
);
|
|
1436
|
+
|
|
1437
|
+
const completionId = `chatcmpl-${Date.now().toString(36)}`;
|
|
1438
|
+
const ts = Math.floor(Date.now() / 1000);
|
|
1439
|
+
|
|
1440
|
+
if (stream) {
|
|
1441
|
+
// Streaming response ā send as a single chunk then [DONE]
|
|
1442
|
+
res.writeHead(200, {
|
|
1443
|
+
"content-type": "text/event-stream",
|
|
1444
|
+
"cache-control": "no-cache",
|
|
1445
|
+
...corsHeaders,
|
|
1446
|
+
});
|
|
1447
|
+
const chunk = {
|
|
1448
|
+
id: completionId,
|
|
1449
|
+
object: "chat.completion.chunk",
|
|
1450
|
+
created: ts,
|
|
1451
|
+
model,
|
|
1452
|
+
choices: [
|
|
1453
|
+
{
|
|
1454
|
+
index: 0,
|
|
1455
|
+
delta: { role: "assistant", content: reply },
|
|
1456
|
+
finish_reason: null,
|
|
1457
|
+
},
|
|
1458
|
+
],
|
|
1459
|
+
};
|
|
1460
|
+
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
|
1461
|
+
const done = {
|
|
1462
|
+
id: completionId,
|
|
1463
|
+
object: "chat.completion.chunk",
|
|
1464
|
+
created: ts,
|
|
1465
|
+
model,
|
|
1466
|
+
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
|
1467
|
+
};
|
|
1468
|
+
res.write(`data: ${JSON.stringify(done)}\n\n`);
|
|
1469
|
+
res.write("data: [DONE]\n\n");
|
|
1470
|
+
res.end();
|
|
1471
|
+
} else {
|
|
1472
|
+
res.writeHead(200, {
|
|
1473
|
+
"content-type": "application/json",
|
|
1474
|
+
...corsHeaders,
|
|
1475
|
+
});
|
|
1476
|
+
res.end(
|
|
1477
|
+
JSON.stringify({
|
|
1478
|
+
id: completionId,
|
|
1479
|
+
object: "chat.completion",
|
|
1480
|
+
created: ts,
|
|
1481
|
+
model,
|
|
1482
|
+
choices: [
|
|
1483
|
+
{
|
|
1484
|
+
index: 0,
|
|
1485
|
+
message: { role: "assistant", content: reply },
|
|
1486
|
+
finish_reason: "stop",
|
|
1487
|
+
},
|
|
1488
|
+
],
|
|
1489
|
+
usage: {
|
|
1490
|
+
prompt_tokens: Math.ceil(composed.inputChars / 4),
|
|
1491
|
+
completion_tokens: Math.ceil(reply.length / 4),
|
|
1492
|
+
total_tokens: Math.ceil((composed.inputChars + reply.length) / 4),
|
|
1493
|
+
},
|
|
1494
|
+
}),
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (url.pathname !== "/mcp") {
|
|
1501
|
+
res.writeHead(404, corsHeaders);
|
|
1502
|
+
res.end("not found");
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (req.method === "GET") {
|
|
1507
|
+
// SSE endpoint for Cursor / streamable HTTP clients
|
|
1508
|
+
res.writeHead(200, {
|
|
1509
|
+
"content-type": "text/event-stream",
|
|
1510
|
+
"cache-control": "no-cache",
|
|
1511
|
+
connection: "keep-alive",
|
|
1512
|
+
...corsHeaders,
|
|
1513
|
+
});
|
|
1514
|
+
res.write(
|
|
1515
|
+
"event: endpoint\ndata: " +
|
|
1516
|
+
JSON.stringify({
|
|
1517
|
+
uri: `http://localhost:${PORT}/mcp`,
|
|
1518
|
+
protocolVersion: "2024-11-05",
|
|
1519
|
+
}) +
|
|
1520
|
+
"\n\n",
|
|
1521
|
+
);
|
|
1522
|
+
req.on("close", () => {});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
if (req.method === "POST") {
|
|
1527
|
+
let body = "";
|
|
1528
|
+
for await (const chunk of req) body += chunk;
|
|
1529
|
+
try {
|
|
1530
|
+
const msg = JSON.parse(body);
|
|
1531
|
+
const resp = await handleMcpMessage(msg);
|
|
1532
|
+
if (resp && !resp._skip) {
|
|
1533
|
+
res.writeHead(200, {
|
|
1534
|
+
"content-type": "application/json",
|
|
1535
|
+
...corsHeaders,
|
|
1536
|
+
});
|
|
1537
|
+
res.end(JSON.stringify(resp));
|
|
1538
|
+
} else {
|
|
1539
|
+
// Notifications should not advertise a JSON body. Some MCP clients
|
|
1540
|
+
// attempt to decode an empty 200/application-json response and log
|
|
1541
|
+
// a transport error during initialized.
|
|
1542
|
+
res.writeHead(204, corsHeaders);
|
|
1543
|
+
res.end();
|
|
1544
|
+
}
|
|
1545
|
+
} catch (e) {
|
|
1546
|
+
res.writeHead(200, {
|
|
1547
|
+
"content-type": "application/json",
|
|
1548
|
+
...corsHeaders,
|
|
1549
|
+
});
|
|
1550
|
+
res.end(
|
|
1551
|
+
JSON.stringify({
|
|
1552
|
+
jsonrpc: "2.0",
|
|
1553
|
+
id: null,
|
|
1554
|
+
error: { code: -32700, message: "Parse error: " + e.message },
|
|
1555
|
+
}),
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
res.writeHead(405, corsHeaders);
|
|
1562
|
+
res.end("method not allowed");
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
server.listen(PORT, "127.0.0.1", async () => {
|
|
1566
|
+
const agents = loadAgents();
|
|
1567
|
+
const skills = loadSkills();
|
|
1568
|
+
|
|
1569
|
+
console.log(`\nš crewswarm MCP Server`);
|
|
1570
|
+
console.log(`${"ā".repeat(50)}`);
|
|
1571
|
+
console.log(` HTTP endpoint : http://127.0.0.1:${PORT}/mcp`);
|
|
1572
|
+
console.log(` Health check : http://127.0.0.1:${PORT}/health`);
|
|
1573
|
+
console.log(
|
|
1574
|
+
` Agents : ${agents.length} (${agents.map((a) => (a.emoji || "") + a.name).join(", ")})`,
|
|
1575
|
+
);
|
|
1576
|
+
console.log(
|
|
1577
|
+
` Skills : ${skills.length} (${skills.map((s) => s.name).join(", ")})`,
|
|
1578
|
+
);
|
|
1579
|
+
|
|
1580
|
+
// Initialize GitNexus MCP bridge (opt-in, async, non-blocking)
|
|
1581
|
+
if (GITNEXUS_ENABLED) {
|
|
1582
|
+
console.log(` GitNexus : checking...`);
|
|
1583
|
+
const gnReady = await initGitNexusBridge();
|
|
1584
|
+
if (gnReady) {
|
|
1585
|
+
console.log(
|
|
1586
|
+
` GitNexus : ā
${gitnexusTools.length} tools available`,
|
|
1587
|
+
);
|
|
1588
|
+
} else {
|
|
1589
|
+
console.log(
|
|
1590
|
+
` GitNexus : āļø not indexed (run 'npx gitnexus analyze' to enable)`,
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
} else {
|
|
1594
|
+
console.log(
|
|
1595
|
+
` GitNexus : off (set CREWSWARM_GITNEXUS_ENABLED=1 to enable)`,
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
console.log(`${"ā".repeat(50)}`);
|
|
1600
|
+
console.log(`\nMCP clients (Cursor / Claude Code / OpenCode):`);
|
|
1601
|
+
console.log(
|
|
1602
|
+
` Cursor .cursor/mcp.json: { "crewswarm": { "url": "http://127.0.0.1:${PORT}/mcp" } }`,
|
|
1603
|
+
);
|
|
1604
|
+
console.log(
|
|
1605
|
+
` Claude Code stdio: node scripts/mcp-server.mjs --stdio`,
|
|
1606
|
+
);
|
|
1607
|
+
console.log(
|
|
1608
|
+
` OpenCode: { "mcpServers": { "crewswarm": { "type": "http", "url": "http://127.0.0.1:${PORT}/mcp" } } }`,
|
|
1609
|
+
);
|
|
1610
|
+
console.log(
|
|
1611
|
+
`\nOpenAI-compatible API (Open WebUI / LM Studio / Aider / Continue.dev):`,
|
|
1612
|
+
);
|
|
1613
|
+
console.log(` Base URL : http://127.0.0.1:${PORT}/v1`);
|
|
1614
|
+
console.log(
|
|
1615
|
+
` API key : (any string ā uses crewswarm auth token internally)`,
|
|
1616
|
+
);
|
|
1617
|
+
console.log(
|
|
1618
|
+
` Models : GET http://127.0.0.1:${PORT}/v1/models (one per agent)`,
|
|
1619
|
+
);
|
|
1620
|
+
console.log(
|
|
1621
|
+
` Chat : POST http://127.0.0.1:${PORT}/v1/chat/completions`,
|
|
1622
|
+
);
|
|
1623
|
+
console.log();
|
|
1624
|
+
});
|
|
1625
|
+
}
|