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,1367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine runners — extracted from gateway-bridge.mjs
|
|
3
|
+
* shouldUse* routing and run*Task implementations for Cursor CLI, Claude Code,
|
|
4
|
+
* Codex, Gemini CLI, Docker Sandbox, and generic drop-in JSON engines.
|
|
5
|
+
*
|
|
6
|
+
* Inject: initRunners({ getAgentOpenCodeConfig, loadAgentList, getOpencodeProjectDir,
|
|
7
|
+
* buildMiniTaskForOpenCode, runOpenCodeTask, loadGenericEngines })
|
|
8
|
+
*
|
|
9
|
+
* Drop-in engines: place a JSON file in engines/ or ~/.crewswarm/engines/ with
|
|
10
|
+
* { id, bin, args: { run: [...] }, outputMode: "stream-json"|"streaming", agentConfigKey, envToggle }
|
|
11
|
+
* and it is picked up automatically — no code changes needed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn, execSync } from "node:child_process";
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import { recordTokenUsage, addAgentSpend } from "../runtime/spending.mjs";
|
|
19
|
+
import { loadSystemConfig, loadSwarmConfig } from "../runtime/config.mjs";
|
|
20
|
+
import { initEngineRegistry, selectEngine as registrySelectEngine, getEngineById } from "./engine-registry.mjs";
|
|
21
|
+
import { runCrewCLITask } from "./crew-cli.mjs";
|
|
22
|
+
import { normalizeProjectDir } from "../runtime/project-dir.mjs";
|
|
23
|
+
import { resolveCursorLaunchSpec } from "./cursor-launcher.mjs";
|
|
24
|
+
|
|
25
|
+
function which(bin) {
|
|
26
|
+
try { execSync(`which ${bin}`, { stdio: "ignore" }); return true; } catch { return false; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Module-level deps (injected via initRunners) ───────────────────────────
|
|
30
|
+
let _getAgentOpenCodeConfig = () => ({ enabled: false, useCursorCli: false, cursorCliModel: null, claudeCodeModel: null });
|
|
31
|
+
let _loadAgentList = () => [];
|
|
32
|
+
let _getOpencodeProjectDir = () => null;
|
|
33
|
+
let _runOpenCodeTask = async () => "";
|
|
34
|
+
let _loadGenericEngines = () => [];
|
|
35
|
+
|
|
36
|
+
function findAgentConfig(agentId) {
|
|
37
|
+
const canonical = String(agentId || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
38
|
+
try {
|
|
39
|
+
const agents = _loadAgentList();
|
|
40
|
+
return (
|
|
41
|
+
agents.find((a) => a.id === canonical || a.id === `crew-${canonical}`) || null
|
|
42
|
+
);
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function agentUsesEngine(agentId, engineIds = [], flagKey = null) {
|
|
49
|
+
const cfg = findAgentConfig(agentId);
|
|
50
|
+
if (!cfg) return false;
|
|
51
|
+
if (flagKey && cfg?.[flagKey] === true) return true;
|
|
52
|
+
const cfgEngine = String(cfg.engine || "").toLowerCase();
|
|
53
|
+
return !!(cfgEngine && engineIds.includes(cfgEngine));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function initRunners({ getAgentOpenCodeConfig, loadAgentList, getOpencodeProjectDir, buildMiniTaskForOpenCode, runOpenCodeTask, loadGenericEngines } = {}) {
|
|
57
|
+
if (getAgentOpenCodeConfig) _getAgentOpenCodeConfig = getAgentOpenCodeConfig;
|
|
58
|
+
if (loadAgentList) _loadAgentList = loadAgentList;
|
|
59
|
+
if (getOpencodeProjectDir) _getOpencodeProjectDir = getOpencodeProjectDir;
|
|
60
|
+
if (runOpenCodeTask) _runOpenCodeTask = runOpenCodeTask;
|
|
61
|
+
if (loadGenericEngines) _loadGenericEngines = loadGenericEngines;
|
|
62
|
+
|
|
63
|
+
// Initialize engine registry with runner functions
|
|
64
|
+
initEngineRegistry({
|
|
65
|
+
loadAgentList: _loadAgentList,
|
|
66
|
+
engineRunners: {
|
|
67
|
+
'cursor': runCursorCliTask,
|
|
68
|
+
'claude-code': runClaudeCodeTask,
|
|
69
|
+
'codex': runCodexTask,
|
|
70
|
+
'docker-sandbox': runDockerSandboxTask,
|
|
71
|
+
'crew-cli': runCrewCLITask,
|
|
72
|
+
'gemini-cli': runGeminiCliTask,
|
|
73
|
+
'opencode': runOpenCodeTask
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Export selectEngine for use in rt-envelope
|
|
79
|
+
export function selectEngine(payload, incomingType) {
|
|
80
|
+
return registrySelectEngine(payload, incomingType);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// RT client for agent_working/agent_idle publishes (same pattern as executor.mjs setRtClient)
|
|
84
|
+
export let _rtClientForApprovals = null;
|
|
85
|
+
export function setRtClientForRunners(rt) {
|
|
86
|
+
_rtClientForApprovals = rt;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Consts from process.env ────────────────────────────────────────────────
|
|
90
|
+
const CREWSWARM_RT_AGENT = process.env.CREWSWARM_RT_AGENT || "crew-main";
|
|
91
|
+
const CREWSWARM_OPENCODE_ENABLED = process.env.CREWSWARM_OPENCODE_ENABLED === "1"; // Opt-IN (was opt-OUT)
|
|
92
|
+
const CREWSWARM_CURSOR_WAVES = process.env.CREWSWARM_CURSOR_WAVES === "1";
|
|
93
|
+
// Evaluated at call time so env overrides and tests work correctly
|
|
94
|
+
function _isClaudeCodeEnabled() {
|
|
95
|
+
if (process.env.CREWSWARM_CLAUDE_CODE) return /^1|true|yes$/i.test(String(process.env.CREWSWARM_CLAUDE_CODE));
|
|
96
|
+
const cfg = loadSystemConfig();
|
|
97
|
+
if (typeof cfg.claudeCode === "boolean") return cfg.claudeCode;
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
const CREWSWARM_OPENCODE_FORCE = process.env.CREWSWARM_OPENCODE_FORCE === "1";
|
|
101
|
+
// Legacy fixed timeout — kept for OpenCode. Cursor/Claude use activity-based watchdog instead.
|
|
102
|
+
const CREWSWARM_OPENCODE_TIMEOUT_MS = Number(process.env.CREWSWARM_OPENCODE_TIMEOUT_MS || "300000");
|
|
103
|
+
// Idle watchdog: kill engine if no stdout/stderr activity for this long (default 5 min).
|
|
104
|
+
const CREWSWARM_ENGINE_IDLE_TIMEOUT_MS = Number(process.env.CREWSWARM_ENGINE_IDLE_TIMEOUT_MS || "300000");
|
|
105
|
+
// Absolute ceiling for any single engine task — safety net (default 45 min).
|
|
106
|
+
const CREWSWARM_ENGINE_MAX_TOTAL_MS = Number(process.env.CREWSWARM_ENGINE_MAX_TOTAL_MS || "2700000");
|
|
107
|
+
const OPENCODE_SESSION_DIR = path.join(os.homedir(), ".crewswarm", "sessions");
|
|
108
|
+
|
|
109
|
+
const CODEX_CLI_BIN = process.env.CODEX_CLI_BIN || "codex";
|
|
110
|
+
const CODEX_CLI_TIMEOUT_MS = Number(process.env.CODEX_CLI_TIMEOUT_MS || "300000");
|
|
111
|
+
// Evaluated at call time so env overrides and tests work correctly
|
|
112
|
+
function _isCodexEnabled() {
|
|
113
|
+
if (process.env.CREWSWARM_CODEX === "1") return true;
|
|
114
|
+
const swarm = loadSwarmConfig();
|
|
115
|
+
return swarm?.codex === true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const GEMINI_CLI_BIN = process.env.GEMINI_CLI_BIN || "gemini";
|
|
119
|
+
const GEMINI_CLI_TIMEOUT_MS = Number(process.env.GEMINI_CLI_TIMEOUT_MS || "300000");
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* `gemini -m` expects a Google model id (e.g. gemini-2.5-flash), not crewswarm roster strings
|
|
123
|
+
* like openai/gpt-5.2 — those produce ModelNotFoundError (404) from the Gemini API.
|
|
124
|
+
*
|
|
125
|
+
* @param {Record<string, unknown>} payload
|
|
126
|
+
* @returns {string|null}
|
|
127
|
+
*/
|
|
128
|
+
function normalizeGeminiCliModelId(raw) {
|
|
129
|
+
let s = String(raw || "").trim();
|
|
130
|
+
if (!s) return null;
|
|
131
|
+
s = s.replace(/^models\//i, "");
|
|
132
|
+
return s || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveGeminiCliModelFlag(payload) {
|
|
136
|
+
const explicit = normalizeGeminiCliModelId(payload?.geminiCliModel);
|
|
137
|
+
if (explicit) return explicit;
|
|
138
|
+
const envM = normalizeGeminiCliModelId(process.env.CREWSWARM_GEMINI_CLI_MODEL);
|
|
139
|
+
if (envM) return envM;
|
|
140
|
+
const agentModel = String(payload?.model || "").trim();
|
|
141
|
+
if (!agentModel) return null;
|
|
142
|
+
if (agentModel.includes("/")) {
|
|
143
|
+
if (/^google\/models\//i.test(agentModel)) {
|
|
144
|
+
return normalizeGeminiCliModelId(
|
|
145
|
+
agentModel.replace(/^google\/models\//i, "").replace(/^google\//i, ""),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (/^google\//i.test(agentModel)) {
|
|
149
|
+
return normalizeGeminiCliModelId(agentModel.replace(/^google\//i, ""));
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
if (/^gemini[\w.-]+$/i.test(agentModel))
|
|
154
|
+
return normalizeGeminiCliModelId(agentModel);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function resolveCodexCliModel(payload, agentCfg = null) {
|
|
159
|
+
const explicit = String(payload?.codexModel || "").trim();
|
|
160
|
+
if (explicit) return explicit;
|
|
161
|
+
const agentModel = String(agentCfg?.codexModel || "").trim();
|
|
162
|
+
if (agentModel) return agentModel;
|
|
163
|
+
const envModel = String(process.env.CREWSWARM_CODEX_MODEL || "").trim();
|
|
164
|
+
if (envModel) return envModel;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const CURSOR_CLI_BIN = process.env.CURSOR_CLI_BIN ||
|
|
169
|
+
(fs.existsSync(path.join(os.homedir(), ".local", "bin", "agent"))
|
|
170
|
+
? path.join(os.homedir(), ".local", "bin", "agent")
|
|
171
|
+
: "agent");
|
|
172
|
+
const CURSOR_SESSION_DIR = OPENCODE_SESSION_DIR;
|
|
173
|
+
|
|
174
|
+
const CLAUDE_CODE_BIN = process.env.CLAUDE_CODE_BIN || "claude";
|
|
175
|
+
const CLAUDE_SESSION_DIR = OPENCODE_SESSION_DIR;
|
|
176
|
+
|
|
177
|
+
// ── shouldUse* routing ────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
export function shouldUseCursorCli(payload, incomingType) {
|
|
180
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
181
|
+
const runtime = String(payload?.runtime || payload?.executor || "").toLowerCase();
|
|
182
|
+
if (runtime === "cursor" || runtime === "cursor-cli") return true;
|
|
183
|
+
if (payload?.useCursorCli === true) return true;
|
|
184
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
185
|
+
if (agentUsesEngine(agentId, ["cursor", "cursor-cli"], "useCursorCli")) return true;
|
|
186
|
+
if (agentId === "crew-orchestrator" || agentId === "orchestrator") {
|
|
187
|
+
const ocCfg = _getAgentOpenCodeConfig(agentId);
|
|
188
|
+
if (ocCfg.useCursorCli === true) return true;
|
|
189
|
+
return CREWSWARM_CURSOR_WAVES;
|
|
190
|
+
}
|
|
191
|
+
return _getAgentOpenCodeConfig(agentId).useCursorCli === true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function shouldUseClaudeCode(payload, incomingType) {
|
|
195
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
196
|
+
if (shouldUseCursorCli(payload, incomingType)) return false;
|
|
197
|
+
const runtime = String(payload?.runtime || payload?.executor || "").toLowerCase();
|
|
198
|
+
if (runtime === "claude" || runtime === "claude-code") return true;
|
|
199
|
+
if (payload?.useClaudeCode === true) return true;
|
|
200
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
201
|
+
if (agentUsesEngine(agentId, ["claude", "claude-code"], "useClaudeCode")) return true;
|
|
202
|
+
// Global fallback — but skip if the agent explicitly uses another engine
|
|
203
|
+
if (_isClaudeCodeEnabled()) {
|
|
204
|
+
const ocCfg = _getAgentOpenCodeConfig(agentId);
|
|
205
|
+
// Skip if agent explicitly uses crew-cli, codex, gemini-cli, docker-sandbox, or opencode
|
|
206
|
+
if (ocCfg.useCrewCLI === true) return false;
|
|
207
|
+
if (ocCfg.useCodex === true) return false;
|
|
208
|
+
if (ocCfg.useGeminiCli === true) return false;
|
|
209
|
+
if (ocCfg.useDockerSandbox === true) return false;
|
|
210
|
+
if (ocCfg.enabled) return false; // useOpenCode
|
|
211
|
+
const cfg = findAgentConfig(agentId);
|
|
212
|
+
const eng = String(cfg?.engine || "").toLowerCase();
|
|
213
|
+
if (
|
|
214
|
+
eng &&
|
|
215
|
+
["codex", "codex-cli", "cursor", "cursor-cli", "opencode", "gpt5", "gpt-5", "gemini", "gemini-cli", "crew-cli", "crewcli", "docker-sandbox"].includes(eng)
|
|
216
|
+
) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
// Skip agents with no engine set but a specific model (they use LLM-direct)
|
|
220
|
+
if (!eng && cfg?.model) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function shouldUseOpenCode(payload, prompt, incomingType) {
|
|
229
|
+
if (!CREWSWARM_OPENCODE_ENABLED) return false;
|
|
230
|
+
if (CREWSWARM_OPENCODE_FORCE) return true;
|
|
231
|
+
if (shouldUseCursorCli(payload, incomingType)) return false;
|
|
232
|
+
if (shouldUseClaudeCode(payload, incomingType)) return false;
|
|
233
|
+
if (shouldUseCodex(payload, incomingType)) return false;
|
|
234
|
+
|
|
235
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
236
|
+
|
|
237
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
238
|
+
if (runtime === "opencode" || runtime === "gpt5" || runtime === "gpt-5") return true;
|
|
239
|
+
if (payload?.useOpenCode === true) return true;
|
|
240
|
+
|
|
241
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
242
|
+
if (agentUsesEngine(agentId, ["opencode", "gpt5", "gpt-5"], "useOpenCode")) return true;
|
|
243
|
+
const ocCfg = _getAgentOpenCodeConfig(agentId);
|
|
244
|
+
return ocCfg.enabled;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function shouldUseCodex(payload, incomingType) {
|
|
248
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
249
|
+
if (shouldUseCursorCli(payload, incomingType)) return false;
|
|
250
|
+
if (shouldUseClaudeCode(payload, incomingType)) return false;
|
|
251
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
252
|
+
if (runtime === "codex" || runtime === "codex-cli") return true;
|
|
253
|
+
if (payload?.useCodex === true) return true;
|
|
254
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
255
|
+
if (agentUsesEngine(agentId, ["codex", "codex-cli"], "useCodex")) return true;
|
|
256
|
+
if (_isCodexEnabled()) {
|
|
257
|
+
const cfg = findAgentConfig(agentId);
|
|
258
|
+
const eng = String(cfg?.engine || "").toLowerCase();
|
|
259
|
+
// Skip agents that explicitly use a different engine
|
|
260
|
+
if (eng && !["codex", "codex-cli"].includes(eng)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
// Skip agents with no engine set but a specific model (they use LLM-direct)
|
|
264
|
+
if (!eng && cfg?.model) {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function shouldUseGeminiCli(payload, incomingType) {
|
|
273
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
274
|
+
// Check higher-priority engines first
|
|
275
|
+
if (shouldUseCursorCli(payload, incomingType)) return false;
|
|
276
|
+
if (shouldUseClaudeCode(payload, incomingType)) return false;
|
|
277
|
+
if (shouldUseCodex(payload, incomingType)) return false;
|
|
278
|
+
if (shouldUseDockerSandbox(payload, incomingType)) return false;
|
|
279
|
+
if (shouldUseCrewCLI(payload, incomingType)) return false;
|
|
280
|
+
|
|
281
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
282
|
+
if (runtime === "gemini" || runtime === "gemini-cli") return true;
|
|
283
|
+
if (payload?.useGeminiCli === true) return true;
|
|
284
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
285
|
+
if (agentUsesEngine(agentId, ["gemini", "gemini-cli"], "useGeminiCli")) return true;
|
|
286
|
+
return process.env.CREWSWARM_GEMINI_CLI_ENABLED === "1";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function shouldUseCrewCLI(payload, incomingType) {
|
|
290
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
291
|
+
// Check higher-priority engines first
|
|
292
|
+
if (shouldUseCursorCli(payload, incomingType)) return false;
|
|
293
|
+
if (shouldUseClaudeCode(payload, incomingType)) return false;
|
|
294
|
+
if (shouldUseCodex(payload, incomingType)) return false;
|
|
295
|
+
if (shouldUseDockerSandbox(payload, incomingType)) return false;
|
|
296
|
+
|
|
297
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
298
|
+
if (runtime === "crew-cli" || runtime === "crewcli") return true;
|
|
299
|
+
if (payload?.useCrewCLI === true) return true;
|
|
300
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
301
|
+
try {
|
|
302
|
+
const agents = _loadAgentList();
|
|
303
|
+
const cfg = agents.find(a => a.id === agentId || a.id === `crew-${agentId}`);
|
|
304
|
+
if (cfg?.useCrewCLI === true) return true;
|
|
305
|
+
} catch {}
|
|
306
|
+
return process.env.CREWSWARM_CREW_CLI_ENABLED === "1";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function runGeminiCliTask(prompt, payload = {}) {
|
|
310
|
+
return new Promise((resolve, reject) => {
|
|
311
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
312
|
+
const taskBody = String(prompt ?? "").trim();
|
|
313
|
+
if (!taskBody) {
|
|
314
|
+
reject(
|
|
315
|
+
new Error(
|
|
316
|
+
"Gemini CLI: task text is empty — the gateway sent no instructions.",
|
|
317
|
+
),
|
|
318
|
+
);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
323
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
324
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
325
|
+
const expandedDir = normalizeProjectDir(projectDir) || projectDir;
|
|
326
|
+
if (expandedDir && fs.existsSync(expandedDir)) {
|
|
327
|
+
projectDir = expandedDir;
|
|
328
|
+
} else if (!fs.existsSync(projectDir)) {
|
|
329
|
+
console.error(
|
|
330
|
+
`[GeminiCli:${agentId}] projectDir not found (${projectDir}), using process.cwd()`,
|
|
331
|
+
);
|
|
332
|
+
projectDir = process.cwd();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
336
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
337
|
+
|
|
338
|
+
const model = resolveGeminiCliModelFlag(payload);
|
|
339
|
+
const args = ["-p", titledPrompt, "--output-format", "stream-json", "--yolo"];
|
|
340
|
+
if (model) args.push("-m", model);
|
|
341
|
+
else if (payload?.model && String(payload.model).includes("/")) {
|
|
342
|
+
console.error(
|
|
343
|
+
`[GeminiCli:${agentId}] Omitting -m: roster model "${payload.model}" is not a Gemini CLI id. Set per-agent geminiCliModel (e.g. gemini-2.5-flash) or CREWSWARM_GEMINI_CLI_MODEL — using CLI default.`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.error(
|
|
348
|
+
`[GeminiCli:${agentId}] spawn: ${GEMINI_CLI_BIN} -m ${model || "(default)"} … cwd=${projectDir} payload.geminiCliModel=${payload?.geminiCliModel ?? "(none)"}`,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
352
|
+
payload: { agent: agentId, model: model || "gemini/auto", ts: Date.now() } });
|
|
353
|
+
|
|
354
|
+
const child = spawn(GEMINI_CLI_BIN, args, {
|
|
355
|
+
cwd: projectDir,
|
|
356
|
+
env: process.env,
|
|
357
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
let lineBuffer = "";
|
|
361
|
+
let accumulatedText = "";
|
|
362
|
+
let orphanStream = "";
|
|
363
|
+
const appendOrphan = (line) => {
|
|
364
|
+
const s = String(line).trim();
|
|
365
|
+
if (!s) return;
|
|
366
|
+
orphanStream = (orphanStream + (orphanStream ? "\n" : "") + s).slice(-8000);
|
|
367
|
+
};
|
|
368
|
+
/** @type {string[]} */
|
|
369
|
+
const geminiErrorEvents = [];
|
|
370
|
+
let stderrBuf = "";
|
|
371
|
+
let resultReceived = false;
|
|
372
|
+
|
|
373
|
+
const hardTimer = setTimeout(() => {
|
|
374
|
+
child.kill("SIGKILL");
|
|
375
|
+
if (!resultReceived) reject(new Error(`GeminiCli timeout after ${GEMINI_CLI_TIMEOUT_MS}ms`));
|
|
376
|
+
}, GEMINI_CLI_TIMEOUT_MS);
|
|
377
|
+
|
|
378
|
+
function failEmpty(label, code, signal) {
|
|
379
|
+
const errTail = geminiErrorEvents.length
|
|
380
|
+
? `\n--- Gemini JSON error events ---\n${geminiErrorEvents.join("\n")}`
|
|
381
|
+
: "";
|
|
382
|
+
const stderrTail = stderrBuf.trim()
|
|
383
|
+
? `\n--- stderr ---\n${stderrBuf.trim().slice(-6000)}`
|
|
384
|
+
: "";
|
|
385
|
+
const orphanTail = orphanStream.trim()
|
|
386
|
+
? `\n--- non-JSON stdout ---\n${orphanStream.trim().slice(-4000)}`
|
|
387
|
+
: "";
|
|
388
|
+
const meta = `\n(cwd=${projectDir}, bin=${GEMINI_CLI_BIN}, code=${code}${signal ? `, signal=${signal}` : ""})`;
|
|
389
|
+
console.error(`[GeminiCli:${agentId}] ${label}${meta}${stderrTail.slice(0, 500)}`);
|
|
390
|
+
reject(new Error(`${label}${errTail}${stderrTail}${orphanTail}${meta}`));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function handleLine(line) {
|
|
394
|
+
line = line.trim();
|
|
395
|
+
if (!line) return;
|
|
396
|
+
try {
|
|
397
|
+
const ev = JSON.parse(line);
|
|
398
|
+
// Collect streaming text chunks (delta:true) or fall back to any assistant message
|
|
399
|
+
if (ev.type === "message" && ev.role === "assistant") {
|
|
400
|
+
if (ev.delta === true && ev.content) {
|
|
401
|
+
accumulatedText += ev.content;
|
|
402
|
+
} else if (ev.delta !== false && ev.content && !accumulatedText) {
|
|
403
|
+
// Some versions may not have delta flag — only use if we have nothing yet
|
|
404
|
+
accumulatedText += ev.content;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (ev.type === "result" && !resultReceived) {
|
|
408
|
+
resultReceived = true;
|
|
409
|
+
clearTimeout(hardTimer);
|
|
410
|
+
child.kill("SIGTERM");
|
|
411
|
+
const fromResult = (ev.response || "").trim();
|
|
412
|
+
const out = accumulatedText.trim() || fromResult;
|
|
413
|
+
if (!out) {
|
|
414
|
+
failEmpty(
|
|
415
|
+
"Gemini CLI returned an empty result (no assistant text and no response field).",
|
|
416
|
+
0,
|
|
417
|
+
null,
|
|
418
|
+
);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
console.log(`[GeminiCli:${agentId}] Done — ${out.length} chars`);
|
|
422
|
+
resolve(out);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (ev.type === "error") {
|
|
426
|
+
const msg = ev.message || JSON.stringify(ev);
|
|
427
|
+
geminiErrorEvents.push(msg);
|
|
428
|
+
console.error(`[GeminiCli:${agentId}] Error event (${ev.severity}):`, msg);
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
appendOrphan(line);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
child.stdout.on("data", (chunk) => {
|
|
436
|
+
lineBuffer += chunk.toString();
|
|
437
|
+
const lines = lineBuffer.split("\n");
|
|
438
|
+
lineBuffer = lines.pop();
|
|
439
|
+
for (const l of lines) handleLine(l);
|
|
440
|
+
});
|
|
441
|
+
child.stderr.on("data", (chunk) => {
|
|
442
|
+
const txt = chunk.toString();
|
|
443
|
+
stderrBuf = (stderrBuf + txt).slice(-12000);
|
|
444
|
+
console.error(`[GeminiCli:${agentId}] stderr: ${txt.slice(0, 400)}`);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
child.on("close", (code, signal) => {
|
|
448
|
+
clearTimeout(hardTimer);
|
|
449
|
+
if (lineBuffer.trim()) handleLine(lineBuffer);
|
|
450
|
+
if (!resultReceived) {
|
|
451
|
+
resultReceived = true;
|
|
452
|
+
if (accumulatedText.trim()) resolve(accumulatedText.trim());
|
|
453
|
+
else
|
|
454
|
+
failEmpty(
|
|
455
|
+
`GeminiCli exited with code ${code} and no usable output`,
|
|
456
|
+
code,
|
|
457
|
+
signal,
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
child.on("error", (e) => {
|
|
463
|
+
clearTimeout(hardTimer);
|
|
464
|
+
if (!resultReceived) {
|
|
465
|
+
resultReceived = true;
|
|
466
|
+
reject(
|
|
467
|
+
new Error(
|
|
468
|
+
`${e?.message || e} (cwd=${projectDir}, bin=${GEMINI_CLI_BIN})`,
|
|
469
|
+
),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ── Generic drop-in engine runner ────────────────────────────────────────────
|
|
477
|
+
// Handles any engine JSON that has: bin, args.run, outputMode, agentConfigKey.
|
|
478
|
+
// To add a new engine: drop a JSON in engines/ or ~/.crewswarm/engines/ — no code changes needed.
|
|
479
|
+
|
|
480
|
+
export function shouldUseGenericEngine(engineDef, payload, incomingType) {
|
|
481
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
482
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
483
|
+
if (runtime === engineDef.id) return true;
|
|
484
|
+
if (Array.isArray(engineDef.runtimeAlias) && engineDef.runtimeAlias.includes(runtime)) return true;
|
|
485
|
+
const configKey = engineDef.agentConfigKey;
|
|
486
|
+
if (configKey && payload?.[configKey] === true) return true;
|
|
487
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
488
|
+
try {
|
|
489
|
+
const agents = _loadAgentList();
|
|
490
|
+
const cfg = agents.find(a => a.id === agentId || a.id === `crew-${agentId}`);
|
|
491
|
+
const cfgEngine = String(cfg?.engine || "").toLowerCase();
|
|
492
|
+
if (configKey && cfg?.[configKey] === true) return true;
|
|
493
|
+
if (
|
|
494
|
+
cfgEngine &&
|
|
495
|
+
(cfgEngine === engineDef.id ||
|
|
496
|
+
(Array.isArray(engineDef.runtimeAlias) && engineDef.runtimeAlias.includes(cfgEngine)))
|
|
497
|
+
) {
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
} catch {}
|
|
501
|
+
if (engineDef.envToggle) return process.env[engineDef.envToggle] === "1";
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export async function runGenericEngineTask(engineDef, prompt, payload = {}) {
|
|
506
|
+
return new Promise((resolve, reject) => {
|
|
507
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
508
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
509
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
510
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
511
|
+
|
|
512
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
513
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
514
|
+
|
|
515
|
+
// Resolve model: payload modelKey → payload.model → env var → default
|
|
516
|
+
const modelKey = engineDef.modelKey ||
|
|
517
|
+
(engineDef.agentConfigKey ? engineDef.agentConfigKey.replace(/^use/, '').replace(/^./, c => c.toLowerCase()) + 'Model' : null);
|
|
518
|
+
const rawModel = (modelKey && payload?.[modelKey])
|
|
519
|
+
|| payload?.model
|
|
520
|
+
|| (engineDef.modelEnvVar ? process.env[engineDef.modelEnvVar] : null)
|
|
521
|
+
|| engineDef.defaultModel
|
|
522
|
+
|| null;
|
|
523
|
+
|
|
524
|
+
// Build args from template — use run_with_model if model is set and template exists
|
|
525
|
+
const argsTemplate = (rawModel && engineDef.args?.run_with_model)
|
|
526
|
+
? engineDef.args.run_with_model
|
|
527
|
+
: engineDef.args?.run || ["-p", "{prompt}"];
|
|
528
|
+
const args = argsTemplate.map(a =>
|
|
529
|
+
a.replace(/{prompt}/g, titledPrompt)
|
|
530
|
+
.replace(/{model}/g, rawModel || "")
|
|
531
|
+
.replace(/{agent}/g, agentId)
|
|
532
|
+
).filter(a => a !== "");
|
|
533
|
+
|
|
534
|
+
const bin = engineDef.bin;
|
|
535
|
+
const outputMode = engineDef.outputMode || "streaming";
|
|
536
|
+
const timeoutMs = Number(
|
|
537
|
+
(engineDef.timeoutEnv ? process.env[engineDef.timeoutEnv] : null) || engineDef.timeoutMs || 300000
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
console.error(`[${engineDef.id}:${agentId}] Running: ${bin} ${args.slice(0, 3).join(" ")}... (model=${rawModel || "default"}, cwd=${projectDir})`);
|
|
541
|
+
|
|
542
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
543
|
+
payload: { agent: agentId, model: rawModel || engineDef.id, ts: Date.now() } });
|
|
544
|
+
|
|
545
|
+
const child = spawn(bin, args, { cwd: projectDir, env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
546
|
+
|
|
547
|
+
let lineBuffer = "";
|
|
548
|
+
let accumulatedText = "";
|
|
549
|
+
let resultReceived = false;
|
|
550
|
+
|
|
551
|
+
const hardTimer = setTimeout(() => {
|
|
552
|
+
child.kill("SIGKILL");
|
|
553
|
+
if (!resultReceived) reject(new Error(`${engineDef.label || engineDef.id} timed out after ${timeoutMs}ms`));
|
|
554
|
+
}, timeoutMs);
|
|
555
|
+
|
|
556
|
+
function handleStreamJsonLine(line) {
|
|
557
|
+
line = line.trim();
|
|
558
|
+
if (!line) return;
|
|
559
|
+
try {
|
|
560
|
+
const ev = JSON.parse(line);
|
|
561
|
+
if (ev.type === "message" && ev.role === "assistant" && ev.content) accumulatedText += ev.content;
|
|
562
|
+
if (ev.type === "result" && !resultReceived) {
|
|
563
|
+
resultReceived = true;
|
|
564
|
+
clearTimeout(hardTimer);
|
|
565
|
+
child.kill("SIGTERM");
|
|
566
|
+
const out = accumulatedText.trim() || `(${engineDef.id} completed with no text output)`;
|
|
567
|
+
console.log(`[${engineDef.id}:${agentId}] Done — ${out.length} chars`);
|
|
568
|
+
resolve(out);
|
|
569
|
+
}
|
|
570
|
+
if (ev.type === "error") {
|
|
571
|
+
console.error(`[${engineDef.id}:${agentId}] Error:`, ev.message || JSON.stringify(ev));
|
|
572
|
+
}
|
|
573
|
+
} catch {}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
child.stdout.on("data", (chunk) => {
|
|
577
|
+
if (outputMode === "stream-json") {
|
|
578
|
+
lineBuffer += chunk.toString();
|
|
579
|
+
const lines = lineBuffer.split("\n");
|
|
580
|
+
lineBuffer = lines.pop();
|
|
581
|
+
for (const l of lines) handleStreamJsonLine(l);
|
|
582
|
+
} else {
|
|
583
|
+
accumulatedText += chunk.toString();
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
child.stderr.on("data", (chunk) => {
|
|
587
|
+
console.error(`[${engineDef.id}:${agentId}] stderr: ${chunk.toString().slice(0, 200)}`);
|
|
588
|
+
});
|
|
589
|
+
child.on("close", (code) => {
|
|
590
|
+
clearTimeout(hardTimer);
|
|
591
|
+
if (outputMode === "stream-json" && lineBuffer.trim()) handleStreamJsonLine(lineBuffer);
|
|
592
|
+
if (!resultReceived) {
|
|
593
|
+
resultReceived = true;
|
|
594
|
+
if (accumulatedText.trim()) resolve(accumulatedText.trim());
|
|
595
|
+
else reject(new Error(`${engineDef.label || engineDef.id} exited code=${code} with no output`));
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
child.on("error", (e) => {
|
|
599
|
+
clearTimeout(hardTimer);
|
|
600
|
+
if (!resultReceived) { resultReceived = true; reject(e); }
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ── Cursor CLI session + run ──────────────────────────────────────────────
|
|
606
|
+
|
|
607
|
+
function readCursorSessionId(agentId) {
|
|
608
|
+
if (!agentId) return null;
|
|
609
|
+
try {
|
|
610
|
+
const f = path.join(CURSOR_SESSION_DIR, `${agentId}.cursor-session`);
|
|
611
|
+
if (fs.existsSync(f)) return fs.readFileSync(f, "utf8").trim() || null;
|
|
612
|
+
} catch {}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function writeCursorSessionId(agentId, chatId) {
|
|
617
|
+
if (!agentId || !chatId) return;
|
|
618
|
+
try {
|
|
619
|
+
fs.mkdirSync(CURSOR_SESSION_DIR, { recursive: true });
|
|
620
|
+
fs.writeFileSync(path.join(CURSOR_SESSION_DIR, `${agentId}.cursor-session`), chatId, "utf8");
|
|
621
|
+
} catch {}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export async function runCursorCliTask(prompt, payload = {}) {
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
627
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
628
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
629
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
630
|
+
|
|
631
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
632
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
633
|
+
|
|
634
|
+
const args = [
|
|
635
|
+
"-p",
|
|
636
|
+
"--force",
|
|
637
|
+
"--trust",
|
|
638
|
+
"--output-format",
|
|
639
|
+
"stream-json",
|
|
640
|
+
"--stream-partial-output",
|
|
641
|
+
titledPrompt,
|
|
642
|
+
];
|
|
643
|
+
|
|
644
|
+
const agentCfg = _getAgentOpenCodeConfig(agentId);
|
|
645
|
+
const cursorDefault =
|
|
646
|
+
process.env.CREWSWARM_CURSOR_MODEL || "composer-2-fast";
|
|
647
|
+
let model =
|
|
648
|
+
payload?.cursorCliModel || agentCfg.cursorCliModel || null;
|
|
649
|
+
if (!model || String(model).trim() === "") {
|
|
650
|
+
model = cursorDefault;
|
|
651
|
+
} else if (String(model).includes("/")) {
|
|
652
|
+
// Provider assignment (e.g. anthropic/claude-…) is not a Cursor CLI id
|
|
653
|
+
model = cursorDefault;
|
|
654
|
+
} else if (String(model).includes("sonnet-4.6")) {
|
|
655
|
+
model = "sonnet-4.5";
|
|
656
|
+
}
|
|
657
|
+
args.push("--model", model);
|
|
658
|
+
|
|
659
|
+
args.push("--workspace", projectDir);
|
|
660
|
+
|
|
661
|
+
const existingChatId = readCursorSessionId(agentId);
|
|
662
|
+
if (existingChatId) {
|
|
663
|
+
args.push(`--resume=${existingChatId}`);
|
|
664
|
+
console.error(`[CursorCLI:${agentId}] Resuming chat ${existingChatId}`);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const cursorSpec = resolveCursorLaunchSpec(CURSOR_CLI_BIN);
|
|
668
|
+
|
|
669
|
+
// Early exit if binary doesn't exist — triggers fallback to OpenCode in rt-envelope.
|
|
670
|
+
if (!fs.existsSync(cursorSpec.bin) && !which(cursorSpec.bin)) {
|
|
671
|
+
throw new Error(`CursorCLI binary not found: "${cursorSpec.bin}". Install Cursor or set CURSOR_CLI_BIN.`);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const spawnArgs = [...cursorSpec.argsPrefix, ...args];
|
|
675
|
+
console.error(`[CursorCLI:${agentId}] Running: ${cursorSpec.displayCommand} -p --force --trust --output-format stream-json --stream-partial-output (workspace=${projectDir})`);
|
|
676
|
+
|
|
677
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
678
|
+
payload: { agent: agentId, model: model || "cursor/auto", ts: Date.now() } });
|
|
679
|
+
|
|
680
|
+
const child = spawn(cursorSpec.bin, spawnArgs, {
|
|
681
|
+
cwd: projectDir,
|
|
682
|
+
env: process.env,
|
|
683
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
let lineBuffer = "";
|
|
687
|
+
let accumulatedText = "";
|
|
688
|
+
let lastCursorAssistantNorm = "";
|
|
689
|
+
let receivedStreamDeltas = false;
|
|
690
|
+
let resultReceived = false;
|
|
691
|
+
let resultChatId = null;
|
|
692
|
+
let stderrAccum = "";
|
|
693
|
+
|
|
694
|
+
// Activity-based watchdog: extend deadline as long as output is flowing.
|
|
695
|
+
// Kills only when idle for CREWSWARM_ENGINE_IDLE_TIMEOUT_MS or absolute ceiling hit.
|
|
696
|
+
let lastActivity = Date.now();
|
|
697
|
+
const startTime = Date.now();
|
|
698
|
+
const watchdog = setInterval(() => {
|
|
699
|
+
if (resultReceived) { clearInterval(watchdog); return; }
|
|
700
|
+
const idle = Date.now() - lastActivity;
|
|
701
|
+
const total = Date.now() - startTime;
|
|
702
|
+
if (idle > CREWSWARM_ENGINE_IDLE_TIMEOUT_MS) {
|
|
703
|
+
clearInterval(watchdog);
|
|
704
|
+
child.kill("SIGKILL");
|
|
705
|
+
if (!resultReceived) reject(new Error(`CursorCLI idle timeout: no output for ${Math.round(idle / 1000)}s`));
|
|
706
|
+
} else if (total > CREWSWARM_ENGINE_MAX_TOTAL_MS) {
|
|
707
|
+
clearInterval(watchdog);
|
|
708
|
+
child.kill("SIGKILL");
|
|
709
|
+
if (!resultReceived) reject(new Error(`CursorCLI absolute max time exceeded: ${Math.round(total / 1000)}s`));
|
|
710
|
+
} else {
|
|
711
|
+
console.error(`[CursorCLI:${agentId}] Still working — idle=${Math.round(idle / 1000)}s, total=${Math.round(total / 1000)}s`);
|
|
712
|
+
}
|
|
713
|
+
}, 30_000);
|
|
714
|
+
|
|
715
|
+
function handleLine(line) {
|
|
716
|
+
line = line.trim();
|
|
717
|
+
if (!line) return;
|
|
718
|
+
try {
|
|
719
|
+
const ev = JSON.parse(line);
|
|
720
|
+
// Match dashboard engine-passthrough: Cursor can emit errors as JSON on stdout
|
|
721
|
+
if (ev.type === "error") {
|
|
722
|
+
const msg =
|
|
723
|
+
ev.message ||
|
|
724
|
+
ev.error ||
|
|
725
|
+
(typeof ev.text === "string" ? ev.text : "") ||
|
|
726
|
+
"";
|
|
727
|
+
if (String(msg).trim()) {
|
|
728
|
+
accumulatedText += `\n[Cursor error] ${String(msg).trim()}\n`;
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Stream-json text deltas (newer Cursor agent CLI) — gateway previously only read `assistant`
|
|
733
|
+
if (
|
|
734
|
+
ev.type === "stream_event" &&
|
|
735
|
+
ev.event?.type === "content_block_delta"
|
|
736
|
+
) {
|
|
737
|
+
const t = ev.event.delta?.text || "";
|
|
738
|
+
if (t) {
|
|
739
|
+
receivedStreamDeltas = true;
|
|
740
|
+
accumulatedText += t;
|
|
741
|
+
}
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (ev.type === "assistant") {
|
|
745
|
+
const content = ev.message?.content;
|
|
746
|
+
let piece = "";
|
|
747
|
+
if (Array.isArray(content)) {
|
|
748
|
+
for (const c of content) {
|
|
749
|
+
if (c.type === "text" && c.text) piece += c.text;
|
|
750
|
+
}
|
|
751
|
+
} else if (typeof content === "string") {
|
|
752
|
+
piece = content;
|
|
753
|
+
}
|
|
754
|
+
const norm = piece.replace(/\r/g, "").trim();
|
|
755
|
+
if (norm && norm === lastCursorAssistantNorm) {
|
|
756
|
+
/* duplicate assistant NDJSON line from Cursor */
|
|
757
|
+
} else if (piece) {
|
|
758
|
+
if (norm) lastCursorAssistantNorm = norm;
|
|
759
|
+
if (!receivedStreamDeltas) accumulatedText += piece;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (ev.type === "result" && !resultReceived) {
|
|
763
|
+
resultReceived = true;
|
|
764
|
+
resultChatId = ev.chatId || null;
|
|
765
|
+
clearInterval(watchdog);
|
|
766
|
+
child.kill("SIGTERM");
|
|
767
|
+
if (ev.is_error === true) {
|
|
768
|
+
const errText =
|
|
769
|
+
(typeof ev.result === "string" && ev.result) ||
|
|
770
|
+
ev.error ||
|
|
771
|
+
ev.message ||
|
|
772
|
+
"Cursor reported result error";
|
|
773
|
+
reject(new Error(String(errText).trim() || "Cursor result error"));
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (!accumulatedText.trim() && typeof ev.result === "string" && ev.result.trim()) {
|
|
777
|
+
accumulatedText = ev.result;
|
|
778
|
+
}
|
|
779
|
+
const out = accumulatedText.trim() || "(cursor agent completed with no text output)";
|
|
780
|
+
console.log(`[CursorCLI:${agentId}] Done via stream-json result event — ${out.length} chars`);
|
|
781
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_idle", to: "broadcast",
|
|
782
|
+
payload: { agent: agentId, ts: Date.now() } });
|
|
783
|
+
if (agentId && resultChatId) {
|
|
784
|
+
writeCursorSessionId(agentId, resultChatId);
|
|
785
|
+
console.error(`[CursorCLI:${agentId}] Chat session saved: ${resultChatId}`);
|
|
786
|
+
}
|
|
787
|
+
resolve(out);
|
|
788
|
+
}
|
|
789
|
+
} catch {
|
|
790
|
+
/* non-JSON line — ignore (Cursor may print rare plain text) */
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
child.stdout.on("data", (d) => {
|
|
795
|
+
lastActivity = Date.now();
|
|
796
|
+
lineBuffer += d.toString();
|
|
797
|
+
const lines = lineBuffer.split("\n");
|
|
798
|
+
lineBuffer = lines.pop();
|
|
799
|
+
lines.forEach(handleLine);
|
|
800
|
+
});
|
|
801
|
+
child.stderr.on("data", (d) => {
|
|
802
|
+
lastActivity = Date.now();
|
|
803
|
+
const raw = d.toString();
|
|
804
|
+
const s = raw.trim();
|
|
805
|
+
if (s && !s.startsWith("\x1b")) console.error(`[CursorCLI:${agentId}] stderr: ${s.slice(0, 200)}`);
|
|
806
|
+
if (stderrAccum.length < 8000) stderrAccum += raw;
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
child.on("error", (err) => {
|
|
810
|
+
clearInterval(watchdog);
|
|
811
|
+
if (!resultReceived) reject(err);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
child.on("close", (code) => {
|
|
815
|
+
clearInterval(watchdog);
|
|
816
|
+
if (lineBuffer.trim()) handleLine(lineBuffer);
|
|
817
|
+
if (!resultReceived) {
|
|
818
|
+
if (accumulatedText.trim()) {
|
|
819
|
+
console.warn(
|
|
820
|
+
`[CursorCLI:${agentId}] No stream-json result event; accepting ${accumulatedText.length} chars of accumulated output (exit ${code})`,
|
|
821
|
+
);
|
|
822
|
+
resultReceived = true;
|
|
823
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_idle", to: "broadcast",
|
|
824
|
+
payload: { agent: agentId, ts: Date.now() } });
|
|
825
|
+
resolve(accumulatedText.trim());
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const tail = stderrAccum.replace(/\u001b\[[\d;?]*[ -/]*[@-~]/g, "").trim();
|
|
829
|
+
const hint =
|
|
830
|
+
existingChatId
|
|
831
|
+
? ` Stale Cursor session resume (chat ${existingChatId.slice(0, 8)}…)? Delete ${path.join(CURSOR_SESSION_DIR, `${agentId}.cursor-session`)} and retry.`
|
|
832
|
+
: "";
|
|
833
|
+
reject(
|
|
834
|
+
new Error(
|
|
835
|
+
`CursorCLI exited without a result event (code ${code}).${hint}` +
|
|
836
|
+
(tail ? ` stderr: ${tail.slice(0, 1200)}` : " No stderr captured — check \`agent -p\` in the project directory or CURSOR_CLI_BIN."),
|
|
837
|
+
),
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// ── Claude Code session + run ───────────────────────────────────────────────
|
|
845
|
+
|
|
846
|
+
function readClaudeSessionId(agentId) {
|
|
847
|
+
if (!agentId) return null;
|
|
848
|
+
try {
|
|
849
|
+
const f = path.join(CLAUDE_SESSION_DIR, `${agentId}.claude-session`);
|
|
850
|
+
if (fs.existsSync(f)) return fs.readFileSync(f, "utf8").trim() || null;
|
|
851
|
+
} catch {}
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function writeClaudeSessionId(agentId, sessionId) {
|
|
856
|
+
if (!agentId || !sessionId) return;
|
|
857
|
+
try {
|
|
858
|
+
fs.mkdirSync(CLAUDE_SESSION_DIR, { recursive: true });
|
|
859
|
+
fs.writeFileSync(path.join(CLAUDE_SESSION_DIR, `${agentId}.claude-session`), sessionId, "utf8");
|
|
860
|
+
} catch {}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
export async function runCodexTask(prompt, payload = {}) {
|
|
864
|
+
return new Promise((resolve, reject) => {
|
|
865
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
866
|
+
const taskBody = String(prompt ?? "").trim();
|
|
867
|
+
if (!taskBody) {
|
|
868
|
+
reject(
|
|
869
|
+
new Error(
|
|
870
|
+
"Codex: task text is empty — the gateway sent no instructions. Check dispatch/pipeline payload.",
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
877
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
878
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
879
|
+
const expandedDir = normalizeProjectDir(projectDir) || projectDir;
|
|
880
|
+
if (expandedDir && fs.existsSync(expandedDir)) {
|
|
881
|
+
projectDir = expandedDir;
|
|
882
|
+
} else if (!fs.existsSync(projectDir)) {
|
|
883
|
+
console.error(
|
|
884
|
+
`[Codex:${agentId}] projectDir not found (${projectDir}), using process.cwd()`,
|
|
885
|
+
);
|
|
886
|
+
projectDir = process.cwd();
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
890
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
891
|
+
|
|
892
|
+
// Match crew-lead engine passthrough (http-server codex path): allow cwd outside a trusted git root.
|
|
893
|
+
const agentCfg = _getAgentOpenCodeConfig(agentId);
|
|
894
|
+
const codexModel = resolveCodexCliModel(payload, agentCfg);
|
|
895
|
+
const args = [
|
|
896
|
+
"-a",
|
|
897
|
+
"never",
|
|
898
|
+
"exec",
|
|
899
|
+
"--sandbox",
|
|
900
|
+
"danger-full-access",
|
|
901
|
+
"--skip-git-repo-check",
|
|
902
|
+
"--json",
|
|
903
|
+
];
|
|
904
|
+
if (codexModel) args.push("--model", codexModel);
|
|
905
|
+
args.push(titledPrompt);
|
|
906
|
+
|
|
907
|
+
console.error(`[Codex:${agentId}] Running: ${CODEX_CLI_BIN} exec --json (cwd=${projectDir})`);
|
|
908
|
+
|
|
909
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
910
|
+
payload: { agent: agentId, model: codexModel || "codex/auto", ts: Date.now() } });
|
|
911
|
+
|
|
912
|
+
const child = spawn(CODEX_CLI_BIN, args, {
|
|
913
|
+
cwd: projectDir,
|
|
914
|
+
env: process.env,
|
|
915
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
let lineBuffer = "";
|
|
919
|
+
let accumulatedText = "";
|
|
920
|
+
/** Non-JSON lines (stderr, errors, usage) — previously swallowed by catch {} */
|
|
921
|
+
let orphanStream = "";
|
|
922
|
+
const appendOrphan = (line) => {
|
|
923
|
+
const s = String(line).trim();
|
|
924
|
+
if (!s) return;
|
|
925
|
+
const chunk = (orphanStream ? "\n" : "") + s;
|
|
926
|
+
orphanStream = (orphanStream + chunk).slice(-8000);
|
|
927
|
+
};
|
|
928
|
+
/** Codex emits {type:"error"} / {type:"turn.failed"} on stdout — capture for reject() */
|
|
929
|
+
let lastCodexError = null;
|
|
930
|
+
let resolved = false;
|
|
931
|
+
|
|
932
|
+
// Activity-based watchdog: kill only if idle for too long OR absolute max exceeded
|
|
933
|
+
let lastActivity = Date.now();
|
|
934
|
+
const startTime = Date.now();
|
|
935
|
+
const watchdog = setInterval(() => {
|
|
936
|
+
if (resolved) { clearInterval(watchdog); return; }
|
|
937
|
+
const idle = Date.now() - lastActivity;
|
|
938
|
+
const total = Date.now() - startTime;
|
|
939
|
+
if (idle > CREWSWARM_ENGINE_IDLE_TIMEOUT_MS) {
|
|
940
|
+
clearInterval(watchdog);
|
|
941
|
+
child.kill("SIGKILL");
|
|
942
|
+
if (!resolved) reject(new Error(`Codex idle timeout: no output for ${Math.round(idle / 1000)}s`));
|
|
943
|
+
} else if (total > CREWSWARM_ENGINE_MAX_TOTAL_MS) {
|
|
944
|
+
clearInterval(watchdog);
|
|
945
|
+
child.kill("SIGKILL");
|
|
946
|
+
if (!resolved) reject(new Error(`Codex absolute max time exceeded: ${Math.round(total / 1000)}s`));
|
|
947
|
+
} else {
|
|
948
|
+
console.error(`[Codex:${agentId}] Still working — idle=${Math.round(idle / 1000)}s, total=${Math.round(total / 1000)}s`);
|
|
949
|
+
}
|
|
950
|
+
}, 30_000);
|
|
951
|
+
|
|
952
|
+
function handleLine(line) {
|
|
953
|
+
line = line.trim();
|
|
954
|
+
if (!line) return;
|
|
955
|
+
try {
|
|
956
|
+
const ev = JSON.parse(line);
|
|
957
|
+
if (ev.type === "error" && ev.message) {
|
|
958
|
+
lastCodexError = String(ev.message).trim();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (ev.type === "turn.failed") {
|
|
962
|
+
const msg =
|
|
963
|
+
(ev.error && String(ev.error.message || "").trim()) ||
|
|
964
|
+
String(ev.message || "").trim();
|
|
965
|
+
if (msg) lastCodexError = msg;
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (ev.type === "item.completed" && ev.item?.type === "agent_message" && ev.item?.text) {
|
|
969
|
+
accumulatedText += ev.item.text;
|
|
970
|
+
} else if (ev.type === "turn.completed") {
|
|
971
|
+
clearInterval(watchdog);
|
|
972
|
+
resolved = true;
|
|
973
|
+
child.kill("SIGTERM");
|
|
974
|
+
resolve(accumulatedText.trim() || "(no output from Codex)");
|
|
975
|
+
}
|
|
976
|
+
} catch {
|
|
977
|
+
appendOrphan(line);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function onData(chunk) {
|
|
982
|
+
lastActivity = Date.now(); // Reset activity timer on ANY output
|
|
983
|
+
lineBuffer += chunk.toString();
|
|
984
|
+
const lines = lineBuffer.split("\n");
|
|
985
|
+
lineBuffer = lines.pop() || "";
|
|
986
|
+
for (const line of lines) handleLine(line);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
child.stdout.on("data", onData);
|
|
990
|
+
child.stderr.on("data", onData);
|
|
991
|
+
|
|
992
|
+
child.on("close", (code, signal) => {
|
|
993
|
+
clearInterval(watchdog);
|
|
994
|
+
if (lineBuffer.trim()) handleLine(lineBuffer);
|
|
995
|
+
if (!resolved) {
|
|
996
|
+
resolved = true;
|
|
997
|
+
if (accumulatedText.trim()) resolve(accumulatedText.trim());
|
|
998
|
+
else if (lastCodexError)
|
|
999
|
+
reject(new Error(lastCodexError));
|
|
1000
|
+
else {
|
|
1001
|
+
const tail = orphanStream.trim();
|
|
1002
|
+
const hint = tail
|
|
1003
|
+
? `\n\n--- Codex raw stream (non-JSON / stderr) ---\n${tail}`
|
|
1004
|
+
: "";
|
|
1005
|
+
const meta = `\n(cwd=${projectDir}, bin=${CODEX_CLI_BIN}, code=${code}${
|
|
1006
|
+
signal ? `, signal=${signal}` : ""
|
|
1007
|
+
})`;
|
|
1008
|
+
console.error(
|
|
1009
|
+
`[Codex:${agentId}] exit ${code}${signal ? ` (${signal})` : ""} no JSON result${hint ? ` — ${tail.slice(0, 500)}` : ""}${meta}`,
|
|
1010
|
+
);
|
|
1011
|
+
reject(
|
|
1012
|
+
new Error(
|
|
1013
|
+
`Codex exited with code ${code} and no JSON result.${hint}${meta}`,
|
|
1014
|
+
),
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
child.on("error", (e) => {
|
|
1021
|
+
clearInterval(watchdog);
|
|
1022
|
+
if (!resolved) {
|
|
1023
|
+
resolved = true;
|
|
1024
|
+
reject(
|
|
1025
|
+
new Error(
|
|
1026
|
+
`${e?.message || e} (cwd=${projectDir}, bin=${CODEX_CLI_BIN})`,
|
|
1027
|
+
),
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export function shouldUseDockerSandbox(payload, incomingType) {
|
|
1035
|
+
if (incomingType !== "command.run_task" && incomingType !== "task.assigned") return false;
|
|
1036
|
+
const runtime = String(payload?.runtime || payload?.executor || payload?.engine || "").toLowerCase();
|
|
1037
|
+
if (runtime === "docker-sandbox" || runtime === "docker") return true;
|
|
1038
|
+
if (payload?.useDockerSandbox === true) return true;
|
|
1039
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "").toLowerCase();
|
|
1040
|
+
try {
|
|
1041
|
+
const agents = _loadAgentList();
|
|
1042
|
+
const cfg = agents.find(a => a.id === agentId || a.id === `crew-${agentId}`);
|
|
1043
|
+
if (cfg?.useDockerSandbox === true) return true;
|
|
1044
|
+
} catch {}
|
|
1045
|
+
return process.env.CREWSWARM_DOCKER_SANDBOX === "1";
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export async function runDockerSandboxTask(prompt, payload = {}) {
|
|
1049
|
+
return new Promise((resolve, reject) => {
|
|
1050
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
1051
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
1052
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
1053
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
1054
|
+
|
|
1055
|
+
const sandboxName = process.env.CREWSWARM_DOCKER_SANDBOX_NAME || "crewswarm";
|
|
1056
|
+
const innerEngine = (process.env.CREWSWARM_DOCKER_SANDBOX_INNER_ENGINE || "claude").toLowerCase();
|
|
1057
|
+
|
|
1058
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
1059
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
1060
|
+
|
|
1061
|
+
let innerArgs;
|
|
1062
|
+
if (innerEngine === "opencode") {
|
|
1063
|
+
innerArgs = ["opencode", "run", titledPrompt, "--model", process.env.CREWSWARM_OPENCODE_MODEL || "anthropic/claude-sonnet-4-5"];
|
|
1064
|
+
} else if (innerEngine === "codex") {
|
|
1065
|
+
innerArgs = ["codex", "exec", "--sandbox", "workspace-write", "--json", titledPrompt];
|
|
1066
|
+
} else {
|
|
1067
|
+
innerArgs = ["claude", "-p", "--dangerously-skip-permissions", "--output-format", "stream-json", "--verbose", titledPrompt];
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
const args = ["sandbox", "exec", sandboxName, "--", ...innerArgs];
|
|
1071
|
+
console.error(`[DockerSandbox:${agentId}] Running: docker ${args.slice(0, 4).join(" ")} (inner=${innerEngine}, cwd=${projectDir})`);
|
|
1072
|
+
|
|
1073
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
1074
|
+
payload: { agent: agentId, model: `docker-sandbox/${innerEngine}`, ts: Date.now() } });
|
|
1075
|
+
|
|
1076
|
+
const TIMEOUT_MS = parseInt(process.env.CREWSWARM_DOCKER_SANDBOX_TIMEOUT_MS || "300000", 10);
|
|
1077
|
+
const child = spawn("docker", args, {
|
|
1078
|
+
cwd: projectDir,
|
|
1079
|
+
env: process.env,
|
|
1080
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
let lineBuffer = "";
|
|
1084
|
+
let accumulatedText = "";
|
|
1085
|
+
let resolved = false;
|
|
1086
|
+
let receivedDeltas = false;
|
|
1087
|
+
|
|
1088
|
+
const hardTimer = setTimeout(() => {
|
|
1089
|
+
child.kill("SIGKILL");
|
|
1090
|
+
if (!resolved) reject(new Error(`Docker Sandbox timeout after ${TIMEOUT_MS}ms`));
|
|
1091
|
+
}, TIMEOUT_MS);
|
|
1092
|
+
|
|
1093
|
+
function handleLine(line) {
|
|
1094
|
+
line = line.trim();
|
|
1095
|
+
if (!line) return;
|
|
1096
|
+
if (innerEngine === "opencode") {
|
|
1097
|
+
accumulatedText += line + "\n";
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (innerEngine === "codex") {
|
|
1101
|
+
try {
|
|
1102
|
+
const ev = JSON.parse(line);
|
|
1103
|
+
if (ev.type === "item.completed" && ev.item?.type === "agent_message" && ev.item?.text) {
|
|
1104
|
+
accumulatedText += ev.item.text;
|
|
1105
|
+
} else if (ev.type === "turn.completed") {
|
|
1106
|
+
clearTimeout(hardTimer); resolved = true;
|
|
1107
|
+
child.kill("SIGTERM");
|
|
1108
|
+
resolve(accumulatedText.trim() || "(no output from Docker Sandbox / Codex)");
|
|
1109
|
+
}
|
|
1110
|
+
} catch {}
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const ev = JSON.parse(line);
|
|
1115
|
+
if (ev.type === "stream_event" && ev.event?.type === "content_block_delta") {
|
|
1116
|
+
const t = ev.event.delta?.text || "";
|
|
1117
|
+
if (t) { accumulatedText += t; receivedDeltas = true; }
|
|
1118
|
+
} else if (ev.type === "assistant" && !receivedDeltas) {
|
|
1119
|
+
const content = ev.message?.content;
|
|
1120
|
+
if (Array.isArray(content)) { for (const c of content) { if (c.type === "text") accumulatedText += c.text; } }
|
|
1121
|
+
else if (typeof content === "string") accumulatedText += content;
|
|
1122
|
+
} else if (ev.type === "result") {
|
|
1123
|
+
if (!accumulatedText && ev.result) accumulatedText += ev.result;
|
|
1124
|
+
clearTimeout(hardTimer); resolved = true;
|
|
1125
|
+
child.kill("SIGTERM");
|
|
1126
|
+
resolve(accumulatedText.trim() || "(no output from Docker Sandbox / Claude)");
|
|
1127
|
+
}
|
|
1128
|
+
} catch { accumulatedText += line + "\n"; }
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function onData(chunk) {
|
|
1132
|
+
lineBuffer += chunk.toString();
|
|
1133
|
+
const lines = lineBuffer.split("\n");
|
|
1134
|
+
lineBuffer = lines.pop() || "";
|
|
1135
|
+
for (const line of lines) handleLine(line);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
child.stdout.on("data", onData);
|
|
1139
|
+
child.stderr.on("data", onData);
|
|
1140
|
+
|
|
1141
|
+
child.on("close", (code) => {
|
|
1142
|
+
clearTimeout(hardTimer);
|
|
1143
|
+
if (lineBuffer.trim()) handleLine(lineBuffer);
|
|
1144
|
+
if (!resolved) {
|
|
1145
|
+
resolved = true;
|
|
1146
|
+
if (accumulatedText.trim()) resolve(accumulatedText.trim());
|
|
1147
|
+
else reject(new Error(`Docker Sandbox exited with code ${code} and no output`));
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
child.on("error", (e) => {
|
|
1152
|
+
clearTimeout(hardTimer);
|
|
1153
|
+
if (!resolved) { resolved = true; reject(e); }
|
|
1154
|
+
});
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
1159
|
+
return new Promise((resolve, reject) => {
|
|
1160
|
+
const agentId = String(payload?.agentId || payload?.agent || CREWSWARM_RT_AGENT || "");
|
|
1161
|
+
const configuredDir = _getOpencodeProjectDir();
|
|
1162
|
+
let projectDir = payload?.projectDir || configuredDir || process.cwd();
|
|
1163
|
+
projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
|
|
1164
|
+
|
|
1165
|
+
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
1166
|
+
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
1167
|
+
|
|
1168
|
+
const args = [
|
|
1169
|
+
"-p",
|
|
1170
|
+
"--dangerously-skip-permissions",
|
|
1171
|
+
"--output-format", "stream-json",
|
|
1172
|
+
"--verbose",
|
|
1173
|
+
];
|
|
1174
|
+
|
|
1175
|
+
const agentCfg = _getAgentOpenCodeConfig(agentId);
|
|
1176
|
+
const model = payload?.claudeCodeModel || agentCfg.claudeCodeModel || process.env.CREWSWARM_CLAUDE_CODE_MODEL || null;
|
|
1177
|
+
if (model && !model.includes("/")) {
|
|
1178
|
+
args.push("--model", model);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const existingSession = readClaudeSessionId(agentId);
|
|
1182
|
+
if (existingSession) {
|
|
1183
|
+
args.push("--resume", existingSession);
|
|
1184
|
+
console.error(`[ClaudeCode:${agentId}] Resuming session ${existingSession}`);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// CRITICAL: Claude Code expects the prompt as a command-line argument, NOT via stdin
|
|
1188
|
+
args.push(titledPrompt);
|
|
1189
|
+
|
|
1190
|
+
if (!which(CLAUDE_CODE_BIN)) {
|
|
1191
|
+
throw new Error(`Claude Code CLI not found: "${CLAUDE_CODE_BIN}". Install with: npm i -g @anthropic-ai/claude-code`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
console.error(`[ClaudeCode:${agentId}] Running: ${CLAUDE_CODE_BIN} -p --dangerously-skip-permissions (cwd=${projectDir})`);
|
|
1195
|
+
|
|
1196
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
1197
|
+
payload: { agent: agentId, model: model || "claude/auto", ts: Date.now() } });
|
|
1198
|
+
|
|
1199
|
+
const child = spawn(CLAUDE_CODE_BIN, args, {
|
|
1200
|
+
cwd: projectDir,
|
|
1201
|
+
env: process.env,
|
|
1202
|
+
stdio: ["ignore", "pipe", "pipe"], // Changed from "pipe" to "ignore" for stdin since we use args
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
let lineBuffer = "";
|
|
1206
|
+
let accumulatedText = "";
|
|
1207
|
+
let stderrText = "";
|
|
1208
|
+
let resultReceived = false;
|
|
1209
|
+
let resultSessionId = null;
|
|
1210
|
+
|
|
1211
|
+
// Activity-based watchdog — same logic as Cursor CLI runner.
|
|
1212
|
+
let lastActivity = Date.now();
|
|
1213
|
+
const startTime = Date.now();
|
|
1214
|
+
const watchdog = setInterval(() => {
|
|
1215
|
+
if (resultReceived) { clearInterval(watchdog); return; }
|
|
1216
|
+
const idle = Date.now() - lastActivity;
|
|
1217
|
+
const total = Date.now() - startTime;
|
|
1218
|
+
if (idle > CREWSWARM_ENGINE_IDLE_TIMEOUT_MS) {
|
|
1219
|
+
clearInterval(watchdog);
|
|
1220
|
+
child.kill("SIGKILL");
|
|
1221
|
+
if (!resultReceived) {
|
|
1222
|
+
const hint = stderrText.trim() ? ` stderr: ${stderrText.slice(-300)}` : "";
|
|
1223
|
+
reject(new Error(`ClaudeCode idle timeout: no output for ${Math.round(idle / 1000)}s.${hint}`));
|
|
1224
|
+
}
|
|
1225
|
+
} else if (total > CREWSWARM_ENGINE_MAX_TOTAL_MS) {
|
|
1226
|
+
clearInterval(watchdog);
|
|
1227
|
+
child.kill("SIGKILL");
|
|
1228
|
+
if (!resultReceived) {
|
|
1229
|
+
const hint = stderrText.trim() ? ` stderr: ${stderrText.slice(-300)}` : "";
|
|
1230
|
+
reject(new Error(`ClaudeCode absolute max time exceeded: ${Math.round(total / 1000)}s.${hint}`));
|
|
1231
|
+
}
|
|
1232
|
+
} else {
|
|
1233
|
+
console.error(`[ClaudeCode:${agentId}] Still working — idle=${Math.round(idle / 1000)}s, total=${Math.round(total / 1000)}s`);
|
|
1234
|
+
}
|
|
1235
|
+
}, 30_000);
|
|
1236
|
+
|
|
1237
|
+
function handleLine(line) {
|
|
1238
|
+
line = line.trim();
|
|
1239
|
+
if (!line) return;
|
|
1240
|
+
try {
|
|
1241
|
+
const ev = JSON.parse(line);
|
|
1242
|
+
|
|
1243
|
+
if (ev.type === "stream_event") {
|
|
1244
|
+
const inner = ev.event;
|
|
1245
|
+
if (inner?.type === "content_block_delta" && inner?.delta?.type === "text_delta") {
|
|
1246
|
+
accumulatedText += inner.delta.text || "";
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
if (ev.type === "assistant") {
|
|
1251
|
+
const content = ev.message?.content;
|
|
1252
|
+
if (Array.isArray(content)) {
|
|
1253
|
+
for (const c of content) {
|
|
1254
|
+
if (c.type === "text" && c.text) accumulatedText += c.text;
|
|
1255
|
+
}
|
|
1256
|
+
} else if (typeof content === "string") {
|
|
1257
|
+
accumulatedText += content;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (ev.type === "result" && !resultReceived) {
|
|
1262
|
+
resultReceived = true;
|
|
1263
|
+
resultSessionId = ev.session_id || ev.sessionId || ev.chatId || null;
|
|
1264
|
+
clearInterval(watchdog);
|
|
1265
|
+
child.kill("SIGTERM");
|
|
1266
|
+
|
|
1267
|
+
// Try to extract meaningful output
|
|
1268
|
+
let out = (ev.result || accumulatedText).trim();
|
|
1269
|
+
|
|
1270
|
+
// If no text output, check for file operations in the event metadata
|
|
1271
|
+
if (!out && ev.filesModified && ev.filesModified.length > 0) {
|
|
1272
|
+
out = `Modified ${ev.filesModified.length} file(s): ${ev.filesModified.slice(0, 3).join(", ")}${ev.filesModified.length > 3 ? "..." : ""}`;
|
|
1273
|
+
} else if (!out && ev.operations && ev.operations.length > 0) {
|
|
1274
|
+
const opSummary = ev.operations.map(op => `${op.type}: ${op.path || op.file || "file"}`).slice(0, 3).join("; ");
|
|
1275
|
+
out = `Completed ${ev.operations.length} operation(s): ${opSummary}${ev.operations.length > 3 ? "..." : ""}`;
|
|
1276
|
+
} else if (!out) {
|
|
1277
|
+
// Check if projectDir exists and was modified (fallback file check)
|
|
1278
|
+
try {
|
|
1279
|
+
const { execSync } = require("node:child_process");
|
|
1280
|
+
const since = Math.floor(Date.now() / 1000) - 60; // Last 60 seconds
|
|
1281
|
+
const changedFiles = execSync(`find "${projectDir}" -type f -newermt "@${since}" 2>/dev/null | head -5`,
|
|
1282
|
+
{ encoding: "utf8", timeout: 2000 }).trim().split("\n").filter(Boolean);
|
|
1283
|
+
if (changedFiles.length > 0) {
|
|
1284
|
+
const fileNames = changedFiles.map(f => f.split("/").pop()).slice(0, 3);
|
|
1285
|
+
out = `Task completed. Modified: ${fileNames.join(", ")}${changedFiles.length > 3 ? "..." : ""}`;
|
|
1286
|
+
} else {
|
|
1287
|
+
out = "(claude code completed with no text output)";
|
|
1288
|
+
}
|
|
1289
|
+
} catch {
|
|
1290
|
+
out = "(claude code completed with no text output)";
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
console.log(`[ClaudeCode:${agentId}] Done — ${out.length} chars`);
|
|
1295
|
+
|
|
1296
|
+
try {
|
|
1297
|
+
const modelUsage = ev.modelUsage || {};
|
|
1298
|
+
const modelEntries = Object.entries(modelUsage);
|
|
1299
|
+
let totalInputTokens = 0, totalOutputTokens = 0;
|
|
1300
|
+
if (modelEntries.length > 0) {
|
|
1301
|
+
for (const [mid, mu] of modelEntries) {
|
|
1302
|
+
const p = (mu.inputTokens || 0) + (mu.cacheCreationInputTokens || 0);
|
|
1303
|
+
const c = mu.outputTokens || 0;
|
|
1304
|
+
totalInputTokens += p;
|
|
1305
|
+
totalOutputTokens += c;
|
|
1306
|
+
recordTokenUsage(mid, {
|
|
1307
|
+
prompt_tokens: p,
|
|
1308
|
+
completion_tokens: c,
|
|
1309
|
+
cache_read_input_tokens: mu.cacheReadInputTokens || 0,
|
|
1310
|
+
}, null);
|
|
1311
|
+
}
|
|
1312
|
+
} else if (ev.usage) {
|
|
1313
|
+
const u = ev.usage;
|
|
1314
|
+
totalInputTokens = (u.input_tokens || 0) + (u.cache_creation_input_tokens || 0);
|
|
1315
|
+
totalOutputTokens = u.output_tokens || 0;
|
|
1316
|
+
recordTokenUsage("claude/auto", {
|
|
1317
|
+
prompt_tokens: totalInputTokens,
|
|
1318
|
+
completion_tokens: totalOutputTokens,
|
|
1319
|
+
cache_read_input_tokens: u.cache_read_input_tokens || 0,
|
|
1320
|
+
}, null);
|
|
1321
|
+
}
|
|
1322
|
+
const exactCost = Number(ev.total_cost_usd || 0);
|
|
1323
|
+
if (agentId && (exactCost > 0 || totalInputTokens > 0)) {
|
|
1324
|
+
addAgentSpend(agentId, totalInputTokens + totalOutputTokens, exactCost);
|
|
1325
|
+
console.log(`[ClaudeCode:${agentId}] Cost: $${exactCost.toFixed(6)} (${totalInputTokens}in + ${totalOutputTokens}out tokens)`);
|
|
1326
|
+
}
|
|
1327
|
+
} catch (usageErr) {
|
|
1328
|
+
console.warn(`[ClaudeCode:${agentId}] Usage capture failed: ${usageErr.message}`);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
_rtClientForApprovals?.publish({ channel: "events", type: "agent_idle", to: "broadcast",
|
|
1332
|
+
payload: { agent: agentId, ts: Date.now() } });
|
|
1333
|
+
if (agentId && resultSessionId) {
|
|
1334
|
+
writeClaudeSessionId(agentId, resultSessionId);
|
|
1335
|
+
console.error(`[ClaudeCode:${agentId}] Session saved: ${resultSessionId}`);
|
|
1336
|
+
}
|
|
1337
|
+
resolve(out);
|
|
1338
|
+
}
|
|
1339
|
+
} catch {}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
child.stdout.on("data", (d) => {
|
|
1343
|
+
lastActivity = Date.now();
|
|
1344
|
+
lineBuffer += d.toString();
|
|
1345
|
+
const lines = lineBuffer.split("\n");
|
|
1346
|
+
lineBuffer = lines.pop();
|
|
1347
|
+
lines.forEach(handleLine);
|
|
1348
|
+
});
|
|
1349
|
+
child.stderr.on("data", (d) => {
|
|
1350
|
+
lastActivity = Date.now();
|
|
1351
|
+
const s = d.toString().trim();
|
|
1352
|
+
stderrText += s + "\n";
|
|
1353
|
+
if (s && !s.startsWith("\x1b")) console.error(`[ClaudeCode:${agentId}] stderr: ${s.slice(0, 160)}`);
|
|
1354
|
+
});
|
|
1355
|
+
child.on("error", (err) => {
|
|
1356
|
+
clearInterval(watchdog);
|
|
1357
|
+
if (!resultReceived) reject(err);
|
|
1358
|
+
});
|
|
1359
|
+
child.on("close", (exitCode) => {
|
|
1360
|
+
clearInterval(watchdog);
|
|
1361
|
+
if (!resultReceived) {
|
|
1362
|
+
const hint = stderrText.trim() ? `\nstderr: ${stderrText.slice(-500)}` : "";
|
|
1363
|
+
reject(new Error(`ClaudeCode exited (code ${exitCode}) without result event. Output: ${accumulatedText.slice(0, 300)}${hint}`));
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
});
|
|
1367
|
+
}
|