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,1575 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* crewswarm Gateway Bridge — agent daemon for real-time LLM calls and tool execution.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node gateway-bridge.mjs "your message here"
|
|
7
|
+
* node gateway-bridge.mjs --status
|
|
8
|
+
* node gateway-bridge.mjs --reset
|
|
9
|
+
* node gateway-bridge.mjs --history
|
|
10
|
+
* CREWSWARM_ONE_SHOT=1 node gateway-bridge.mjs "task" # Exit after task (fresh context)
|
|
11
|
+
*/
|
|
12
|
+
import { WebSocket } from "ws";
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import os from "node:os";
|
|
17
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
18
|
+
import { getProjectDir } from "./lib/project-dir.mjs";
|
|
19
|
+
import { rewriteTaskPathsRelativeToProjectRoot } from "./lib/runtime/project-dir.mjs";
|
|
20
|
+
import { COORDINATOR_AGENT_IDS } from "./lib/agent-registry.mjs";
|
|
21
|
+
import {
|
|
22
|
+
isSharedMemoryAvailable,
|
|
23
|
+
initSharedMemory,
|
|
24
|
+
recallMemoryContext,
|
|
25
|
+
recordTaskMemory,
|
|
26
|
+
rememberFact,
|
|
27
|
+
CREW_MEMORY_DIR,
|
|
28
|
+
} from "./lib/memory/shared-adapter.mjs";
|
|
29
|
+
import {
|
|
30
|
+
resolveConfig,
|
|
31
|
+
resolveTelegramBridgeConfig,
|
|
32
|
+
loadAgentList,
|
|
33
|
+
loadAgentLLMConfig,
|
|
34
|
+
loadLoopBrainConfig,
|
|
35
|
+
loadProviderMap,
|
|
36
|
+
CREWSWARM_RT_SWARM_AGENTS,
|
|
37
|
+
RT_TO_GATEWAY_AGENT_MAP,
|
|
38
|
+
} from "./lib/agents/registry.mjs";
|
|
39
|
+
import {
|
|
40
|
+
validateCodingArtifacts,
|
|
41
|
+
assertTaskPromptProtocol,
|
|
42
|
+
initValidation,
|
|
43
|
+
} from "./lib/agents/validation.mjs";
|
|
44
|
+
import {
|
|
45
|
+
spawnAgentDaemon,
|
|
46
|
+
isAgentDaemonRunning,
|
|
47
|
+
readPid,
|
|
48
|
+
resolveSpawnTargets,
|
|
49
|
+
} from "./lib/agents/daemon.mjs";
|
|
50
|
+
import {
|
|
51
|
+
acquireTaskLease,
|
|
52
|
+
renewTaskLease,
|
|
53
|
+
releaseTaskLease,
|
|
54
|
+
markTaskDone,
|
|
55
|
+
dispatchKeyForTask,
|
|
56
|
+
shouldUseDispatchGuard,
|
|
57
|
+
shouldRetryTaskFailure,
|
|
58
|
+
isCodingTask,
|
|
59
|
+
} from "./lib/agents/dispatch.mjs";
|
|
60
|
+
|
|
61
|
+
// ── One-shot mode: exit after task completion (fresh context) ────────────────
|
|
62
|
+
const ONE_SHOT = process.env.CREWSWARM_ONE_SHOT === '1' || process.argv.includes('--one-shot');
|
|
63
|
+
import {
|
|
64
|
+
CREWSWARM_RT_URL,
|
|
65
|
+
CREWSWARM_RT_AGENT,
|
|
66
|
+
CREWSWARM_RT_TOKEN,
|
|
67
|
+
CREWSWARM_RT_CHANNELS,
|
|
68
|
+
CREWSWARM_RT_TLS_INSECURE,
|
|
69
|
+
CREWSWARM_RT_RECONNECT_MS,
|
|
70
|
+
CREWSWARM_RT_DISPATCH_LEASE_MS,
|
|
71
|
+
CREWSWARM_RT_DISPATCH_HEARTBEAT_MS,
|
|
72
|
+
CREWSWARM_RT_DISPATCH_MAX_RETRIES,
|
|
73
|
+
CREWSWARM_RT_DISPATCH_MAX_RETRIES_CODING,
|
|
74
|
+
CREWSWARM_RT_DISPATCH_RETRY_BACKOFF_MS,
|
|
75
|
+
CREWSWARM_OPENCODE_ENABLED,
|
|
76
|
+
CREWSWARM_OPENCODE_BIN,
|
|
77
|
+
CREWSWARM_OPENCODE_AGENT,
|
|
78
|
+
CREWSWARM_OPENCODE_MODEL,
|
|
79
|
+
CREWSWARM_OPENCODE_FALLBACK_DEFAULT,
|
|
80
|
+
CREWSWARM_OPENCODE_TIMEOUT_MS,
|
|
81
|
+
loadGenericEngines,
|
|
82
|
+
SHARED_MEMORY_BASE,
|
|
83
|
+
SHARED_MEMORY_NAMESPACE,
|
|
84
|
+
SWARM_STATUS_LOG,
|
|
85
|
+
SWARM_DISPATCH_DIR,
|
|
86
|
+
SWARM_DLQ_DIR,
|
|
87
|
+
SWARM_RUNTIME_DIR,
|
|
88
|
+
CREWSWARM_RT_COMMAND_TYPES,
|
|
89
|
+
PROTOCOL_VERSION,
|
|
90
|
+
CLI_VERSION,
|
|
91
|
+
RUN_ID,
|
|
92
|
+
GATEWAY_URL,
|
|
93
|
+
TELEMETRY_DIR,
|
|
94
|
+
LEGACY_STATE_DIR,
|
|
95
|
+
CREWSWARM_CONFIG_PATH,
|
|
96
|
+
MEMORY_BOOTSTRAP_AGENT,
|
|
97
|
+
ED25519_SPKI_PREFIX,
|
|
98
|
+
REQUEST_TIMEOUT_MS,
|
|
99
|
+
CHAT_TIMEOUT_MS,
|
|
100
|
+
loadSystemConfig,
|
|
101
|
+
loadSwarmConfig,
|
|
102
|
+
} from "./lib/runtime/config.mjs";
|
|
103
|
+
import {
|
|
104
|
+
TELEMETRY_LOG,
|
|
105
|
+
formatError,
|
|
106
|
+
formatDuration,
|
|
107
|
+
median,
|
|
108
|
+
percentile,
|
|
109
|
+
readTelemetryEvents,
|
|
110
|
+
} from "./lib/runtime/utils.mjs";
|
|
111
|
+
import {
|
|
112
|
+
initTaskLease,
|
|
113
|
+
ensureSwarmRuntimeDirs,
|
|
114
|
+
parseTaskState,
|
|
115
|
+
taskIdentity,
|
|
116
|
+
taskKeyFor,
|
|
117
|
+
leasePath,
|
|
118
|
+
taskStatePath,
|
|
119
|
+
lockPath,
|
|
120
|
+
withTaskLock,
|
|
121
|
+
clearStaleTaskState,
|
|
122
|
+
claimTaskLease,
|
|
123
|
+
startTaskLeaseHeartbeat,
|
|
124
|
+
finalizeTaskState,
|
|
125
|
+
releaseRuntimeTaskLease,
|
|
126
|
+
} from "./lib/runtime/task-lease.mjs";
|
|
127
|
+
import {
|
|
128
|
+
initTools,
|
|
129
|
+
setRtClient,
|
|
130
|
+
pendingCmdApprovals,
|
|
131
|
+
isAutoApproveAgent,
|
|
132
|
+
loadAgentToolPermissions,
|
|
133
|
+
buildToolInstructions,
|
|
134
|
+
loadCmdAllowlist,
|
|
135
|
+
isCommandBlocked,
|
|
136
|
+
isCommandAllowlisted,
|
|
137
|
+
sanitizeToolPath,
|
|
138
|
+
executeToolCalls,
|
|
139
|
+
} from "./lib/tools/executor.mjs";
|
|
140
|
+
import {
|
|
141
|
+
initSpending,
|
|
142
|
+
loadSpending,
|
|
143
|
+
saveSpending,
|
|
144
|
+
addAgentSpend,
|
|
145
|
+
checkSpendingCap,
|
|
146
|
+
notifyTelegramSpending,
|
|
147
|
+
tokenUsage,
|
|
148
|
+
recordTokenUsage,
|
|
149
|
+
} from "./lib/runtime/spending.mjs";
|
|
150
|
+
import {
|
|
151
|
+
initSkills,
|
|
152
|
+
resolveSkillAlias,
|
|
153
|
+
loadSkillDef,
|
|
154
|
+
loadPendingSkills,
|
|
155
|
+
savePendingSkills,
|
|
156
|
+
executeSkill,
|
|
157
|
+
notifyTelegramSkillApproval,
|
|
158
|
+
} from "./lib/skills/index.mjs";
|
|
159
|
+
import {
|
|
160
|
+
initMemory,
|
|
161
|
+
SHARED_MEMORY_DIR,
|
|
162
|
+
SHARED_MEMORY_MAX_FILE_CHARS,
|
|
163
|
+
SHARED_MEMORY_MAX_TOTAL_CHARS,
|
|
164
|
+
SHARED_MEMORY_FILES,
|
|
165
|
+
_AGENT_EXTRA_MEMORY_STATIC,
|
|
166
|
+
_EXTRA_MEMORY_BY_ROLE,
|
|
167
|
+
getAgentExtraMemory,
|
|
168
|
+
loadSharedMemoryBundle,
|
|
169
|
+
getLastHandoffTimestamp,
|
|
170
|
+
loadAgentPrompts,
|
|
171
|
+
buildTaskPrompt,
|
|
172
|
+
} from "./lib/runtime/memory.mjs";
|
|
173
|
+
import {
|
|
174
|
+
initOuroboros,
|
|
175
|
+
runOuroborosStyleLoop,
|
|
176
|
+
} from "./lib/engines/ouroboros.mjs";
|
|
177
|
+
import { initRtEnvelope, handleRealtimeEnvelope } from "./lib/engines/rt-envelope.mjs";
|
|
178
|
+
import {
|
|
179
|
+
initRunners,
|
|
180
|
+
setRtClientForRunners,
|
|
181
|
+
selectEngine,
|
|
182
|
+
shouldUseCursorCli,
|
|
183
|
+
shouldUseClaudeCode,
|
|
184
|
+
shouldUseOpenCode,
|
|
185
|
+
shouldUseCodex,
|
|
186
|
+
shouldUseGeminiCli,
|
|
187
|
+
shouldUseCrewCLI,
|
|
188
|
+
runGeminiCliTask,
|
|
189
|
+
shouldUseGenericEngine,
|
|
190
|
+
runGenericEngineTask,
|
|
191
|
+
runCursorCliTask,
|
|
192
|
+
runCodexTask,
|
|
193
|
+
shouldUseDockerSandbox,
|
|
194
|
+
runDockerSandboxTask,
|
|
195
|
+
runClaudeCodeTask,
|
|
196
|
+
_rtClientForApprovals,
|
|
197
|
+
} from "./lib/engines/runners.mjs";
|
|
198
|
+
import { initCrewCLI, runCrewCLITask } from "./lib/engines/crew-cli.mjs";
|
|
199
|
+
import { initLlmDirect, callLLMDirect } from "./lib/engines/llm-direct.mjs";
|
|
200
|
+
import { initOpenCode, runOpenCodeTask } from "./lib/engines/opencode.mjs";
|
|
201
|
+
import { initGatewayWs } from "./lib/bridges/gateway-ws.mjs";
|
|
202
|
+
|
|
203
|
+
// Wire injected deps into extracted modules
|
|
204
|
+
initTaskLease({ telemetry, sleep, parseJsonSafe });
|
|
205
|
+
initTools({
|
|
206
|
+
resolveConfig, resolveTelegramBridgeConfig, loadAgentList, getOpencodeProjectDir,
|
|
207
|
+
loadSkillDef, loadPendingSkills, savePendingSkills, notifyTelegramSkillApproval, executeSkill
|
|
208
|
+
});
|
|
209
|
+
initSpending({ resolveConfig, resolveTelegramBridgeConfig });
|
|
210
|
+
initSkills({ resolveConfig, resolveTelegramBridgeConfig });
|
|
211
|
+
initMemory({ telemetry, ensureSharedMemoryFiles, loadAgentList, loadAgentToolPermissions, buildToolInstructions, getOpencodeProjectDir });
|
|
212
|
+
initValidation({ telemetry });
|
|
213
|
+
|
|
214
|
+
// Initialize shared memory (CLI-style) for cross-system memory sharing
|
|
215
|
+
const sharedMemoryInit = initSharedMemory();
|
|
216
|
+
if (sharedMemoryInit.ok) {
|
|
217
|
+
console.log(`[gateway-bridge] Shared memory initialized: ${sharedMemoryInit.path}`);
|
|
218
|
+
if (!isSharedMemoryAvailable()) {
|
|
219
|
+
console.warn('[gateway-bridge] CLI memory modules not available — run: cd crew-cli && npm run build');
|
|
220
|
+
} else {
|
|
221
|
+
console.log('[gateway-bridge] CLI shared memory integration enabled (AgentKeeper + AgentMemory + MemoryBroker)');
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
console.warn(`[gateway-bridge] Shared memory init failed: ${sharedMemoryInit.error}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getOpencodeProjectDir() {
|
|
228
|
+
return getProjectDir("") || "";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Per-agent OpenCode session persistence ─────────────────────────────────
|
|
232
|
+
// Each agent maintains a session ID so `opencode run -s <id>` continues from
|
|
233
|
+
// where the last task left off, rather than starting cold every time.
|
|
234
|
+
// Sessions are stored in ~/.crewswarm/sessions/<agentId>.session
|
|
235
|
+
const OPENCODE_SESSION_DIR = path.join(os.homedir(), ".crewswarm", "sessions");
|
|
236
|
+
|
|
237
|
+
// Free OpenCode models for fallback rotation when primary hits rate limit
|
|
238
|
+
const OPENCODE_FREE_MODEL_CHAIN = [
|
|
239
|
+
"groq/moonshotai/kimi-k2-instruct-0905",
|
|
240
|
+
"groq/qwen/qwen3-32b",
|
|
241
|
+
"groq/llama-3.3-70b-versatile",
|
|
242
|
+
"opencode/gpt-5.1-codex-mini",
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
function readAgentSessionId(agentId) {
|
|
246
|
+
if (!agentId) return null;
|
|
247
|
+
try {
|
|
248
|
+
const f = path.join(OPENCODE_SESSION_DIR, `${agentId}.session`);
|
|
249
|
+
if (fs.existsSync(f)) return fs.readFileSync(f, "utf8").trim() || null;
|
|
250
|
+
} catch (e) {
|
|
251
|
+
console.error(`[gateway-bridge] Failed to read session ID for ${agentId}: ${e.message}`);
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function writeAgentSessionId(agentId, sessionId) {
|
|
257
|
+
if (!agentId || !sessionId) return;
|
|
258
|
+
try {
|
|
259
|
+
fs.mkdirSync(OPENCODE_SESSION_DIR, { recursive: true });
|
|
260
|
+
fs.writeFileSync(path.join(OPENCODE_SESSION_DIR, `${agentId}.session`), sessionId, "utf8");
|
|
261
|
+
} catch (e) {
|
|
262
|
+
console.error(`[gateway-bridge] Failed to write session ID for ${agentId}: ${e.message}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function clearAgentSessionId(agentId) {
|
|
267
|
+
if (!agentId) return;
|
|
268
|
+
try {
|
|
269
|
+
const f = path.join(OPENCODE_SESSION_DIR, `${agentId}.session`);
|
|
270
|
+
if (fs.existsSync(f)) fs.unlinkSync(f);
|
|
271
|
+
} catch (e) {
|
|
272
|
+
console.error(`[gateway-bridge] Failed to clear session ID for ${agentId}: ${e.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
// Parse the most-recent session ID from `opencode session list` stdout.
|
|
278
|
+
// If agentPrefix is provided (e.g. "[crew-coder]"), only match sessions whose
|
|
279
|
+
// title contains that prefix — prevents race conditions when multiple agents
|
|
280
|
+
// finish simultaneously and each would otherwise grab the globally-first session.
|
|
281
|
+
function parseMostRecentSessionId(listOutput, agentPrefix) {
|
|
282
|
+
for (const line of listOutput.split("\n")) {
|
|
283
|
+
const m = line.trim().match(/^(ses_[A-Za-z0-9]+)\s+(.*)/);
|
|
284
|
+
if (!m) continue;
|
|
285
|
+
if (agentPrefix) {
|
|
286
|
+
// Title is everything after the session ID; check it contains the agent tag
|
|
287
|
+
if (m[2].includes(agentPrefix)) return m[1];
|
|
288
|
+
} else {
|
|
289
|
+
return m[1];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
// Detect a rate-limited OpenCode session: process exited null and only printed the banner.
|
|
297
|
+
// Pattern: ANSI codes + "> agentname · modelname" with no actual tool output.
|
|
298
|
+
function isOpencodeRateLimitBanner(output) {
|
|
299
|
+
const stripped = output.replace(/\x1b\[[0-9;]*m/g, "").trim();
|
|
300
|
+
return /^>\s+\S+\s+·\s+\S+\s*$/.test(stripped);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function getOpencodeFallbackModel() {
|
|
304
|
+
if (process.env.CREWSWARM_OPENCODE_FALLBACK_MODEL) return process.env.CREWSWARM_OPENCODE_FALLBACK_MODEL;
|
|
305
|
+
const cfg = loadSystemConfig();
|
|
306
|
+
if (cfg.opencodeFallbackModel && String(cfg.opencodeFallbackModel).trim()) return String(cfg.opencodeFallbackModel).trim();
|
|
307
|
+
const swarm = loadSwarmConfig();
|
|
308
|
+
if (swarm.globalFallbackModel && String(swarm.globalFallbackModel).trim()) return String(swarm.globalFallbackModel).trim();
|
|
309
|
+
return CREWSWARM_OPENCODE_FALLBACK_DEFAULT;
|
|
310
|
+
}
|
|
311
|
+
console.log(`[bridge] Registered ${CREWSWARM_RT_SWARM_AGENTS.length} RT agents: ${CREWSWARM_RT_SWARM_AGENTS.join(", ")}`);
|
|
312
|
+
|
|
313
|
+
// callLLMDirect → lib/engines/llm-direct.mjs
|
|
314
|
+
|
|
315
|
+
// ─── Crypto helpers ─────────────────────────────────────────────────────────
|
|
316
|
+
function b64url(buf) { return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, ""); }
|
|
317
|
+
function deriveRaw(pem) {
|
|
318
|
+
const spki = crypto.createPublicKey(pem).export({ type: "spki", format: "der" });
|
|
319
|
+
return spki.length === ED25519_SPKI_PREFIX.length + 32 &&
|
|
320
|
+
spki.subarray(0, ED25519_SPKI_PREFIX.length).equals(ED25519_SPKI_PREFIX)
|
|
321
|
+
? spki.subarray(ED25519_SPKI_PREFIX.length) : spki;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
|
|
325
|
+
|
|
326
|
+
function progress(msg) { process.stderr.write(`• ${msg}\n`); }
|
|
327
|
+
|
|
328
|
+
function telemetry(event, metadata = {}) {
|
|
329
|
+
try {
|
|
330
|
+
fs.mkdirSync(TELEMETRY_DIR, { recursive: true });
|
|
331
|
+
fs.appendFileSync(TELEMETRY_LOG, `${JSON.stringify({
|
|
332
|
+
timestamp: new Date().toISOString(),
|
|
333
|
+
event,
|
|
334
|
+
runId: RUN_ID,
|
|
335
|
+
version: CLI_VERSION,
|
|
336
|
+
metadata,
|
|
337
|
+
})}\n`);
|
|
338
|
+
} catch (e) {
|
|
339
|
+
console.error(`[gateway-bridge] Failed to write telemetry event ${event}: ${e.message}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function transientError(err) {
|
|
344
|
+
const msg = String(err?.message ?? err ?? "").toLowerCase();
|
|
345
|
+
return ["timeout", "timed out", "econnrefused", "ehostunreach", "econnreset", "socket hang up", "websocket is not open", "connection closed", "broken pipe"].some((s) => msg.includes(s));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function withRetry(fn, { retries = 2, baseDelayMs = 300, label = "request" } = {}) {
|
|
349
|
+
let attempt = 0;
|
|
350
|
+
while (true) {
|
|
351
|
+
try {
|
|
352
|
+
return await fn();
|
|
353
|
+
} catch (err) {
|
|
354
|
+
if (attempt >= retries || !transientError(err)) throw err;
|
|
355
|
+
attempt += 1;
|
|
356
|
+
const delayMs = baseDelayMs * (2 ** (attempt - 1));
|
|
357
|
+
telemetry("retry_attempt", { label, attempt, delayMs, error: err?.message ?? String(err) });
|
|
358
|
+
progress(`Retrying ${label} (${attempt}/${retries}) in ${delayMs}ms...`);
|
|
359
|
+
await sleep(delayMs);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function parseTextContent(content) {
|
|
365
|
+
return typeof content === "string" ? content
|
|
366
|
+
: Array.isArray(content) ? content.filter((c) => c.type === "text").map((c) => c.text).join("") : "";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Remove <think>...</think> reasoning blocks and Gemini-style chain-of-thought so they are not shown in task.done. */
|
|
370
|
+
function stripThink(text) {
|
|
371
|
+
if (!text || typeof text !== "string") return text;
|
|
372
|
+
|
|
373
|
+
return text
|
|
374
|
+
// Strip standard <think> XML tags (DeepSeek, etc.)
|
|
375
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, "")
|
|
376
|
+
.replace(/<\/think>/g, "")
|
|
377
|
+
.replace(/<think>/g, "")
|
|
378
|
+
|
|
379
|
+
// Strip Gemini/Grok-style thinking headers
|
|
380
|
+
.replace(/\*\*\[Grok-\d+[^\]]*:[\s\S]*?\.\]\*\*/g, "")
|
|
381
|
+
|
|
382
|
+
// Strip "My Chain of Thought" statements
|
|
383
|
+
.replace(/My Chain of Thought (is active|has been updated)[.\n]*/gi, "")
|
|
384
|
+
|
|
385
|
+
// Strip "Project context confirmed"
|
|
386
|
+
.replace(/Project context confirmed:[^\n]*\n*/g, "")
|
|
387
|
+
|
|
388
|
+
// Strip other thinking-style prefixes
|
|
389
|
+
.replace(/\*\*My (thinking|analysis|approach):\*\*/gi, "")
|
|
390
|
+
.replace(/\n*\*\*Chain of Thought\*\*:[\s\S]*?(?=\n\n|\n\*\*|$)/gi, "")
|
|
391
|
+
.trim();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function parseJsonSafe(raw, fallback = {}) {
|
|
395
|
+
try {
|
|
396
|
+
const parsed = JSON.parse(raw);
|
|
397
|
+
return parsed && typeof parsed === "object" ? parsed : fallback;
|
|
398
|
+
} catch {
|
|
399
|
+
return fallback;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function currentUtcLabel(date = new Date()) {
|
|
404
|
+
const iso = date.toISOString();
|
|
405
|
+
return `${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function isoNow() {
|
|
409
|
+
return new Date().toISOString();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function memoryTemplate(fileName) {
|
|
413
|
+
const now = currentUtcLabel();
|
|
414
|
+
if (fileName === "current-state.md") {
|
|
415
|
+
return [
|
|
416
|
+
"# Current State",
|
|
417
|
+
"",
|
|
418
|
+
`Last updated: ${now}`,
|
|
419
|
+
`Updated by: ${MEMORY_BOOTSTRAP_AGENT}`,
|
|
420
|
+
"",
|
|
421
|
+
"## Project Snapshot",
|
|
422
|
+
"",
|
|
423
|
+
"- Status: initialization pending",
|
|
424
|
+
"- Active objective: define current objective",
|
|
425
|
+
"- Current phase: startup",
|
|
426
|
+
"",
|
|
427
|
+
"## In Progress",
|
|
428
|
+
"",
|
|
429
|
+
"- None yet.",
|
|
430
|
+
"",
|
|
431
|
+
"## Next Steps",
|
|
432
|
+
"",
|
|
433
|
+
"1. Confirm project objective.",
|
|
434
|
+
"2. Execute first concrete task.",
|
|
435
|
+
"3. Update this state after execution.",
|
|
436
|
+
"",
|
|
437
|
+
"## Constraints and Defaults",
|
|
438
|
+
"",
|
|
439
|
+
"- Memory files in `memory/` are source of truth.",
|
|
440
|
+
"- If memory and chat conflict, prefer latest timestamped memory entry.",
|
|
441
|
+
"- Do not remove historical entries from append-only logs.",
|
|
442
|
+
].join("\n");
|
|
443
|
+
}
|
|
444
|
+
if (fileName === "decisions.md") {
|
|
445
|
+
return [
|
|
446
|
+
"# Decisions",
|
|
447
|
+
"",
|
|
448
|
+
"Record durable choices here. Append new decisions at the top.",
|
|
449
|
+
"",
|
|
450
|
+
"## Template",
|
|
451
|
+
"",
|
|
452
|
+
"```",
|
|
453
|
+
"## [DEC-000] Title",
|
|
454
|
+
"- Date: YYYY-MM-DD HH:MM UTC",
|
|
455
|
+
"- Owner: agent-name-or-id",
|
|
456
|
+
"- Context: why this decision was needed",
|
|
457
|
+
"- Decision: what was chosen",
|
|
458
|
+
"- Impact: what this changes",
|
|
459
|
+
"- Revisit trigger: when to revisit this decision",
|
|
460
|
+
"```",
|
|
461
|
+
"",
|
|
462
|
+
"## Entries",
|
|
463
|
+
"",
|
|
464
|
+
"- None yet.",
|
|
465
|
+
].join("\n");
|
|
466
|
+
}
|
|
467
|
+
if (fileName === "open-questions.md") {
|
|
468
|
+
return [
|
|
469
|
+
"# Open Questions",
|
|
470
|
+
"",
|
|
471
|
+
"Track unresolved items. Move resolved questions to `memory/session-log.md` with outcome.",
|
|
472
|
+
"",
|
|
473
|
+
"## Template",
|
|
474
|
+
"",
|
|
475
|
+
"```",
|
|
476
|
+
"## [Q-000] Question title",
|
|
477
|
+
"- Opened: YYYY-MM-DD HH:MM UTC",
|
|
478
|
+
"- Opened by: agent-name-or-id",
|
|
479
|
+
"- Why it matters: impact on execution",
|
|
480
|
+
"- Needed input: exact answer needed",
|
|
481
|
+
"- Default if no answer: safe fallback",
|
|
482
|
+
"- Status: open | blocked | answered",
|
|
483
|
+
"```",
|
|
484
|
+
"",
|
|
485
|
+
"## Entries",
|
|
486
|
+
"",
|
|
487
|
+
"- None currently open.",
|
|
488
|
+
].join("\n");
|
|
489
|
+
}
|
|
490
|
+
if (fileName === "agent-handoff.md") {
|
|
491
|
+
return [
|
|
492
|
+
"# Agent Handoff",
|
|
493
|
+
"",
|
|
494
|
+
"Use this file for a fast restart brief. Overwrite sections each session; keep stable structure.",
|
|
495
|
+
"",
|
|
496
|
+
`Last updated: ${now}`,
|
|
497
|
+
`Updated by: ${MEMORY_BOOTSTRAP_AGENT}`,
|
|
498
|
+
"",
|
|
499
|
+
"## What just happened",
|
|
500
|
+
"",
|
|
501
|
+
"- Memory bootstrap initialized required files.",
|
|
502
|
+
"",
|
|
503
|
+
"## Current truth",
|
|
504
|
+
"",
|
|
505
|
+
"- Canonical memory path: `memory/`",
|
|
506
|
+
"- Required files are present.",
|
|
507
|
+
"",
|
|
508
|
+
"## Next best action",
|
|
509
|
+
"",
|
|
510
|
+
"1. Execute the incoming task.",
|
|
511
|
+
"2. Record outcomes in state and logs.",
|
|
512
|
+
"",
|
|
513
|
+
"## Risks",
|
|
514
|
+
"",
|
|
515
|
+
"- Context quality depends on keeping this file current.",
|
|
516
|
+
"",
|
|
517
|
+
"## If blocked",
|
|
518
|
+
"",
|
|
519
|
+
"- Use defaults from `memory/current-state.md` and log assumptions in `memory/session-log.md`.",
|
|
520
|
+
].join("\n");
|
|
521
|
+
}
|
|
522
|
+
if (fileName === "session-log.md") {
|
|
523
|
+
return [
|
|
524
|
+
"# Session Log",
|
|
525
|
+
"",
|
|
526
|
+
"Append-only execution log for all agents.",
|
|
527
|
+
"",
|
|
528
|
+
"## Template",
|
|
529
|
+
"",
|
|
530
|
+
"```",
|
|
531
|
+
"## YYYY-MM-DD HH:MM UTC | agent-name-or-id | task-id",
|
|
532
|
+
"- Intent: what the agent attempted",
|
|
533
|
+
"- Actions: key steps taken",
|
|
534
|
+
"- Result: success | partial | failed",
|
|
535
|
+
"- Artifacts: files changed / commands run",
|
|
536
|
+
"- Decisions: links to DEC ids (if any)",
|
|
537
|
+
"- Follow-ups: immediate next steps",
|
|
538
|
+
"```",
|
|
539
|
+
"",
|
|
540
|
+
"## Entries",
|
|
541
|
+
"",
|
|
542
|
+
].join("\n");
|
|
543
|
+
}
|
|
544
|
+
return "";
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function appendMemoryBootstrapLog(createdFiles) {
|
|
548
|
+
if (!Array.isArray(createdFiles) || !createdFiles.length) return;
|
|
549
|
+
try {
|
|
550
|
+
const sessionLogPath = path.join(SHARED_MEMORY_DIR, "session-log.md");
|
|
551
|
+
const ts = currentUtcLabel();
|
|
552
|
+
const lines = [
|
|
553
|
+
`## ${ts} | ${MEMORY_BOOTSTRAP_AGENT} | memory-bootstrap`,
|
|
554
|
+
"- Intent: Auto-create missing required memory files before task execution.",
|
|
555
|
+
`- Actions: Created templates for ${createdFiles.join(", ")}.`,
|
|
556
|
+
"- Result: success",
|
|
557
|
+
`- Artifacts: ${createdFiles.map((f) => `memory/${f}`).join(", ")}`,
|
|
558
|
+
"- Decisions: none",
|
|
559
|
+
"- Follow-ups: Confirm and refine bootstrap content during normal task shutdown updates.",
|
|
560
|
+
"",
|
|
561
|
+
];
|
|
562
|
+
fs.appendFileSync(sessionLogPath, `\n${lines.join("\n")}`);
|
|
563
|
+
} catch (err) {
|
|
564
|
+
telemetry("shared_memory_bootstrap_log_error", { message: err?.message ?? String(err) });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function ensureSharedMemoryFiles() {
|
|
569
|
+
try {
|
|
570
|
+
fs.mkdirSync(SHARED_MEMORY_DIR, { recursive: true });
|
|
571
|
+
const created = [];
|
|
572
|
+
for (const fileName of SHARED_MEMORY_FILES) {
|
|
573
|
+
const fullPath = path.join(SHARED_MEMORY_DIR, fileName);
|
|
574
|
+
if (fs.existsSync(fullPath)) continue;
|
|
575
|
+
fs.writeFileSync(fullPath, memoryTemplate(fileName), "utf8");
|
|
576
|
+
created.push(fileName);
|
|
577
|
+
telemetry("shared_memory_file_created", { fileName });
|
|
578
|
+
}
|
|
579
|
+
appendMemoryBootstrapLog(created);
|
|
580
|
+
return { created, error: null };
|
|
581
|
+
} catch (err) {
|
|
582
|
+
telemetry("shared_memory_bootstrap_error", { message: err?.message ?? String(err) });
|
|
583
|
+
return { created: [], error: err };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function getAgentOpenCodeConfig(agentId) {
|
|
588
|
+
const agents = loadAgentList();
|
|
589
|
+
const cfg = agents.find(a => a.id === agentId);
|
|
590
|
+
const fallback = cfg?.opencodeFallbackModel || getOpencodeFallbackModel();
|
|
591
|
+
const loop = cfg?.opencodeLoop === true || process.env.CREWSWARM_ENGINE_LOOP === "1";
|
|
592
|
+
const cursorCliModel = cfg?.cursorCliModel || null;
|
|
593
|
+
const claudeCodeModel = cfg?.claudeCodeModel || null;
|
|
594
|
+
const codexModel = cfg?.codexModel || null;
|
|
595
|
+
const geminiCliModel = cfg?.geminiCliModel || null;
|
|
596
|
+
const crewCliModel = cfg?.crewCliModel || null;
|
|
597
|
+
const agentModel = cfg?.model || null; // ← Get the agent's base model
|
|
598
|
+
if (!cfg) {
|
|
599
|
+
return {
|
|
600
|
+
enabled: false,
|
|
601
|
+
model: null,
|
|
602
|
+
fallbackModel: fallback,
|
|
603
|
+
loop: false,
|
|
604
|
+
useCursorCli: false,
|
|
605
|
+
cursorCliModel: null,
|
|
606
|
+
claudeCodeModel: null,
|
|
607
|
+
useClaudeCode: false,
|
|
608
|
+
useCodex: false,
|
|
609
|
+
codexModel: null,
|
|
610
|
+
useGeminiCli: false,
|
|
611
|
+
geminiCliModel: null,
|
|
612
|
+
useCrewCLI: false,
|
|
613
|
+
crewCliModel: null,
|
|
614
|
+
engine: null,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const assignedEngine = String(cfg.engine || "").toLowerCase() || null;
|
|
619
|
+
const useCursorCli = cfg.useCursorCli === true || assignedEngine === "cursor";
|
|
620
|
+
const useClaudeCode = cfg.useClaudeCode === true || assignedEngine === "claude";
|
|
621
|
+
const useCodex = cfg.useCodex === true || assignedEngine === "codex";
|
|
622
|
+
const useGeminiCli =
|
|
623
|
+
cfg.useGeminiCli === true ||
|
|
624
|
+
assignedEngine === "gemini" ||
|
|
625
|
+
assignedEngine === "gemini-cli";
|
|
626
|
+
const useCrewCLI =
|
|
627
|
+
cfg.useCrewCLI === true ||
|
|
628
|
+
assignedEngine === "crew-cli";
|
|
629
|
+
|
|
630
|
+
if (cfg.useOpenCode === true || assignedEngine === "opencode") {
|
|
631
|
+
return {
|
|
632
|
+
enabled: true,
|
|
633
|
+
model: cfg.opencodeModel || agentModel,
|
|
634
|
+
fallbackModel: fallback,
|
|
635
|
+
loop,
|
|
636
|
+
useCursorCli,
|
|
637
|
+
cursorCliModel,
|
|
638
|
+
claudeCodeModel,
|
|
639
|
+
useClaudeCode,
|
|
640
|
+
useCodex,
|
|
641
|
+
codexModel,
|
|
642
|
+
useGeminiCli,
|
|
643
|
+
geminiCliModel,
|
|
644
|
+
useCrewCLI,
|
|
645
|
+
crewCliModel,
|
|
646
|
+
engine: assignedEngine,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (cfg.useOpenCode === false || (assignedEngine && assignedEngine !== "opencode")) {
|
|
651
|
+
return {
|
|
652
|
+
enabled: false,
|
|
653
|
+
model: agentModel,
|
|
654
|
+
fallbackModel: fallback,
|
|
655
|
+
loop: false,
|
|
656
|
+
useCursorCli,
|
|
657
|
+
cursorCliModel,
|
|
658
|
+
claudeCodeModel,
|
|
659
|
+
useClaudeCode,
|
|
660
|
+
useCodex,
|
|
661
|
+
codexModel,
|
|
662
|
+
useGeminiCli,
|
|
663
|
+
geminiCliModel,
|
|
664
|
+
useCrewCLI,
|
|
665
|
+
crewCliModel,
|
|
666
|
+
engine: assignedEngine,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
enabled: false,
|
|
672
|
+
model: cfg.opencodeModel || agentModel,
|
|
673
|
+
fallbackModel: fallback,
|
|
674
|
+
loop: false,
|
|
675
|
+
useCursorCli,
|
|
676
|
+
cursorCliModel,
|
|
677
|
+
claudeCodeModel,
|
|
678
|
+
useClaudeCode,
|
|
679
|
+
useCodex,
|
|
680
|
+
codexModel,
|
|
681
|
+
useGeminiCli,
|
|
682
|
+
geminiCliModel,
|
|
683
|
+
engine: assignedEngine,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Engine runners → lib/engines/runners.mjs
|
|
688
|
+
|
|
689
|
+
function shouldConnectGateway(args) {
|
|
690
|
+
if (process.env.CREWSWARM_FORCE_GATEWAY === "1") return true;
|
|
691
|
+
if (args.includes("--broadcast")) return false;
|
|
692
|
+
if (args[0] === "--send") return false;
|
|
693
|
+
// In RT-daemon mode: skip legacy gateway unless explicitly forced.
|
|
694
|
+
// Agents use direct LLM calls; legacy gateway is optional.
|
|
695
|
+
if (args.includes("--rt-daemon")) {
|
|
696
|
+
if (process.env.CREWSWARM_GATEWAY_ENABLED === "1") return true;
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function createOpenCodeOnlyBridge() {
|
|
703
|
+
return {
|
|
704
|
+
kind: "opencode",
|
|
705
|
+
chat: async (msg) => runOpenCodeTask(msg, {}),
|
|
706
|
+
close: () => { },
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// ---------------------------------------------------------------------------
|
|
711
|
+
// runCursorWaveTask — dispatch a full wave of tasks to Cursor subagents in
|
|
712
|
+
// parallel via the crew-orchestrator subagent. The orchestrator receives the
|
|
713
|
+
// wave manifest as JSON and fans out to /crew-* subagents simultaneously,
|
|
714
|
+
// returning a combined === WAVE [n] RESULTS === report.
|
|
715
|
+
// ---------------------------------------------------------------------------
|
|
716
|
+
async function runCursorWaveTask(waveIndex, tasks, payload = {}) {
|
|
717
|
+
const projectDir = payload?.projectDir || getOpencodeProjectDir() || process.cwd();
|
|
718
|
+
const context = payload?.priorWaveContext || "";
|
|
719
|
+
|
|
720
|
+
const manifest = {
|
|
721
|
+
wave: waveIndex + 1,
|
|
722
|
+
projectDir,
|
|
723
|
+
context: context ? context.slice(0, 2000) : undefined,
|
|
724
|
+
tasks: tasks.map(t => ({ agent: t.agent, task: t.task })),
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// Build the orchestrator prompt: instruct it to dispatch all tasks in parallel
|
|
728
|
+
const orchestratorPrompt = [
|
|
729
|
+
`[crew-orchestrator] Execute this wave manifest — dispatch ALL tasks to subagents in parallel:`,
|
|
730
|
+
"```json",
|
|
731
|
+
JSON.stringify(manifest, null, 2),
|
|
732
|
+
"```",
|
|
733
|
+
`Dispatch all ${tasks.length} task(s) simultaneously using the Task tool. Return combined results.`,
|
|
734
|
+
].join("\n");
|
|
735
|
+
|
|
736
|
+
console.error(`[CursorWave] Wave ${waveIndex + 1}: dispatching ${tasks.length} tasks via crew-orchestrator in parallel`);
|
|
737
|
+
tasks.forEach(t => console.error(` → ${t.agent}: ${String(t.task).slice(0, 80)}`));
|
|
738
|
+
|
|
739
|
+
return runCursorCliTask(orchestratorPrompt, {
|
|
740
|
+
...payload,
|
|
741
|
+
agentId: "crew-orchestrator",
|
|
742
|
+
projectDir,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// runOpenCodeTask → lib/engines/opencode.mjs
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
/** Extract project root from task text when it contains absolute paths (e.g. /Users/.../Desktop/polymarket-ai-strat/...). */
|
|
750
|
+
function extractProjectDirFromTask(taskText) {
|
|
751
|
+
if (!taskText || typeof taskText !== "string") return null;
|
|
752
|
+
// Match /Users/<user>/Desktop/<project-name> with optional trailing slash or /subpath
|
|
753
|
+
const m = taskText.match(/\/Users\/[^/]+\/Desktop\/[^/\s]+/);
|
|
754
|
+
if (!m) return null;
|
|
755
|
+
return m[0];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/** Minimal prompt for OpenCode: task + project path only. Enhanced with shared memory from CLI. */
|
|
759
|
+
async function buildMiniTaskForOpenCode(taskText, agentId, projectDir) {
|
|
760
|
+
let dir = projectDir || getOpencodeProjectDir() || null;
|
|
761
|
+
if (!dir || dir === process.cwd()) {
|
|
762
|
+
const fromTask = extractProjectDirFromTask(taskText);
|
|
763
|
+
if (fromTask) dir = fromTask;
|
|
764
|
+
}
|
|
765
|
+
dir = dir || process.cwd();
|
|
766
|
+
const taskForPrompt = rewriteTaskPathsRelativeToProjectRoot(taskText, dir);
|
|
767
|
+
|
|
768
|
+
// Prepend condensed memory — now using shared memory adapter
|
|
769
|
+
const readSafe = (p) => { try { return fs.readFileSync(p, "utf8").trim(); } catch { return ""; } };
|
|
770
|
+
const memParts = [];
|
|
771
|
+
|
|
772
|
+
const globalRules = readSafe(path.join(os.homedir(), ".crewswarm", "global-rules.md"));
|
|
773
|
+
if (globalRules) memParts.push(`Global rules:\n${globalRules}`);
|
|
774
|
+
|
|
775
|
+
// Use CLI's MemoryBroker if available (blends AgentKeeper + AgentMemory + Collections)
|
|
776
|
+
// crew-cli gets MINIMAL context (it has its own L2 RAG + planning)
|
|
777
|
+
// BUT we DO want to pass project-specific hints (constraints, decisions), not cross-project generic stuff
|
|
778
|
+
const agents = loadAgentList();
|
|
779
|
+
const agentConfig = agents.find(a => a.id === agentId);
|
|
780
|
+
const usingCrewCLI = agentConfig?.engine === 'crew-cli';
|
|
781
|
+
|
|
782
|
+
let sharedMemoryContext = '';
|
|
783
|
+
if (isSharedMemoryAvailable() && !usingCrewCLI) {
|
|
784
|
+
try {
|
|
785
|
+
// Adaptive memory result scaling based on task complexity
|
|
786
|
+
const taskTokens = taskForPrompt.split(/\s+/).length;
|
|
787
|
+
const maxResults = taskTokens < 50 ? 3 // Simple: "write hello.js"
|
|
788
|
+
: taskTokens < 150 ? 5 // Medium: "build auth endpoint"
|
|
789
|
+
: 8; // Complex: detailed requirements
|
|
790
|
+
|
|
791
|
+
// Skip memory recall for chat-only agents (crew-loco)
|
|
792
|
+
// They should only see their own conversation history, not project work
|
|
793
|
+
if (agentId !== 'crew-loco') {
|
|
794
|
+
sharedMemoryContext = await recallMemoryContext(dir, taskForPrompt, {
|
|
795
|
+
maxResults,
|
|
796
|
+
includeDocs: true,
|
|
797
|
+
includeCode: false,
|
|
798
|
+
preferSuccessful: true,
|
|
799
|
+
crewId: agentId || 'crew-lead'
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
} catch (err) {
|
|
803
|
+
console.warn(`[gateway-bridge] Shared memory recall failed: ${err.message}`);
|
|
804
|
+
}
|
|
805
|
+
} else if (usingCrewCLI && dir) {
|
|
806
|
+
// For crew-cli: Pass ONLY project-specific hints (decisions/constraints, not full code)
|
|
807
|
+
// crew-cli's L2 RAG will load the actual files it needs automatically
|
|
808
|
+
try {
|
|
809
|
+
const projectHints = await recallMemoryContext(dir, taskForPrompt, {
|
|
810
|
+
maxResults: 2, // Minimal - just key decisions
|
|
811
|
+
includeDocs: false, // crew-cli loads its own docs via L2 RAG
|
|
812
|
+
includeCode: false,
|
|
813
|
+
preferSuccessful: true,
|
|
814
|
+
crewId: agentId || 'crew-lead'
|
|
815
|
+
});
|
|
816
|
+
if (projectHints) {
|
|
817
|
+
// Extract just decision/constraint lines, no code blocks
|
|
818
|
+
const lines = projectHints.split('\n').filter(line =>
|
|
819
|
+
!line.includes('```') && // No code blocks
|
|
820
|
+
!line.includes('@@') && // No tool calls
|
|
821
|
+
line.trim().length > 0 &&
|
|
822
|
+
line.trim().length < 200 // No long paragraphs
|
|
823
|
+
);
|
|
824
|
+
sharedMemoryContext = lines.slice(0, 5).join('\n'); // Max 5 hint lines
|
|
825
|
+
if (sharedMemoryContext) {
|
|
826
|
+
console.log(`[gateway-bridge] crew-cli: passing ${lines.length} project hint lines (${sharedMemoryContext.length} chars)`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
} catch (err) {
|
|
830
|
+
console.warn(`[gateway-bridge] Project hints failed: ${err.message}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Fallback to legacy brain.md if shared memory not available or empty
|
|
835
|
+
// Skip for crew-cli (it has its own context management)
|
|
836
|
+
if (!sharedMemoryContext && !usingCrewCLI) {
|
|
837
|
+
const lessons = readSafe(path.join(SHARED_MEMORY_DIR, "lessons.md"));
|
|
838
|
+
if (lessons) memParts.push(`Lessons learned:\n${lessons}`);
|
|
839
|
+
|
|
840
|
+
const brain = readSafe(path.join(SHARED_MEMORY_DIR, "brain.md")).slice(-1500);
|
|
841
|
+
if (brain) memParts.push(`Shared brain (recent):\n${brain}`);
|
|
842
|
+
|
|
843
|
+
// Project-specific brain if projectDir has one
|
|
844
|
+
if (dir) {
|
|
845
|
+
const projBrain = readSafe(path.join(dir, ".crewswarm", "brain.md")).slice(-1000);
|
|
846
|
+
if (projBrain) memParts.push(`Project brain:\n${projBrain}`);
|
|
847
|
+
|
|
848
|
+
const roadmap = readSafe(path.join(dir, "ROADMAP.md")).slice(-1500);
|
|
849
|
+
if (roadmap) memParts.push(`Active ROADMAP:\n${roadmap}`);
|
|
850
|
+
}
|
|
851
|
+
} else if (sharedMemoryContext) {
|
|
852
|
+
memParts.push(sharedMemoryContext);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const memHeader = memParts.length > 0
|
|
856
|
+
? `[Memory context — read before acting]\n${memParts.join("\n\n")}\n[End memory context]\n\n`
|
|
857
|
+
: "";
|
|
858
|
+
|
|
859
|
+
// crew-cli gets minimal prompt with optional project hints
|
|
860
|
+
// crew-cli has its own L2 RAG, memory broker, and context management
|
|
861
|
+
// We pass project-specific constraints/decisions as hints, but let crew-cli load files itself
|
|
862
|
+
if (usingCrewCLI) {
|
|
863
|
+
const contextInfo = sharedMemoryContext
|
|
864
|
+
? `\n\nProject constraints/decisions:\n${sharedMemoryContext}`
|
|
865
|
+
: '';
|
|
866
|
+
console.log(`[gateway-bridge] crew-cli: minimal context${contextInfo ? ` + ${sharedMemoryContext.length} char hints` : ' (no hints)'}`);
|
|
867
|
+
return `[${agentId}] ${taskText}${contextInfo}\n\nProject directory: ${dir}. crew-cli will load relevant files automatically via L2 RAG.`;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
return `${memHeader}[${agentId}] ${taskText}\n\nProject directory: ${dir}. Use the project files to complete this task only.`;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Ouroboros-style LLM ↔ engine loop (see https://github.com/joi-lab/ouroboros).
|
|
875
|
+
* LLM decomposes task into steps; each step is executed by the chosen engine (OpenCode, Cursor CLI,
|
|
876
|
+
* or Claude Code); results are fed back until LLM says DONE or max rounds is reached.
|
|
877
|
+
* engine: "opencode" | "cursor" | "claude" — selects which execution backend runs each step.
|
|
878
|
+
*/
|
|
879
|
+
// callLLMDirect → lib/engines/llm-direct.mjs
|
|
880
|
+
initLlmDirect({ loadAgentLLMConfig, checkSpendingCap, notifyTelegramSpending, recordTokenUsage, loadProviderMap });
|
|
881
|
+
|
|
882
|
+
// runOpenCodeTask → lib/engines/opencode.mjs
|
|
883
|
+
initOpenCode({
|
|
884
|
+
CREWSWARM_OPENCODE_BIN, CREWSWARM_RT_AGENT, CREWSWARM_OPENCODE_MODEL,
|
|
885
|
+
CREWSWARM_OPENCODE_TIMEOUT_MS, CREWSWARM_OPENCODE_AGENT,
|
|
886
|
+
getAgentOpenCodeConfig, getOpencodeProjectDir,
|
|
887
|
+
extractProjectDirFromTask, readAgentSessionId, writeAgentSessionId,
|
|
888
|
+
parseMostRecentSessionId, isOpencodeRateLimitBanner,
|
|
889
|
+
get _rtClientForApprovals() { return _rtClientForApprovals; },
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// Engine runners → lib/engines/runners.mjs
|
|
893
|
+
initRunners({ getAgentOpenCodeConfig, loadAgentList, getOpencodeProjectDir, buildMiniTaskForOpenCode, runOpenCodeTask, loadGenericEngines });
|
|
894
|
+
|
|
895
|
+
// crew-cli engine → lib/engines/crew-cli.mjs
|
|
896
|
+
initCrewCLI({
|
|
897
|
+
CREWSWARM_RT_AGENT,
|
|
898
|
+
getAgentOpenCodeConfig,
|
|
899
|
+
getOpencodeProjectDir,
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// runOuroborosStyleLoop → lib/engines/ouroboros.mjs
|
|
903
|
+
initOuroboros({
|
|
904
|
+
loadAgentList,
|
|
905
|
+
loadLoopBrainConfig,
|
|
906
|
+
loadAgentPrompts,
|
|
907
|
+
callLLMDirect,
|
|
908
|
+
buildMiniTaskForOpenCode,
|
|
909
|
+
runCursorCliTask,
|
|
910
|
+
runClaudeCodeTask,
|
|
911
|
+
runCodexTask,
|
|
912
|
+
runOpenCodeTask,
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
// handleRealtimeEnvelope → lib/engines/rt-envelope.mjs
|
|
916
|
+
initRtEnvelope({
|
|
917
|
+
// dispatch lease (gateway-bridge local)
|
|
918
|
+
acquireTaskLease,
|
|
919
|
+
renewTaskLease,
|
|
920
|
+
releaseTaskLease,
|
|
921
|
+
markTaskDone,
|
|
922
|
+
dispatchKeyForTask,
|
|
923
|
+
// spawn / status
|
|
924
|
+
resolveSpawnTargets,
|
|
925
|
+
spawnAgentDaemon,
|
|
926
|
+
isAgentDaemonRunning,
|
|
927
|
+
readPid,
|
|
928
|
+
pendingCmdApprovals,
|
|
929
|
+
// routing
|
|
930
|
+
selectEngine,
|
|
931
|
+
shouldUseCursorCli,
|
|
932
|
+
shouldUseClaudeCode,
|
|
933
|
+
shouldUseCodex,
|
|
934
|
+
shouldUseDockerSandbox,
|
|
935
|
+
shouldUseGeminiCli,
|
|
936
|
+
shouldUseOpenCode,
|
|
937
|
+
shouldUseGenericEngine,
|
|
938
|
+
loadGenericEngines,
|
|
939
|
+
// runners
|
|
940
|
+
runCursorCliTask,
|
|
941
|
+
runClaudeCodeTask,
|
|
942
|
+
runCodexTask,
|
|
943
|
+
runGeminiCliTask,
|
|
944
|
+
runGenericEngineTask,
|
|
945
|
+
runDockerSandboxTask,
|
|
946
|
+
runCrewCLITask,
|
|
947
|
+
runOpenCodeTask,
|
|
948
|
+
runOuroborosStyleLoop,
|
|
949
|
+
// llm + prompt
|
|
950
|
+
callLLMDirect,
|
|
951
|
+
buildTaskPrompt,
|
|
952
|
+
buildMiniTaskForOpenCode,
|
|
953
|
+
// tools
|
|
954
|
+
executeToolCalls,
|
|
955
|
+
// memory
|
|
956
|
+
loadAgentPrompts,
|
|
957
|
+
// validation
|
|
958
|
+
validateCodingArtifacts,
|
|
959
|
+
isCodingTask,
|
|
960
|
+
stripThink,
|
|
961
|
+
shouldUseDispatchGuard,
|
|
962
|
+
shouldRetryTaskFailure,
|
|
963
|
+
assertTaskPromptProtocol,
|
|
964
|
+
// utils
|
|
965
|
+
telemetry,
|
|
966
|
+
progress,
|
|
967
|
+
extractProjectDirFromTask,
|
|
968
|
+
getAgentOpenCodeConfig,
|
|
969
|
+
getOpencodeProjectDir,
|
|
970
|
+
// consts
|
|
971
|
+
CREWSWARM_RT_AGENT,
|
|
972
|
+
CREWSWARM_RT_COMMAND_TYPES,
|
|
973
|
+
CREWSWARM_RT_DISPATCH_LEASE_MS,
|
|
974
|
+
CREWSWARM_RT_DISPATCH_HEARTBEAT_MS,
|
|
975
|
+
CREWSWARM_RT_DISPATCH_MAX_RETRIES,
|
|
976
|
+
CREWSWARM_RT_DISPATCH_MAX_RETRIES_CODING,
|
|
977
|
+
CREWSWARM_RT_DISPATCH_RETRY_BACKOFF_MS,
|
|
978
|
+
CREWSWARM_OPENCODE_AGENT,
|
|
979
|
+
CREWSWARM_OPENCODE_MODEL,
|
|
980
|
+
OPENCODE_FREE_MODEL_CHAIN,
|
|
981
|
+
RT_TO_GATEWAY_AGENT_MAP,
|
|
982
|
+
SHARED_MEMORY_DIR,
|
|
983
|
+
SWARM_DLQ_DIR,
|
|
984
|
+
COORDINATOR_AGENT_IDS,
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
function printMemoryStatus() {
|
|
988
|
+
const bundle = loadSharedMemoryBundle();
|
|
989
|
+
console.log(`Shared memory directory: ${SHARED_MEMORY_DIR}`);
|
|
990
|
+
console.log(`Files expected: ${SHARED_MEMORY_FILES.length}`);
|
|
991
|
+
console.log(`Files included: ${bundle.included.length}`);
|
|
992
|
+
if (bundle.included.length) {
|
|
993
|
+
for (const f of bundle.included) console.log(`- ok: ${f}`);
|
|
994
|
+
}
|
|
995
|
+
if (bundle.missing.length) {
|
|
996
|
+
for (const f of bundle.missing) console.log(`- missing: ${f}`);
|
|
997
|
+
}
|
|
998
|
+
console.log(`Context bytes prepared: ${bundle.bytes}`);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function printMetrics() {
|
|
1002
|
+
const events = readTelemetryEvents();
|
|
1003
|
+
if (!events.length) {
|
|
1004
|
+
console.log("No telemetry events yet. Run --quickstart first.");
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
const byRun = new Map();
|
|
1009
|
+
for (const ev of events) {
|
|
1010
|
+
const key = ev.runId ?? "unknown";
|
|
1011
|
+
if (!byRun.has(key)) byRun.set(key, []);
|
|
1012
|
+
byRun.get(key).push(ev);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const connectSuccess = events.filter((e) => e.event === "connect_success").length;
|
|
1016
|
+
const connectError = events.filter((e) => e.event === "connect_error").length;
|
|
1017
|
+
const chatStarted = events.filter((e) => e.event === "chat_started").length;
|
|
1018
|
+
const chatDone = events.filter((e) => e.event === "chat_done").length;
|
|
1019
|
+
const retries = events.filter((e) => e.event === "retry_attempt").length;
|
|
1020
|
+
|
|
1021
|
+
const ttfvMs = [];
|
|
1022
|
+
const chatLatencyMs = [];
|
|
1023
|
+
|
|
1024
|
+
for (const runEvents of byRun.values()) {
|
|
1025
|
+
runEvents.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
1026
|
+
const onboarding = runEvents.find((e) => e.event === "onboarding_started");
|
|
1027
|
+
const connected = runEvents.find((e) => e.event === "connect_success");
|
|
1028
|
+
if (onboarding && connected) {
|
|
1029
|
+
const delta = new Date(connected.timestamp).getTime() - new Date(onboarding.timestamp).getTime();
|
|
1030
|
+
if (delta >= 0) ttfvMs.push(delta);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const started = runEvents.find((e) => e.event === "chat_started");
|
|
1034
|
+
const done = runEvents.find((e) => e.event === "chat_done");
|
|
1035
|
+
if (started && done) {
|
|
1036
|
+
const delta = new Date(done.timestamp).getTime() - new Date(started.timestamp).getTime();
|
|
1037
|
+
if (delta >= 0) chatLatencyMs.push(delta);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const connectAttempts = connectSuccess + connectError;
|
|
1042
|
+
const connectRate = connectAttempts ? ((connectSuccess / connectAttempts) * 100).toFixed(1) : "n/a";
|
|
1043
|
+
const chatCompletion = chatStarted ? ((chatDone / chatStarted) * 100).toFixed(1) : "n/a";
|
|
1044
|
+
|
|
1045
|
+
console.log("crewswarm Metrics");
|
|
1046
|
+
console.log(`- Sessions observed: ${byRun.size}`);
|
|
1047
|
+
console.log(`- Connect success rate: ${connectRate}${connectRate === "n/a" ? "" : "%"} (${connectSuccess}/${connectAttempts || 0})`);
|
|
1048
|
+
console.log(`- Chat completion rate: ${chatCompletion}${chatCompletion === "n/a" ? "" : "%"} (${chatDone}/${chatStarted || 0})`);
|
|
1049
|
+
console.log(`- Retry attempts: ${retries}`);
|
|
1050
|
+
if (ttfvMs.length) {
|
|
1051
|
+
const p95 = percentile(ttfvMs, 95);
|
|
1052
|
+
console.log(`- Time-to-first-value: median ${formatDuration(median(ttfvMs))}, p95 ${formatDuration(p95)}`);
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log("- Time-to-first-value: n/a (run --quickstart a few times)");
|
|
1055
|
+
}
|
|
1056
|
+
if (chatLatencyMs.length) {
|
|
1057
|
+
const p95 = percentile(chatLatencyMs, 95);
|
|
1058
|
+
console.log(`- Chat latency: median ${formatDuration(median(chatLatencyMs))}, p95 ${formatDuration(p95)}`);
|
|
1059
|
+
} else {
|
|
1060
|
+
console.log("- Chat latency: n/a");
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function loadCredentials() {
|
|
1065
|
+
const dev = JSON.parse(fs.readFileSync(path.join(LEGACY_STATE_DIR, "identity/device.json"), "utf8"));
|
|
1066
|
+
// Try crewswarm.json first, fall back to openclaw.json
|
|
1067
|
+
let cfg = loadSwarmConfig();
|
|
1068
|
+
if (!cfg || !Object.keys(cfg).length) {
|
|
1069
|
+
try { cfg = JSON.parse(fs.readFileSync(path.join(LEGACY_STATE_DIR, "openclaw.json"), "utf8")); } catch { cfg = {}; }
|
|
1070
|
+
}
|
|
1071
|
+
const gatewayToken = cfg.gateway?.auth?.token;
|
|
1072
|
+
// Prefer gateway token when gateway is in token mode (avoids device token mismatch)
|
|
1073
|
+
if (cfg.gateway?.auth?.mode === "token" && gatewayToken) {
|
|
1074
|
+
return { dev, authToken: gatewayToken };
|
|
1075
|
+
}
|
|
1076
|
+
let deviceToken;
|
|
1077
|
+
try {
|
|
1078
|
+
const da = JSON.parse(fs.readFileSync(path.join(LEGACY_STATE_DIR, "identity/device-auth.json"), "utf8"));
|
|
1079
|
+
deviceToken = da?.tokens?.operator?.token;
|
|
1080
|
+
} catch (e) {
|
|
1081
|
+
console.error(`[gateway-bridge] Failed to read device-auth.json: ${e.message}`);
|
|
1082
|
+
}
|
|
1083
|
+
return { dev, authToken: deviceToken || gatewayToken };
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// ─── Load credentials ───────────────────────────────────────────────────────
|
|
1087
|
+
// Loaded lazily in main for better first-run errors.
|
|
1088
|
+
|
|
1089
|
+
// ─── Bridge ─────────────────────────────────────────────────────────────────
|
|
1090
|
+
const { createRealtimeClient, createBridge, runRealtimeDaemon } = initGatewayWs({
|
|
1091
|
+
WebSocket,
|
|
1092
|
+
crypto,
|
|
1093
|
+
CREWSWARM_RT_URL,
|
|
1094
|
+
CREWSWARM_RT_TLS_INSECURE,
|
|
1095
|
+
CREWSWARM_RT_TOKEN,
|
|
1096
|
+
GATEWAY_URL,
|
|
1097
|
+
PROTOCOL_VERSION,
|
|
1098
|
+
REQUEST_TIMEOUT_MS,
|
|
1099
|
+
CHAT_TIMEOUT_MS,
|
|
1100
|
+
CREWSWARM_RT_AGENT,
|
|
1101
|
+
CREWSWARM_RT_CHANNELS,
|
|
1102
|
+
CREWSWARM_RT_RECONNECT_MS,
|
|
1103
|
+
telemetry,
|
|
1104
|
+
progress,
|
|
1105
|
+
parseJsonSafe,
|
|
1106
|
+
parseTextContent,
|
|
1107
|
+
withRetry,
|
|
1108
|
+
sleep,
|
|
1109
|
+
b64url,
|
|
1110
|
+
deriveRaw,
|
|
1111
|
+
syncOpenCodePermissions,
|
|
1112
|
+
handleRealtimeEnvelope,
|
|
1113
|
+
setRtClient,
|
|
1114
|
+
setRtClientForRunners,
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
1118
|
+
const args = process.argv.slice(2);
|
|
1119
|
+
const usage = "Usage: node gateway-bridge.mjs \"message\" | --send <agent> \"message\" | --broadcast \"message\" | --status | ...";
|
|
1120
|
+
|
|
1121
|
+
function printStatusSummary(res) {
|
|
1122
|
+
const channels = Array.isArray(res?.channels) ? res.channels : [];
|
|
1123
|
+
if (!channels.length) {
|
|
1124
|
+
console.log("No channel list available; raw status follows:");
|
|
1125
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
console.log(`Channels: ${channels.length}`);
|
|
1129
|
+
for (const ch of channels.slice(0, 12)) {
|
|
1130
|
+
const name = ch.name ?? ch.id ?? "unknown";
|
|
1131
|
+
const state = ch.state ?? ch.status ?? "unknown";
|
|
1132
|
+
const detail = ch.mode ? ` (${ch.mode})` : "";
|
|
1133
|
+
console.log(`- ${name}: ${state}${detail}`);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
async function runRealtimeStatusCheck() {
|
|
1138
|
+
progress(`Connecting to OpenCrew RT ${CREWSWARM_RT_URL}...`);
|
|
1139
|
+
const rt = await withRetry(() => createRealtimeClient({ onEnvelope: null }), {
|
|
1140
|
+
retries: 2,
|
|
1141
|
+
baseDelayMs: 300,
|
|
1142
|
+
label: "realtime connect",
|
|
1143
|
+
});
|
|
1144
|
+
console.log(`OpenCrew RT connected as ${CREWSWARM_RT_AGENT}`);
|
|
1145
|
+
console.log(`- URL: ${CREWSWARM_RT_URL}`);
|
|
1146
|
+
console.log(`- Channels: ${CREWSWARM_RT_CHANNELS.join(", ")}`);
|
|
1147
|
+
console.log(`- Token configured: ${CREWSWARM_RT_TOKEN ? "yes" : "no"}`);
|
|
1148
|
+
rt.close();
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
async function runBroadcastTask(message, { timeoutMs = 25000 } = {}) {
|
|
1152
|
+
const taskId = `broadcast-${Date.now()}`;
|
|
1153
|
+
const sender = process.env.CREWSWARM_RT_BROADCAST_SENDER || "orchestrator";
|
|
1154
|
+
const replies = [];
|
|
1155
|
+
let deliveredExpected = 0;
|
|
1156
|
+
|
|
1157
|
+
const rt = await withRetry(() => createRealtimeClient({
|
|
1158
|
+
agentName: sender,
|
|
1159
|
+
channels: ["done", "issues"],
|
|
1160
|
+
onEnvelope: async (envelope) => {
|
|
1161
|
+
if (!envelope || envelope.taskId !== taskId) return;
|
|
1162
|
+
if (envelope.channel !== "done" && envelope.channel !== "issues") return;
|
|
1163
|
+
replies.push({
|
|
1164
|
+
from: envelope.from || "unknown",
|
|
1165
|
+
channel: envelope.channel,
|
|
1166
|
+
type: envelope.type,
|
|
1167
|
+
payload: envelope.payload || {},
|
|
1168
|
+
});
|
|
1169
|
+
},
|
|
1170
|
+
}), { retries: 2, baseDelayMs: 300, label: "realtime broadcast connect" });
|
|
1171
|
+
|
|
1172
|
+
try {
|
|
1173
|
+
rt.publish({
|
|
1174
|
+
channel: "command",
|
|
1175
|
+
type: "command.run_task",
|
|
1176
|
+
to: "broadcast",
|
|
1177
|
+
taskId,
|
|
1178
|
+
priority: "high",
|
|
1179
|
+
payload: {
|
|
1180
|
+
action: "run_task",
|
|
1181
|
+
prompt: message,
|
|
1182
|
+
source: "crewswarm-broadcast",
|
|
1183
|
+
},
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
const startedAt = Date.now();
|
|
1187
|
+
const waitForReplies = async () => {
|
|
1188
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1189
|
+
await sleep(300);
|
|
1190
|
+
const uniqueResponders = new Set(replies.map((r) => r.from)).size;
|
|
1191
|
+
if (deliveredExpected > 0 && uniqueResponders >= deliveredExpected) break;
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
await waitForReplies();
|
|
1196
|
+
|
|
1197
|
+
const grouped = new Map();
|
|
1198
|
+
for (const r of replies) {
|
|
1199
|
+
if (!grouped.has(r.from)) grouped.set(r.from, []);
|
|
1200
|
+
grouped.get(r.from).push(r);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const lines = [];
|
|
1204
|
+
lines.push(`Broadcast sent as ${sender} (taskId: ${taskId})`);
|
|
1205
|
+
lines.push(`Responses: ${grouped.size}`);
|
|
1206
|
+
for (const [agent, entries] of grouped.entries()) {
|
|
1207
|
+
const latest = entries[entries.length - 1];
|
|
1208
|
+
if (latest.channel === "done") {
|
|
1209
|
+
const reply = String(latest.payload?.reply || "ok").replace(/\s+/g, " ").slice(0, 220);
|
|
1210
|
+
lines.push(`- ${agent}: done - ${reply}`);
|
|
1211
|
+
} else {
|
|
1212
|
+
const err = String(latest.payload?.error || "failed").replace(/\s+/g, " ").slice(0, 220);
|
|
1213
|
+
lines.push(`- ${agent}: issue - ${err}`);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
return lines.join("\n");
|
|
1218
|
+
} finally {
|
|
1219
|
+
rt.close();
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Send a task to a specific RT agent (targeted delegation). Only that agent processes it.
|
|
1225
|
+
* Use this for PM-led orchestration: PM plan → send each subtask to the assigned agent.
|
|
1226
|
+
* When agentId is crew-main and task is synthesis, the crew-main daemon routes to OpenCode
|
|
1227
|
+
* (OPENCODE_AGENTS); pass projectDir so OpenCode runs in the PM output dir.
|
|
1228
|
+
*/
|
|
1229
|
+
async function runSendToAgent(agentId, message, { timeoutMs = Number(process.env.CREWSWARM_RT_SEND_TIMEOUT_MS || "120000"), projectDir } = {}) {
|
|
1230
|
+
const taskId = `send-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
1231
|
+
const correlationId = crypto.randomUUID();
|
|
1232
|
+
const sender = process.env.CREWSWARM_RT_SEND_SENDER || "orchestrator";
|
|
1233
|
+
let reply = null;
|
|
1234
|
+
let done = false;
|
|
1235
|
+
|
|
1236
|
+
const rt = await withRetry(() => createRealtimeClient({
|
|
1237
|
+
agentName: sender,
|
|
1238
|
+
channels: ["done", "issues"],
|
|
1239
|
+
onEnvelope: (envelope) => {
|
|
1240
|
+
if (done) return;
|
|
1241
|
+
if (envelope.channel !== "done" && envelope.channel !== "issues") return;
|
|
1242
|
+
if (envelope.from !== agentId) return;
|
|
1243
|
+
const match = envelope.taskId === taskId ||
|
|
1244
|
+
envelope.correlationId === correlationId ||
|
|
1245
|
+
(envelope.payload?.idempotencyKey && String(envelope.payload.idempotencyKey) === correlationId);
|
|
1246
|
+
if (!match) return;
|
|
1247
|
+
if (envelope.channel === "issues") {
|
|
1248
|
+
reply = String(envelope.payload?.error || envelope.payload?.note || "agent reported issue").trim();
|
|
1249
|
+
done = true;
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
reply = envelope.payload?.reply != null ? String(envelope.payload.reply) : "";
|
|
1253
|
+
done = true;
|
|
1254
|
+
},
|
|
1255
|
+
}), { retries: 2, baseDelayMs: 300, label: "realtime send connect" });
|
|
1256
|
+
|
|
1257
|
+
const payload = {
|
|
1258
|
+
action: "run_task",
|
|
1259
|
+
prompt: message,
|
|
1260
|
+
message,
|
|
1261
|
+
source: sender,
|
|
1262
|
+
idempotencyKey: correlationId,
|
|
1263
|
+
};
|
|
1264
|
+
if (projectDir) payload.projectDir = projectDir;
|
|
1265
|
+
|
|
1266
|
+
try {
|
|
1267
|
+
rt.publish({
|
|
1268
|
+
channel: "command",
|
|
1269
|
+
type: "command.run_task",
|
|
1270
|
+
to: agentId,
|
|
1271
|
+
taskId,
|
|
1272
|
+
correlationId,
|
|
1273
|
+
priority: "high",
|
|
1274
|
+
payload,
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
const startedAt = Date.now();
|
|
1278
|
+
while (!done && Date.now() - startedAt < timeoutMs) {
|
|
1279
|
+
await sleep(400);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (!done) {
|
|
1283
|
+
throw new Error(`Timeout waiting for ${agentId} (${timeoutMs}ms)`);
|
|
1284
|
+
}
|
|
1285
|
+
return reply != null ? reply : "(no reply body)";
|
|
1286
|
+
} finally {
|
|
1287
|
+
rt.close();
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// handleRealtimeEnvelope → lib/engines/rt-envelope.mjs
|
|
1292
|
+
|
|
1293
|
+
// Sync dashboard tool permissions → OpenCode agent profiles in .opencode/opencode.jsonc
|
|
1294
|
+
// Called at daemon startup so the two permission systems stay in sync automatically.
|
|
1295
|
+
function syncOpenCodePermissions() {
|
|
1296
|
+
try {
|
|
1297
|
+
const ocCfgPath = path.join(process.cwd(), ".opencode", "opencode.jsonc");
|
|
1298
|
+
if (!fs.existsSync(ocCfgPath)) return;
|
|
1299
|
+
|
|
1300
|
+
// crewswarm tool → OpenCode permission keys
|
|
1301
|
+
const TOOL_TO_OC = {
|
|
1302
|
+
write_file: { write: "allow", edit: "allow" },
|
|
1303
|
+
read_file: { read: "allow", glob: "allow", grep: "allow" },
|
|
1304
|
+
run_cmd: { bash: "allow" },
|
|
1305
|
+
dispatch: { task: "allow" },
|
|
1306
|
+
// git handled separately below — bash allow is too broad
|
|
1307
|
+
};
|
|
1308
|
+
|
|
1309
|
+
// crewswarm agent-id → OpenCode agent profile name
|
|
1310
|
+
const AGENT_TO_OC_PROFILE = {
|
|
1311
|
+
"crew-coder": "coder",
|
|
1312
|
+
"crew-coder-front": "coder-front",
|
|
1313
|
+
"crew-coder-back": "coder-back",
|
|
1314
|
+
"crew-fixer": "fixer",
|
|
1315
|
+
"crew-frontend": "frontend",
|
|
1316
|
+
"crew-qa": "qa",
|
|
1317
|
+
"crew-security": "security",
|
|
1318
|
+
"crew-pm": "pm",
|
|
1319
|
+
"crew-main": "main",
|
|
1320
|
+
"crew-copywriter": "copywriter",
|
|
1321
|
+
"crew-github": "github",
|
|
1322
|
+
"crew-orchestrator": "orchestrator",
|
|
1323
|
+
"orchestrator": "orchestrator",
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
const agents = loadAgentList();
|
|
1327
|
+
if (!agents?.length) return;
|
|
1328
|
+
|
|
1329
|
+
// Resolve profile name: use static map, fall back to stripping crew- prefix
|
|
1330
|
+
const resolveProfile = (agentId) =>
|
|
1331
|
+
AGENT_TO_OC_PROFILE[agentId] || agentId.replace(/^crew-/, "");
|
|
1332
|
+
|
|
1333
|
+
let raw = fs.readFileSync(ocCfgPath, "utf8");
|
|
1334
|
+
const stripped = raw.replace(/\/\/[^\n]*/g, "");
|
|
1335
|
+
let cfg;
|
|
1336
|
+
try {
|
|
1337
|
+
cfg = JSON.parse(stripped);
|
|
1338
|
+
} catch (e) {
|
|
1339
|
+
console.error(`[gateway-bridge] Failed to parse OpenCode config: ${e.message}`);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (!cfg.agent) cfg.agent = {};
|
|
1343
|
+
|
|
1344
|
+
for (const agentCfg of agents) {
|
|
1345
|
+
const agentId = agentCfg.id || agentCfg.agentId;
|
|
1346
|
+
if (!agentId) continue;
|
|
1347
|
+
const profile = resolveProfile(agentId);
|
|
1348
|
+
|
|
1349
|
+
const tools = loadAgentToolPermissions(agentId); // reads crewswarm.json → role defaults
|
|
1350
|
+
const ocPerms = {};
|
|
1351
|
+
|
|
1352
|
+
for (const [tool, perms] of Object.entries(TOOL_TO_OC)) {
|
|
1353
|
+
if (tools.has(tool)) {
|
|
1354
|
+
Object.assign(ocPerms, perms);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// git tool: allow git commands in bash (don't grant full bash)
|
|
1359
|
+
if (tools.has("git") && !tools.has("run_cmd")) {
|
|
1360
|
+
ocPerms.bash = typeof ocPerms.bash === "object" ? ocPerms.bash : {};
|
|
1361
|
+
if (ocPerms.bash !== "allow") {
|
|
1362
|
+
ocPerms.bash["git *"] = "allow";
|
|
1363
|
+
ocPerms.bash["git diff*"] = "allow";
|
|
1364
|
+
ocPerms.bash["git log*"] = "allow";
|
|
1365
|
+
ocPerms.bash["git status*"] = "allow";
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Always deny dangerous stuff for non-admin agents
|
|
1370
|
+
if (profile !== "admin") {
|
|
1371
|
+
ocPerms.question = "deny";
|
|
1372
|
+
ocPerms.plan_enter = "deny";
|
|
1373
|
+
ocPerms.plan_exit = "deny";
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Merge into existing profile, preserving model/prompt/mode
|
|
1377
|
+
if (!cfg.agent[profile]) cfg.agent[profile] = {};
|
|
1378
|
+
cfg.agent[profile].permission = {
|
|
1379
|
+
...cfg.agent[profile].permission,
|
|
1380
|
+
...ocPerms,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// Re-serialize preserving the comment header
|
|
1385
|
+
const headerComment = raw.match(/^(\s*\/\/[^\n]*\n)*/)?.[0] || "";
|
|
1386
|
+
const newJson = JSON.stringify(cfg, null, "\t");
|
|
1387
|
+
// Restore the schema comment at top if it was there
|
|
1388
|
+
fs.writeFileSync(ocCfgPath, newJson + "\n");
|
|
1389
|
+
console.error(`[sync-oc-perms] Synced ${agents.length} agents → ${ocCfgPath}`);
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
console.error("[sync-oc-perms] Failed:", err.message);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
let bridge;
|
|
1397
|
+
let connected = false;
|
|
1398
|
+
try {
|
|
1399
|
+
telemetry("cli_started", { args: args.join(" ") || "(none)", platform: process.platform });
|
|
1400
|
+
if (args.includes("--metrics")) {
|
|
1401
|
+
printMetrics();
|
|
1402
|
+
process.exit(0);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
if (args.includes("--memory-status")) {
|
|
1406
|
+
printMemoryStatus();
|
|
1407
|
+
process.exit(0);
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (args.includes("--rt-status")) {
|
|
1411
|
+
await runRealtimeStatusCheck();
|
|
1412
|
+
process.exit(0);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
if (!args.length) {
|
|
1416
|
+
console.error(`${usage}\nTip: start with --quickstart for a guided setup check.`);
|
|
1417
|
+
process.exit(1);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
if (args.includes("--quickstart")) telemetry("onboarding_started", { source: "--quickstart" });
|
|
1421
|
+
|
|
1422
|
+
if (shouldConnectGateway(args)) {
|
|
1423
|
+
progress("Loading local identity/config...");
|
|
1424
|
+
const creds = loadCredentials();
|
|
1425
|
+
progress(`Connecting to gateway ${GATEWAY_URL}...`);
|
|
1426
|
+
bridge = await withRetry(() => createBridge(creds), { retries: 2, baseDelayMs: 350, label: "gateway connect" });
|
|
1427
|
+
bridge.kind = "gateway";
|
|
1428
|
+
connected = true;
|
|
1429
|
+
telemetry("connect_success", { url: GATEWAY_URL });
|
|
1430
|
+
process.stderr.write("✅ Connected to gateway\n");
|
|
1431
|
+
} else {
|
|
1432
|
+
progress("Starting in OpenCode-only worker mode (no gateway chat bridge)...");
|
|
1433
|
+
bridge = createOpenCodeOnlyBridge();
|
|
1434
|
+
telemetry("connect_skipped", { mode: "opencode_only", agent: CREWSWARM_RT_AGENT });
|
|
1435
|
+
process.stderr.write("✅ OpenCode-only worker mode enabled\n");
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (args.includes("--quickstart")) {
|
|
1439
|
+
progress("Running quickstart checks...");
|
|
1440
|
+
const res = await withRetry(() => bridge.send("channels.status", {}), { retries: 2, label: "channels.status" });
|
|
1441
|
+
console.log("Quickstart complete. Gateway is reachable.");
|
|
1442
|
+
printStatusSummary(res);
|
|
1443
|
+
console.log("\nTargets:");
|
|
1444
|
+
console.log("- Time-to-first-value: under 60s");
|
|
1445
|
+
console.log("- Connect success rate: above 95%");
|
|
1446
|
+
console.log("\nTry this command:");
|
|
1447
|
+
console.log("node gateway-bridge.mjs \"Give me a 3-bullet channel health summary\"");
|
|
1448
|
+
console.log("\nThen check metrics:");
|
|
1449
|
+
console.log("node gateway-bridge.mjs --metrics");
|
|
1450
|
+
} else if (args.includes("--status")) {
|
|
1451
|
+
progress("Fetching channel status...");
|
|
1452
|
+
const res = await withRetry(() => bridge.send("channels.status", {}), { retries: 2, label: "channels.status" });
|
|
1453
|
+
console.log(JSON.stringify(res, null, 2));
|
|
1454
|
+
} else if (args[0] === "--reset-session" && args[1]) {
|
|
1455
|
+
// Clear OpenCode session ID for a specific agent.
|
|
1456
|
+
// Called by the dashboard "Reset context window" button.
|
|
1457
|
+
const targetAgent = args[1];
|
|
1458
|
+
clearAgentSessionId(targetAgent);
|
|
1459
|
+
console.log(`OpenCode session cleared for ${targetAgent}. Next task will start a fresh session.`);
|
|
1460
|
+
} else if (args.includes("--reset-session")) {
|
|
1461
|
+
// No agent specified — list which sessions exist
|
|
1462
|
+
try {
|
|
1463
|
+
const files = fs.readdirSync(OPENCODE_SESSION_DIR);
|
|
1464
|
+
if (files.length === 0) { console.log("No saved sessions."); }
|
|
1465
|
+
else { console.log("Saved sessions:\n" + files.map(f => ` ${f.replace(".session", "")}`).join("\n")); }
|
|
1466
|
+
} catch (e) {
|
|
1467
|
+
console.error(`[gateway-bridge] Failed to list sessions: ${e.message}`);
|
|
1468
|
+
console.log("No session directory found.");
|
|
1469
|
+
}
|
|
1470
|
+
} else if (args.includes("--reset")) {
|
|
1471
|
+
progress("Resetting main session...");
|
|
1472
|
+
await withRetry(() => bridge.send("sessions.reset", { key: "main" }), { retries: 2, label: "sessions.reset" });
|
|
1473
|
+
console.log("Session reset.");
|
|
1474
|
+
} else if (args.includes("--history")) {
|
|
1475
|
+
progress("Fetching recent chat history...");
|
|
1476
|
+
const res = await withRetry(() => bridge.send("chat.history", { sessionKey: "main" }), { retries: 2, label: "chat.history" });
|
|
1477
|
+
const msgs = res?.messages ?? [];
|
|
1478
|
+
for (const m of msgs.slice(-10)) {
|
|
1479
|
+
const role = m.role ?? "?";
|
|
1480
|
+
const text = parseTextContent(m.content);
|
|
1481
|
+
if (text.trim()) console.log(`[${role}] ${text.slice(0, 300)}`);
|
|
1482
|
+
}
|
|
1483
|
+
} else if (args.includes("--rt-daemon")) {
|
|
1484
|
+
await runRealtimeDaemon(bridge);
|
|
1485
|
+
} else if (args.includes("--broadcast")) {
|
|
1486
|
+
const message = args.filter(a => !a.startsWith("--")).join(" ").trim();
|
|
1487
|
+
if (!message) {
|
|
1488
|
+
console.error("Broadcast message is required. Example: --broadcast \"All agents report status\"");
|
|
1489
|
+
process.exit(1);
|
|
1490
|
+
}
|
|
1491
|
+
const result = await runBroadcastTask(message);
|
|
1492
|
+
console.log(result);
|
|
1493
|
+
} else if (args[0] === "--send" && args[1]) {
|
|
1494
|
+
const agentId = args[1];
|
|
1495
|
+
const message = args.slice(2).join(" ").trim();
|
|
1496
|
+
if (!message) {
|
|
1497
|
+
console.error("Usage: node gateway-bridge.mjs --send <agentId> \"task message\"");
|
|
1498
|
+
console.error("Example: node gateway-bridge.mjs --send crew-coder \"Create server.js with Express\"");
|
|
1499
|
+
process.exit(1);
|
|
1500
|
+
}
|
|
1501
|
+
if (!CREWSWARM_RT_SWARM_AGENTS.includes(agentId)) {
|
|
1502
|
+
console.error(`Unknown agent: ${agentId}. Known: ${CREWSWARM_RT_SWARM_AGENTS.join(", ")}`);
|
|
1503
|
+
process.exit(1);
|
|
1504
|
+
}
|
|
1505
|
+
process.stderr.write(`📤 Sending to ${agentId} only (no broadcast)...\n`);
|
|
1506
|
+
const projectDir = getOpencodeProjectDir() || null;
|
|
1507
|
+
const reply = await runSendToAgent(agentId, message, { projectDir });
|
|
1508
|
+
process.stderr.write("✅ Reply received\n");
|
|
1509
|
+
console.log(reply);
|
|
1510
|
+
telemetry("send_to_agent", { agentId, replyChars: reply.length });
|
|
1511
|
+
} else {
|
|
1512
|
+
const message = args.filter(a => !a.startsWith("--")).join(" ");
|
|
1513
|
+
if (!message) { console.error(usage); process.exit(1); }
|
|
1514
|
+
if (message.trim().startsWith("/broadcast ")) {
|
|
1515
|
+
const payload = message.trim().slice("/broadcast ".length).trim();
|
|
1516
|
+
if (!payload) {
|
|
1517
|
+
console.error("Usage: /broadcast <message>");
|
|
1518
|
+
process.exit(1);
|
|
1519
|
+
}
|
|
1520
|
+
const result = await runBroadcastTask(payload);
|
|
1521
|
+
console.log(result);
|
|
1522
|
+
telemetry("chat_broadcast", { chars: payload.length });
|
|
1523
|
+
process.exit(0);
|
|
1524
|
+
}
|
|
1525
|
+
progress("Loading persistent shared memory...");
|
|
1526
|
+
const { finalPrompt, sharedMemory } = buildTaskPrompt(message, "User request");
|
|
1527
|
+
if (sharedMemory.loadFailed || finalPrompt === "MEMORY_LOAD_FAILED") {
|
|
1528
|
+
telemetry("chat_memory_load_failed", { sessionKey: "main", sharedMemoryMissing: sharedMemory.missing });
|
|
1529
|
+
console.log("MEMORY_LOAD_FAILED");
|
|
1530
|
+
process.exit(2);
|
|
1531
|
+
}
|
|
1532
|
+
assertTaskPromptProtocol(finalPrompt, "direct-chat");
|
|
1533
|
+
|
|
1534
|
+
// Check if we should route to OpenCode instead of legacy gateway
|
|
1535
|
+
if (shouldUseOpenCode({}, finalPrompt, null)) {
|
|
1536
|
+
console.error("[OpenCode] Routing to OpenCode CLI...");
|
|
1537
|
+
// Pass raw message to OpenCode (no memory wrapper)
|
|
1538
|
+
const reply = await runOpenCodeTask(message, { model: CREWSWARM_OPENCODE_MODEL });
|
|
1539
|
+
console.log(reply);
|
|
1540
|
+
telemetry("chat_done_opencode", { sessionKey: CREWSWARM_RT_AGENT, replyChars: reply.length });
|
|
1541
|
+
process.exit(0);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
telemetry("chat_started", {
|
|
1545
|
+
sessionKey: "main",
|
|
1546
|
+
messageChars: message.length,
|
|
1547
|
+
sharedMemoryIncluded: Boolean(sharedMemory.text),
|
|
1548
|
+
sharedMemoryBytes: sharedMemory.bytes,
|
|
1549
|
+
sharedMemoryMissing: sharedMemory.missing,
|
|
1550
|
+
});
|
|
1551
|
+
process.stderr.write(`📤 ${CREWSWARM_RT_AGENT || "main"} ${message.slice(0, 80)}\n`);
|
|
1552
|
+
process.stderr.write("⏳ Waiting for assistant reply...\n");
|
|
1553
|
+
const targetAgent = RT_TO_GATEWAY_AGENT_MAP[CREWSWARM_RT_AGENT] || "main";
|
|
1554
|
+
const reply = await bridge.chat(finalPrompt, targetAgent);
|
|
1555
|
+
|
|
1556
|
+
telemetry("chat_done", { sessionKey: targetAgent, replyChars: reply.length });
|
|
1557
|
+
process.stderr.write("✅ Reply received\n");
|
|
1558
|
+
console.log(reply);
|
|
1559
|
+
|
|
1560
|
+
// One-shot mode: exit after task completion (fresh context next run)
|
|
1561
|
+
if (ONE_SHOT) {
|
|
1562
|
+
process.stderr.write("[gateway-bridge] ONE-SHOT: Exiting after task completion\n");
|
|
1563
|
+
process.exit(0);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
telemetry("cli_finished", { ok: true });
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
if (!connected) telemetry("connect_error", { message: err?.message ?? String(err) });
|
|
1569
|
+
telemetry("error_shown", { message: err?.message ?? String(err) });
|
|
1570
|
+
telemetry("cli_finished", { ok: false, message: err?.message ?? String(err) });
|
|
1571
|
+
console.error(formatError(err));
|
|
1572
|
+
process.exit(1);
|
|
1573
|
+
} finally {
|
|
1574
|
+
bridge?.close();
|
|
1575
|
+
}
|