crewswarm 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
- package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
- package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
- package/apps/dashboard/dist/index.html +135 -15
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +101 -56
- package/crew-lead.mjs +34 -4
- package/lib/bridges/cli-executor.mjs +1 -1
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/browser/passthrough-stderr.js +1 -0
- package/lib/chat/project-messages.mjs +3 -5
- package/lib/cli-process-tracker.mjs +3 -2
- package/lib/contacts/identity-linker.mjs +1 -0
- package/lib/crew-judge/judge.mjs +19 -18
- package/lib/crew-lead/agent-manager.mjs +1 -1
- package/lib/crew-lead/background.mjs +14 -1
- package/lib/crew-lead/chat-handler.mjs +38 -1
- package/lib/crew-lead/http-server.mjs +106 -57
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +14 -1
- package/lib/crew-lead/tools.mjs +3 -2
- package/lib/crew-lead/wave-dispatcher.mjs +19 -5
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/crew-cli.mjs +1 -1
- package/lib/engines/engine-registry.mjs +14 -3
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +28 -4
- package/lib/gemini-cli-passthrough-noise.mjs +1 -1
- package/lib/integrations/code-search.mjs +4 -3
- package/lib/memory/shared-adapter.mjs +23 -10
- package/lib/pipeline/manager.mjs +2 -1
- package/lib/runtime/config.mjs +1 -1
- package/lib/runtime/paths.mjs +12 -8
- package/lib/runtime/spending.mjs +2 -1
- package/package.json +42 -14
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +76 -0
- package/scripts/dashboard.mjs +1667 -551
- package/scripts/generate-openapi.mjs +683 -277
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/restart-service.sh +12 -9
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- 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 +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- 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 +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- 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 +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- 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 +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
|
@@ -7,6 +7,60 @@ import { applyProjectDirToPipelineSteps } from "../dispatch/parsers.mjs";
|
|
|
7
7
|
let reconnectTimer = null;
|
|
8
8
|
let isConnecting = false;
|
|
9
9
|
let crewLeadHeartbeat = null;
|
|
10
|
+
let currentWs = null; // Global ref to current WebSocket — prevents stale closures
|
|
11
|
+
let connectionId = 0; // Monotonic ID to detect stale connections
|
|
12
|
+
let reconnectAttempts = 0; // For exponential backoff
|
|
13
|
+
|
|
14
|
+
const CODER_AGENT_RE = /crew-coder|crew-frontend|crew-fixer|crew-ml|crew-coder-back|crew-coder-front/;
|
|
15
|
+
|
|
16
|
+
function normalizeEngineId(value) {
|
|
17
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
18
|
+
if (!raw) return null;
|
|
19
|
+
if (raw === "claude" || raw === "claude-code" || raw.includes("claude code")) return "claude";
|
|
20
|
+
if (raw === "codex" || raw === "codex-cli" || raw.includes("codex")) return "codex";
|
|
21
|
+
if (raw === "cursor" || raw === "cursor-cli" || raw.includes("cursor")) return "cursor";
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function inferDispatchEngine(dispatch = null, message = "") {
|
|
26
|
+
const explicit =
|
|
27
|
+
normalizeEngineId(dispatch?.engineUsed)
|
|
28
|
+
|| normalizeEngineId(dispatch?.runtime)
|
|
29
|
+
|| (dispatch?.useCodex === true ? "codex" : null)
|
|
30
|
+
|| (dispatch?.useCursorCli === true ? "cursor" : null)
|
|
31
|
+
|| (dispatch?.useClaudeCode === true ? "claude" : null);
|
|
32
|
+
if (explicit) return explicit;
|
|
33
|
+
|
|
34
|
+
const text = String(message || "");
|
|
35
|
+
if (/claude\s*code|anthropic|sonnet|opus/i.test(text)) return "claude";
|
|
36
|
+
if (/codex|gpt-5(\.\d+)?-codex|openai/i.test(text)) return "codex";
|
|
37
|
+
if (/cursor/i.test(text)) return "cursor";
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getNextCoderEngine(currentEngine) {
|
|
42
|
+
const current = normalizeEngineId(currentEngine);
|
|
43
|
+
if (current === "claude") return "codex";
|
|
44
|
+
if (current === "codex") return "claude";
|
|
45
|
+
if (current === "cursor") return null;
|
|
46
|
+
return "codex";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildEngineFallbackMeta(dispatch = null, currentEngine = null, trigger = "rate-limit-fallback") {
|
|
50
|
+
const nextEngine = getNextCoderEngine(currentEngine);
|
|
51
|
+
if (!nextEngine) return null;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
...(dispatch || {}),
|
|
55
|
+
useClaudeCode: nextEngine === "claude",
|
|
56
|
+
useCodex: nextEngine === "codex",
|
|
57
|
+
useCursorCli: nextEngine === "cursor",
|
|
58
|
+
runtime: nextEngine,
|
|
59
|
+
engineFallbackFrom: normalizeEngineId(currentEngine),
|
|
60
|
+
engineFallbackTo: nextEngine,
|
|
61
|
+
triggeredBy: trigger,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
10
64
|
|
|
11
65
|
export function initWsRouter(deps) {
|
|
12
66
|
const {
|
|
@@ -29,6 +83,7 @@ export function initWsRouter(deps) {
|
|
|
29
83
|
appendHistory,
|
|
30
84
|
pendingPipelines,
|
|
31
85
|
handleAutonomousMentions,
|
|
86
|
+
saveProjectMessage,
|
|
32
87
|
checkWaveQualityGate,
|
|
33
88
|
failPipelineOnQualityGate,
|
|
34
89
|
savePipelineState,
|
|
@@ -40,6 +95,25 @@ export function initWsRouter(deps) {
|
|
|
40
95
|
autonomousPmLoopSessions
|
|
41
96
|
} = deps;
|
|
42
97
|
|
|
98
|
+
function scheduleReconnect() {
|
|
99
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
100
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, ... capped at 30s
|
|
101
|
+
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
102
|
+
reconnectAttempts++;
|
|
103
|
+
console.log(`[crew-lead] RT reconnecting in ${delay}ms (attempt ${reconnectAttempts})`);
|
|
104
|
+
reconnectTimer = setTimeout(connectRT, delay);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function cleanupConnection() {
|
|
108
|
+
if (crewLeadHeartbeat) { clearInterval(crewLeadHeartbeat); crewLeadHeartbeat = null; }
|
|
109
|
+
setRtPublish(null);
|
|
110
|
+
// Close old WebSocket and remove all listeners to prevent leaks
|
|
111
|
+
if (currentWs) {
|
|
112
|
+
try { currentWs.removeAllListeners(); currentWs.close(); } catch {}
|
|
113
|
+
currentWs = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
43
117
|
function connectRT() {
|
|
44
118
|
if (isConnecting) {
|
|
45
119
|
console.log("[crew-lead] Already connecting to RT, skipping duplicate call");
|
|
@@ -51,17 +125,27 @@ export function initWsRouter(deps) {
|
|
|
51
125
|
reconnectTimer = null;
|
|
52
126
|
}
|
|
53
127
|
|
|
128
|
+
// Clean up any previous connection before creating a new one (#1, #8)
|
|
129
|
+
cleanupConnection();
|
|
130
|
+
|
|
54
131
|
isConnecting = true;
|
|
132
|
+
const thisConnId = ++connectionId;
|
|
55
133
|
const ws = new WebSocket(RT_URL);
|
|
134
|
+
currentWs = ws;
|
|
56
135
|
|
|
57
136
|
ws.on("open", () => {
|
|
137
|
+
if (thisConnId !== connectionId) return; // stale (#7)
|
|
58
138
|
console.log("[crew-lead] RT socket open");
|
|
59
139
|
isConnecting = false;
|
|
60
140
|
});
|
|
61
141
|
|
|
62
142
|
ws.on("message", (raw) => {
|
|
143
|
+
if (thisConnId !== connectionId) return; // stale (#7)
|
|
63
144
|
let p;
|
|
64
|
-
try { p = JSON.parse(raw.toString()); } catch {
|
|
145
|
+
try { p = JSON.parse(raw.toString()); } catch (parseErr) {
|
|
146
|
+
console.warn(`[crew-lead] RT message parse failed: ${parseErr.message} (${raw.toString().slice(0, 80)})`); // #15
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
65
149
|
|
|
66
150
|
if (p.type === "server.hello") {
|
|
67
151
|
ws.send(JSON.stringify({ type: "hello", agent: "crew-lead", token: RT_TOKEN }));
|
|
@@ -69,10 +153,27 @@ export function initWsRouter(deps) {
|
|
|
69
153
|
}
|
|
70
154
|
if (p.type === "hello.ack") {
|
|
71
155
|
ws.send(JSON.stringify({ type: "subscribe", channels: ["done", "events", "command", "issues", "status"] }));
|
|
156
|
+
reconnectAttempts = 0; // Reset backoff on successful connection (#12)
|
|
72
157
|
|
|
158
|
+
// rtPublish always uses currentWs, not a captured closure (#6)
|
|
73
159
|
setRtPublish(({ channel, type, to, payload }) => {
|
|
74
160
|
const taskId = crypto.randomUUID();
|
|
75
|
-
|
|
161
|
+
const activeWs = currentWs;
|
|
162
|
+
if (!activeWs || activeWs.readyState !== WebSocket.OPEN) {
|
|
163
|
+
console.error(`[crew-lead] RT ws not open (state=${activeWs?.readyState}) — triggering reconnect`);
|
|
164
|
+
setRtPublish(null);
|
|
165
|
+
scheduleReconnect();
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
activeWs.send(JSON.stringify({ type: "publish", channel, messageType: type, to, taskId, priority: "high", payload }));
|
|
170
|
+
} catch (sendErr) {
|
|
171
|
+
console.error(`[crew-lead] RT ws.send failed (${sendErr.message}) — triggering reconnect`);
|
|
172
|
+
setRtPublish(null);
|
|
173
|
+
try { activeWs.close(); } catch {}
|
|
174
|
+
scheduleReconnect();
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
76
177
|
return taskId;
|
|
77
178
|
});
|
|
78
179
|
|
|
@@ -80,16 +181,42 @@ export function initWsRouter(deps) {
|
|
|
80
181
|
setTimeout(resumePipelines, 2000);
|
|
81
182
|
startBackgroundLoop();
|
|
82
183
|
|
|
184
|
+
// Heartbeat with failure detection (#3, #5, #10)
|
|
83
185
|
if (crewLeadHeartbeat) clearInterval(crewLeadHeartbeat);
|
|
186
|
+
let missedHeartbeats = 0;
|
|
84
187
|
crewLeadHeartbeat = setInterval(() => {
|
|
188
|
+
if (thisConnId !== connectionId) {
|
|
189
|
+
clearInterval(crewLeadHeartbeat);
|
|
190
|
+
crewLeadHeartbeat = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
85
193
|
try {
|
|
194
|
+
if (!currentWs || currentWs.readyState !== WebSocket.OPEN) {
|
|
195
|
+
missedHeartbeats++;
|
|
196
|
+
console.warn(`[crew-lead] Heartbeat skipped — ws not open (missed=${missedHeartbeats})`);
|
|
197
|
+
if (missedHeartbeats >= 3) {
|
|
198
|
+
console.error(`[crew-lead] 3 missed heartbeats — triggering reconnect`);
|
|
199
|
+
cleanupConnection();
|
|
200
|
+
scheduleReconnect();
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
missedHeartbeats = 0;
|
|
86
205
|
const taskId = crypto.randomUUID();
|
|
87
|
-
|
|
206
|
+
currentWs.send(JSON.stringify({
|
|
88
207
|
type: "publish", channel: "status", messageType: "agent.heartbeat",
|
|
89
208
|
to: "broadcast", taskId, priority: "low",
|
|
90
209
|
payload: { agent: "crew-lead", ts: new Date().toISOString() },
|
|
91
210
|
}));
|
|
92
|
-
} catch {
|
|
211
|
+
} catch (hbErr) {
|
|
212
|
+
console.warn(`[crew-lead] Heartbeat send failed: ${hbErr.message}`);
|
|
213
|
+
missedHeartbeats++;
|
|
214
|
+
if (missedHeartbeats >= 3) {
|
|
215
|
+
console.error(`[crew-lead] 3 heartbeat failures — triggering reconnect`);
|
|
216
|
+
cleanupConnection();
|
|
217
|
+
scheduleReconnect();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
93
220
|
}, 30000);
|
|
94
221
|
return;
|
|
95
222
|
}
|
|
@@ -103,7 +230,10 @@ export function initWsRouter(deps) {
|
|
|
103
230
|
|
|
104
231
|
if (p.type === "message" && p.envelope) {
|
|
105
232
|
const env = p.envelope;
|
|
106
|
-
if (env.id)
|
|
233
|
+
if (env.id) {
|
|
234
|
+
try { ws.send(JSON.stringify({ type: "ack", messageId: env.id, status: "received" })); }
|
|
235
|
+
catch (ackErr) { console.warn(`[crew-lead] Ack send failed for ${env.id}: ${ackErr.message}`); }
|
|
236
|
+
}
|
|
107
237
|
|
|
108
238
|
const from = env.from || env.sender_agent_id || env.payload?.source || "";
|
|
109
239
|
const msgType = env.messageType || env.type || "";
|
|
@@ -154,8 +284,22 @@ export function initWsRouter(deps) {
|
|
|
154
284
|
emitTaskLifecycle("failed", { taskId: failedTaskId, agentId: failedAgent, taskType: "task", error: { message: errMsg } });
|
|
155
285
|
const dispatch = pendingDispatches.get(failedTaskId);
|
|
156
286
|
if (dispatch && RATE_LIMIT_PATTERN.test(errMsg)) {
|
|
157
|
-
const fallback = getRateLimitFallback(failedAgent);
|
|
158
287
|
const targetSession = dispatch.sessionId || "owner";
|
|
288
|
+
const currentEngine = inferDispatchEngine({ ...dispatch, engineUsed: env.payload?.engineUsed || dispatch.engineUsed }, errMsg);
|
|
289
|
+
const engineFallbackMeta = CODER_AGENT_RE.test(failedAgent)
|
|
290
|
+
? buildEngineFallbackMeta(dispatch, currentEngine, "rate-limit-engine-fallback")
|
|
291
|
+
: null;
|
|
292
|
+
if (engineFallbackMeta) {
|
|
293
|
+
pendingDispatches.delete(failedTaskId);
|
|
294
|
+
const newTaskId = dispatchTask(failedAgent, dispatch.task, targetSession, engineFallbackMeta);
|
|
295
|
+
if (newTaskId) {
|
|
296
|
+
appendHistory("default", targetSession, "system", `[crew-lead] ${failedAgent} hit rate limit on ${currentEngine || "current engine"} (${errMsg.slice(0, 80)}). Re-dispatched same task on ${engineFallbackMeta.engineFallbackTo}.`);
|
|
297
|
+
broadcastSSE({ type: "agent_reply", from: "crew-lead", content: `Rate limit: retried ${failedAgent} on ${engineFallbackMeta.engineFallbackTo}.`, sessionId: targetSession, taskId: failedTaskId, ts: Date.now() });
|
|
298
|
+
console.log(`[crew-lead] Rate limit engine fallback: ${failedAgent} ${currentEngine || "unknown"} → ${engineFallbackMeta.engineFallbackTo}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
const fallback = getRateLimitFallback(failedAgent);
|
|
159
303
|
if (fallback !== failedAgent) {
|
|
160
304
|
pendingDispatches.delete(failedTaskId);
|
|
161
305
|
const newTaskId = dispatchTask(fallback, dispatch.task, targetSession, { ...dispatch, pipelineId: dispatch.pipelineId, waveIndex: dispatch.waveIndex });
|
|
@@ -183,12 +327,10 @@ export function initWsRouter(deps) {
|
|
|
183
327
|
setTimeout(() => pendingDispatches.delete(taskId), 600_000);
|
|
184
328
|
}
|
|
185
329
|
|
|
186
|
-
const _autoRetryKey = `_question_retried_${taskId}`;
|
|
187
330
|
const _askedQuestion = /(?:would you like|shall i|should i|do you want|want me to|may i|can i proceed|would it help|do you need|is that correct|shall we|ready to proceed|would you prefer|let me know|please (?:confirm|clarify|specify|advise))\??/i.test(content);
|
|
188
331
|
const _didWork = /@@WRITE_FILE|@@RUN_CMD|wrote|created|updated|fixed|patched|done\.|complete/i.test(content);
|
|
189
|
-
if (_askedQuestion && !_didWork && !pendingPipelines.has(dispatch?.pipelineId) && !
|
|
190
|
-
|
|
191
|
-
setTimeout(() => { delete global[_autoRetryKey]; }, 600_000);
|
|
332
|
+
if (_askedQuestion && !_didWork && !pendingPipelines.has(dispatch?.pipelineId) && !dispatch?._questionRetried) {
|
|
333
|
+
if (dispatch) dispatch._questionRetried = true;
|
|
192
334
|
const _originalTask = dispatch?.task || "";
|
|
193
335
|
const _retryTask = (_originalTask.slice(0, 2000) || content.slice(0, 500)) +
|
|
194
336
|
"\n\nDo NOT ask for permission or confirmation. Proceed immediately with your best judgment. Just do it.";
|
|
@@ -204,15 +346,13 @@ export function initWsRouter(deps) {
|
|
|
204
346
|
return;
|
|
205
347
|
}
|
|
206
348
|
|
|
207
|
-
const
|
|
208
|
-
const _isCoderAgent = /crew-coder|crew-frontend|crew-fixer|crew-ml|crew-coder-back|crew-coder-front/.test(from);
|
|
349
|
+
const _isCoderAgent = CODER_AGENT_RE.test(from);
|
|
209
350
|
const _returnedPlan = !_didWork && content.length > 300 && (
|
|
210
351
|
/##\s+(component|feature|file structure|design|breakdown|overview|plan|approach|implementation plan|technical spec)/i.test(content) ||
|
|
211
352
|
/here'?s? (?:the|my|a|what|how)/i.test(content.slice(0, 200))
|
|
212
353
|
);
|
|
213
|
-
if (_isCoderAgent && _returnedPlan && !
|
|
214
|
-
|
|
215
|
-
setTimeout(() => { delete global[_planRetryKey]; }, 600_000);
|
|
354
|
+
if (_isCoderAgent && _returnedPlan && !dispatch?._planRetried) {
|
|
355
|
+
if (dispatch) dispatch._planRetried = true;
|
|
216
356
|
const _originalTask = dispatch?.task || "";
|
|
217
357
|
const _retryTask = `STOP PLANNING. Your last response was a plan/analysis with no code written.\n\nOriginal task: ${_originalTask.slice(0, 1500)}\n\nNow WRITE THE CODE. Use @@WRITE_FILE for every file. Do not describe what you will do — do it.`;
|
|
218
358
|
console.log(`[crew-lead] Agent ${from} returned a plan instead of code — auto-retrying`);
|
|
@@ -229,17 +369,20 @@ export function initWsRouter(deps) {
|
|
|
229
369
|
return;
|
|
230
370
|
}
|
|
231
371
|
|
|
232
|
-
const _bailRetryKey = `_bail_retried_${taskId}`;
|
|
233
372
|
const _bailed = /couldn'?t complete|could not complete|i'?m sorry[,.]? but|i was unable to|i'?m unable to|session (?:limit|ended|expired)|ran out of|context (?:limit|window)|i (?:apologize|regret)|partial(?:ly)? complete|not (?:all|every|fully) (?:changes?|tasks?|items?|fixes?)/i.test(content);
|
|
234
|
-
if (_bailed && !
|
|
235
|
-
|
|
236
|
-
setTimeout(() => { delete global[_bailRetryKey]; }, 600_000);
|
|
373
|
+
if (_bailed && !dispatch?._bailRetried) {
|
|
374
|
+
if (dispatch) dispatch._bailRetried = true;
|
|
237
375
|
const _originalTask = dispatch?.task || "";
|
|
238
|
-
const
|
|
376
|
+
const currentEngine = inferDispatchEngine(dispatch, content);
|
|
377
|
+
const engineFallbackMeta = _isCoderAgent
|
|
378
|
+
? buildEngineFallbackMeta(dispatch, currentEngine, "auto-retry-bail")
|
|
379
|
+
: null;
|
|
380
|
+
const fallbackAgent = engineFallbackMeta ? from : (_isCoderAgent ? from : (getRateLimitFallback(from) || from));
|
|
239
381
|
const _retryTask = `Your previous attempt at this task was incomplete. You said you couldn't finish.\n\nOriginal task:\n${_originalTask.slice(0, 2000)}\n\nDo not apologize. Do not explain why you couldn't finish. Just complete the remaining work now. Use @@WRITE_FILE for every file you change. If the task is too large, complete the most critical items first.`;
|
|
240
|
-
console.log(`[crew-lead] Agent ${from} bailed out mid-task — auto-retrying with ${fallbackAgent}`);
|
|
241
|
-
appendHistory("default", targetSession, "system", `${from} bailed mid-task — auto-retrying with ${fallbackAgent}.`);
|
|
382
|
+
console.log(`[crew-lead] Agent ${from} bailed out mid-task — auto-retrying with ${engineFallbackMeta?.engineFallbackTo || fallbackAgent}`);
|
|
383
|
+
appendHistory("default", targetSession, "system", `${from} bailed mid-task — auto-retrying with ${engineFallbackMeta?.engineFallbackTo || fallbackAgent}.`);
|
|
242
384
|
dispatchTask(fallbackAgent, _retryTask, targetSession, {
|
|
385
|
+
...(engineFallbackMeta || {}),
|
|
243
386
|
...(dispatch?.pipelineId ? { pipelineId: dispatch.pipelineId } : {}),
|
|
244
387
|
...(dispatch?.projectDir ? { projectDir: dispatch.projectDir } : {}),
|
|
245
388
|
originProjectId: dispatch?.originProjectId,
|
|
@@ -283,6 +426,31 @@ export function initWsRouter(deps) {
|
|
|
283
426
|
}
|
|
284
427
|
|
|
285
428
|
const originChannel = dispatch?.originChannel || dispatch?.originProjectId || null;
|
|
429
|
+
|
|
430
|
+
// Persist agent result to project messages so swarm chat history is complete
|
|
431
|
+
if (originChannel && saveProjectMessage) {
|
|
432
|
+
try {
|
|
433
|
+
saveProjectMessage(originChannel, {
|
|
434
|
+
source: "agent",
|
|
435
|
+
role: "assistant",
|
|
436
|
+
content: content.slice(0, 8000),
|
|
437
|
+
agent: from,
|
|
438
|
+
threadId: dispatch?.originThreadId || `${originChannel}:${targetSession}`,
|
|
439
|
+
parentId: dispatch?.originMessageId || null,
|
|
440
|
+
metadata: {
|
|
441
|
+
agentName: from,
|
|
442
|
+
autonomous: true,
|
|
443
|
+
engineUsed: env.payload?.engineUsed || null,
|
|
444
|
+
durationMs: dispatch?.ts ? Date.now() - dispatch.ts : null,
|
|
445
|
+
triggeredBy: dispatch?.triggeredBy || "dispatch",
|
|
446
|
+
taskId,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
} catch (e) {
|
|
450
|
+
console.warn(`[crew-lead] Failed to save agent result to project messages: ${e.message}`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
286
454
|
if (originChannel) {
|
|
287
455
|
void handleAutonomousMentions({
|
|
288
456
|
message: { content },
|
|
@@ -293,6 +461,9 @@ export function initWsRouter(deps) {
|
|
|
293
461
|
projectDir: dispatch?.projectDir || null,
|
|
294
462
|
originMessageId: dispatch?.originMessageId || null,
|
|
295
463
|
originThreadId: dispatch?.originThreadId || `${originChannel}:${targetSession}`,
|
|
464
|
+
appendToChatHistory: (entry) => {
|
|
465
|
+
appendHistory("default", targetSession, "system", entry.content || String(entry));
|
|
466
|
+
},
|
|
296
467
|
broadcastSSE,
|
|
297
468
|
}).catch((err) => {
|
|
298
469
|
console.warn(`[crew-lead] Autonomous mention routing failed for ${from}: ${err.message}`);
|
|
@@ -325,6 +496,27 @@ export function initWsRouter(deps) {
|
|
|
325
496
|
}
|
|
326
497
|
}
|
|
327
498
|
|
|
499
|
+
// Parse @@DISPATCH markers from any agent result (not just crew-pm).
|
|
500
|
+
// This lets crew-orchestrator (and others) fan out tasks via @@DISPATCH
|
|
501
|
+
// even when running on direct-llm without CLI tools.
|
|
502
|
+
if (from !== "crew-pm" && content.includes("@@DISPATCH")) {
|
|
503
|
+
const agentDispatches = parseDispatches(content);
|
|
504
|
+
for (const d of agentDispatches) {
|
|
505
|
+
const ok = dispatchTask(d.agent, d, targetSession, {
|
|
506
|
+
originProjectId: dispatch?.originProjectId || dispatch?.projectId || "general",
|
|
507
|
+
originChannel: dispatch?.originChannel || dispatch?.projectId || "general",
|
|
508
|
+
originThreadId: dispatch?.originThreadId || `${dispatch?.originProjectId || dispatch?.projectId || "general"}:${targetSession}`,
|
|
509
|
+
originMessageId: dispatch?.originMessageId || null,
|
|
510
|
+
projectDir: d.projectDir || dispatch?.projectDir || null,
|
|
511
|
+
triggeredBy: `${from}-dispatch`,
|
|
512
|
+
});
|
|
513
|
+
if (ok) {
|
|
514
|
+
console.log(`[crew-lead] ${from} dispatched to ${d.agent}: "${(d.task || "").slice(0, 120)}"`);
|
|
515
|
+
appendHistory("default", targetSession, "system", `${from} dispatched to ${d.agent}: "${(d.task || "").slice(0, 120)}".`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
328
520
|
if (from === "crew-pm") {
|
|
329
521
|
const pipelineSpec = parsePipeline(content);
|
|
330
522
|
if (pipelineSpec) {
|
|
@@ -410,17 +602,17 @@ export function initWsRouter(deps) {
|
|
|
410
602
|
});
|
|
411
603
|
|
|
412
604
|
ws.on("close", () => {
|
|
413
|
-
|
|
605
|
+
if (thisConnId !== connectionId) return; // stale close event
|
|
606
|
+
cleanupConnection();
|
|
414
607
|
isConnecting = false;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
418
|
-
reconnectTimer = setTimeout(connectRT, 5000);
|
|
608
|
+
console.log("[crew-lead] RT disconnected");
|
|
609
|
+
scheduleReconnect();
|
|
419
610
|
});
|
|
420
611
|
|
|
421
612
|
ws.on("error", (e) => {
|
|
422
613
|
console.error("[crew-lead] RT socket error:", e.message);
|
|
423
614
|
isConnecting = false;
|
|
615
|
+
// close event will fire after error — reconnect happens there
|
|
424
616
|
});
|
|
425
617
|
}
|
|
426
618
|
|
package/lib/engines/crew-cli.mjs
CHANGED
|
@@ -111,7 +111,7 @@ export async function runCrewCLITask(prompt, payload = {}) {
|
|
|
111
111
|
const engine = await getEngine();
|
|
112
112
|
|
|
113
113
|
// Create sandbox pointed at the project directory
|
|
114
|
-
const sandbox = new engine.Sandbox(
|
|
114
|
+
const sandbox = new engine.Sandbox(projectDir);
|
|
115
115
|
|
|
116
116
|
// Run the agentic executor directly — THINK→ACT→OBSERVE loop with 34 tools
|
|
117
117
|
const result = await engine.runAgenticWorker(prompt, sandbox, {
|
|
@@ -7,6 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import os from "node:os";
|
|
9
9
|
import { loadAgentList as _defaultLoadAgentList } from "../runtime/config.mjs";
|
|
10
|
+
import { isCodingTask } from "../agents/dispatch.mjs";
|
|
10
11
|
|
|
11
12
|
const ENGINES_BUNDLED_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "engines");
|
|
12
13
|
const ENGINES_USER_DIR = path.join(os.homedir(), ".crewswarm", "engines");
|
|
@@ -153,7 +154,9 @@ function evaluateShouldUse(engineDef, payload, incomingType) {
|
|
|
153
154
|
* Select engine: payload flags → payload runtime → per-agent crewswarm.json → fallback rules
|
|
154
155
|
*/
|
|
155
156
|
export function selectEngine(payload, incomingType) {
|
|
156
|
-
|
|
157
|
+
const prompt = payload?.prompt || payload?.task || payload?.message || "";
|
|
158
|
+
|
|
159
|
+
// PRIORITY 1: Payload flags (from enriched task) — always respected regardless of prompt content
|
|
157
160
|
const explicitEngines = [
|
|
158
161
|
{ key: 'useCodex', id: 'codex' },
|
|
159
162
|
{ key: 'useCursor', id: 'cursor' },
|
|
@@ -186,7 +189,9 @@ export function selectEngine(payload, incomingType) {
|
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
// PRIORITY 2.3: Per-agent engine from crewswarm.json (`engine` + use* flags)
|
|
192
|
+
// PRIORITY 2.3: Per-agent engine from crewswarm.json (`engine` + use* flags) — always respected.
|
|
193
|
+
// Agents that explicitly opt into an engine (useGeminiCli, useClaudeCode, etc.) should always
|
|
194
|
+
// use it, even for non-coding prompts like "reply with exactly X".
|
|
190
195
|
const preferredFromCfg = resolveAgentPreferredEngineId(payload);
|
|
191
196
|
if (preferredFromCfg) {
|
|
192
197
|
const preferredEngine = _engines.find((e) => e.id === preferredFromCfg);
|
|
@@ -195,7 +200,13 @@ export function selectEngine(payload, incomingType) {
|
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
202
|
|
|
198
|
-
// PRIORITY 2.5:
|
|
203
|
+
// PRIORITY 2.5: Chat messages and non-coding tasks always use direct LLM for fallback routing.
|
|
204
|
+
// Only applies when no explicit engine was configured above.
|
|
205
|
+
if (!isCodingTask(incomingType, prompt, payload)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// PRIORITY 2.6: If agent explicitly opted out of ALL CLI engines, skip fallback — use direct LLM
|
|
199
210
|
const agentId = String(payload?.agentId || payload?.agent || "").toLowerCase();
|
|
200
211
|
if (agentId && _loadAgentList) {
|
|
201
212
|
try {
|
|
@@ -1612,6 +1612,7 @@ export async function handleRealtimeEnvelope(envelope, client, bridge) {
|
|
|
1612
1612
|
payload: {
|
|
1613
1613
|
source: CREWSWARM_RT_AGENT,
|
|
1614
1614
|
error: message,
|
|
1615
|
+
engineUsed: typeof engineUsed !== "undefined" ? engineUsed : null,
|
|
1615
1616
|
idempotencyKey: dispatchKey,
|
|
1616
1617
|
attempt: dispatchAttempt,
|
|
1617
1618
|
},
|
package/lib/engines/runners.mjs
CHANGED
|
@@ -27,6 +27,19 @@ function which(bin) {
|
|
|
27
27
|
try { execSync(`which ${bin}`, { stdio: "ignore" }); return true; } catch { return false; }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function isClaudeOauthAuthenticated() {
|
|
31
|
+
try {
|
|
32
|
+
const output = execSync(`${CLAUDE_CODE_BIN} auth status`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] })
|
|
33
|
+
.trim()
|
|
34
|
+
.toLowerCase();
|
|
35
|
+
if (!output) return false;
|
|
36
|
+
if (/"loggedin"\s*:\s*true/.test(output)) return true;
|
|
37
|
+
return output.includes("logged in");
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
30
43
|
// ── Module-level deps (injected via initRunners) ───────────────────────────
|
|
31
44
|
let _getAgentOpenCodeConfig = () => ({ enabled: false, useCursorCli: false, cursorCliModel: null, claudeCodeModel: null });
|
|
32
45
|
let _loadAgentList = () => [];
|
|
@@ -1076,7 +1089,7 @@ export async function runDockerSandboxTask(prompt, payload = {}) {
|
|
|
1076
1089
|
|
|
1077
1090
|
let innerArgs;
|
|
1078
1091
|
if (innerEngine === "opencode") {
|
|
1079
|
-
innerArgs = ["opencode", "run", titledPrompt, "--model", process.env.CREWSWARM_OPENCODE_MODEL || "
|
|
1092
|
+
innerArgs = ["opencode", "run", titledPrompt, "--model", process.env.CREWSWARM_OPENCODE_MODEL || "openai/gpt-5.4"];
|
|
1080
1093
|
} else if (innerEngine === "codex") {
|
|
1081
1094
|
innerArgs = ["codex", "exec", "--sandbox", "workspace-write", "--json", titledPrompt];
|
|
1082
1095
|
} else {
|
|
@@ -1181,6 +1194,7 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1181
1194
|
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
1182
1195
|
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
1183
1196
|
|
|
1197
|
+
const emptyMcpCfg = path.join(os.homedir(), ".crewswarm", "config", "empty-mcp.json");
|
|
1184
1198
|
const args = [
|
|
1185
1199
|
"-p",
|
|
1186
1200
|
"--dangerously-skip-permissions",
|
|
@@ -1205,21 +1219,31 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1205
1219
|
}
|
|
1206
1220
|
}
|
|
1207
1221
|
|
|
1208
|
-
//
|
|
1209
|
-
args.push(
|
|
1222
|
+
// Skip user MCP servers to avoid 30s+ init hangs
|
|
1223
|
+
args.push("--strict-mcp-config", "--mcp-config", emptyMcpCfg);
|
|
1224
|
+
// -- separates flags from prompt (--mcp-config is variadic and eats positional args)
|
|
1225
|
+
args.push("--", titledPrompt);
|
|
1210
1226
|
|
|
1211
1227
|
if (!which(CLAUDE_CODE_BIN)) {
|
|
1212
1228
|
throw new Error(`Claude Code CLI not found: "${CLAUDE_CODE_BIN}". Install with: npm i -g @anthropic-ai/claude-code`);
|
|
1213
1229
|
}
|
|
1230
|
+
if (!isClaudeOauthAuthenticated()) {
|
|
1231
|
+
console.error(`[ClaudeCode:${agentId}] auth preflight reported logged out; continuing and letting Claude decide at runtime.`);
|
|
1232
|
+
}
|
|
1214
1233
|
|
|
1215
1234
|
console.error(`[ClaudeCode:${agentId}] Running: ${CLAUDE_CODE_BIN} -p --dangerously-skip-permissions (cwd=${projectDir})`);
|
|
1216
1235
|
|
|
1217
1236
|
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
1218
1237
|
payload: { agent: agentId, model: model || "claude/auto", ts: Date.now() } });
|
|
1219
1238
|
|
|
1239
|
+
const childEnv = { ...process.env };
|
|
1240
|
+
delete childEnv.ANTHROPIC_API_KEY;
|
|
1241
|
+
delete childEnv.CLAUDECODE;
|
|
1242
|
+
delete childEnv.CLAUDE_CODE;
|
|
1243
|
+
|
|
1220
1244
|
const child = spawn(CLAUDE_CODE_BIN, args, {
|
|
1221
1245
|
cwd: projectDir,
|
|
1222
|
-
env:
|
|
1246
|
+
env: childEnv,
|
|
1223
1247
|
stdio: ["ignore", "pipe", "pipe"], // Changed from "pipe" to "ignore" for stdin since we use args
|
|
1224
1248
|
});
|
|
1225
1249
|
|
|
@@ -33,5 +33,5 @@ export function filterGeminiPassthroughTextChunk(engine, text) {
|
|
|
33
33
|
if (engine !== "gemini" && engine !== "gemini-cli") return String(text ?? "");
|
|
34
34
|
const s = String(text ?? "");
|
|
35
35
|
if (!s) return s;
|
|
36
|
-
return s.split("\n").filter((ln) => !shouldSkipGeminiPassthroughLine(ln)).join("\n");
|
|
36
|
+
return s.split("\n").filter((ln) => !shouldSkipGeminiPassthroughLine(ln)).map((ln) => ln.replace(/\r$/, "")).join("\n");
|
|
37
37
|
}
|
|
@@ -198,7 +198,8 @@ export function formatSearchResults(searchResults, maxChars = 4000) {
|
|
|
198
198
|
*/
|
|
199
199
|
export async function findFiles(pattern, projectDir = process.cwd()) {
|
|
200
200
|
try {
|
|
201
|
-
const
|
|
201
|
+
const safePattern = pattern.replace(/["`$\\]/g, '\\$&');
|
|
202
|
+
const output = execSync(`rg --files | rg "${safePattern}"`, {
|
|
202
203
|
cwd: projectDir,
|
|
203
204
|
encoding: 'utf8',
|
|
204
205
|
stdio: ['pipe', 'pipe', 'ignore']
|
|
@@ -247,7 +248,7 @@ export async function findFunctions(name, projectDir = process.cwd()) {
|
|
|
247
248
|
// Escape special regex characters in name
|
|
248
249
|
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
249
250
|
const pattern = `(function|const|let|export)\\s+${escapedName}`;
|
|
250
|
-
return await searchPattern(pattern, projectDir, { fileTypes: ['js', 'ts'
|
|
251
|
+
return await searchPattern(pattern, projectDir, { fileTypes: ['js', 'ts'] });
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
/**
|
|
@@ -255,5 +256,5 @@ export async function findFunctions(name, projectDir = process.cwd()) {
|
|
|
255
256
|
*/
|
|
256
257
|
export async function findClasses(name, projectDir = process.cwd()) {
|
|
257
258
|
const pattern = `class\\s+${name}\\s*[{(]`;
|
|
258
|
-
return await searchPattern(pattern, projectDir, { fileTypes: ['js', 'ts'
|
|
259
|
+
return await searchPattern(pattern, projectDir, { fileTypes: ['js', 'ts'] });
|
|
259
260
|
}
|
|
@@ -36,6 +36,19 @@ try {
|
|
|
36
36
|
// Shared memory root (env var or default)
|
|
37
37
|
export const CREW_MEMORY_DIR = process.env.CREW_MEMORY_DIR || path.join(os.homedir(), '.crewswarm', 'shared-memory');
|
|
38
38
|
|
|
39
|
+
// Cache project-messages-rag module to avoid dynamic import() on every call
|
|
40
|
+
let _projectMessagesRag = null;
|
|
41
|
+
async function getProjectMessagesRag() {
|
|
42
|
+
if (_projectMessagesRag === null) {
|
|
43
|
+
try {
|
|
44
|
+
_projectMessagesRag = await import('../chat/project-messages-rag.mjs');
|
|
45
|
+
} catch {
|
|
46
|
+
_projectMessagesRag = false; // mark as unavailable
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return _projectMessagesRag || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
/**
|
|
40
53
|
* Get or create AgentKeeper instance for task memory.
|
|
41
54
|
* @param {string} projectDir - Project root directory
|
|
@@ -143,8 +156,8 @@ export async function recallMemoryContext(projectDir, query, options = {}) {
|
|
|
143
156
|
// This lets agents see past discussions about the same topic
|
|
144
157
|
if (options.projectId) {
|
|
145
158
|
try {
|
|
146
|
-
const
|
|
147
|
-
const conversationContext = getConversationContext(options.projectId, query, 3);
|
|
159
|
+
const ragModule = await getProjectMessagesRag();
|
|
160
|
+
const conversationContext = ragModule?.getConversationContext(options.projectId, query, 3);
|
|
148
161
|
|
|
149
162
|
if (conversationContext) {
|
|
150
163
|
memoryContext += (memoryContext ? '\n\n' : '') + conversationContext;
|
|
@@ -264,21 +277,21 @@ export async function migrateBrainToMemory(brainPath, agentId = 'crew-lead') {
|
|
|
264
277
|
|
|
265
278
|
// Extract tags from markdown list items
|
|
266
279
|
const tags = [];
|
|
267
|
-
let
|
|
280
|
+
let factContent = trimmed;
|
|
268
281
|
|
|
269
282
|
// Extract date tags (YYYY-MM-DD)
|
|
270
|
-
const dateMatch =
|
|
283
|
+
const dateMatch = factContent.match(/\b(\d{4}-\d{2}-\d{2})\b/);
|
|
271
284
|
if (dateMatch) tags.push('dated', dateMatch[1]);
|
|
272
|
-
|
|
285
|
+
|
|
273
286
|
// Extract agent mentions
|
|
274
|
-
const agentMatch =
|
|
287
|
+
const agentMatch = factContent.match(/\b(crew-\w+)\b/);
|
|
275
288
|
if (agentMatch) tags.push('agent', agentMatch[1]);
|
|
276
|
-
|
|
289
|
+
|
|
277
290
|
// Determine criticality (heuristic: lines with CRITICAL, ERROR, WARNING, or !)
|
|
278
|
-
const critical = /\b(CRITICAL|ERROR|WARNING|!)\b/i.test(
|
|
279
|
-
|
|
291
|
+
const critical = /\b(CRITICAL|ERROR|WARNING|!)\b/i.test(factContent) || factContent.includes('MUST') || factContent.includes('NEVER');
|
|
292
|
+
|
|
280
293
|
try {
|
|
281
|
-
memory.remember(
|
|
294
|
+
memory.remember(factContent, {
|
|
282
295
|
critical,
|
|
283
296
|
tags: tags.length > 0 ? tags : ['brain-migration'],
|
|
284
297
|
provider: 'brain-migration'
|
package/lib/pipeline/manager.mjs
CHANGED
|
@@ -20,7 +20,7 @@ function tryRead(p) {
|
|
|
20
20
|
|
|
21
21
|
export const pendingProjects = new Map();
|
|
22
22
|
|
|
23
|
-
export function initPipelineManager({ dashboard, broadcastSSE, appendHistory, handleChat, loadConfig }) {
|
|
23
|
+
export function initPipelineManager({ dashboard, broadcastSSE, appendHistory, handleChat, loadConfig } = {}) {
|
|
24
24
|
if (dashboard !== undefined) _dashboard = dashboard;
|
|
25
25
|
if (broadcastSSE) _broadcastSSE = broadcastSSE;
|
|
26
26
|
if (appendHistory) _appendHistory = appendHistory;
|
|
@@ -502,6 +502,7 @@ export function parseRoadmapPhases(content) {
|
|
|
502
502
|
}
|
|
503
503
|
|
|
504
504
|
export function findNextRoadmapPhase(projectDir) {
|
|
505
|
+
if (!projectDir) return null;
|
|
505
506
|
const roadmapPath = path.join(projectDir, "ROADMAP.md");
|
|
506
507
|
if (!fs.existsSync(roadmapPath)) return null;
|
|
507
508
|
try {
|
package/lib/runtime/config.mjs
CHANGED
|
@@ -185,7 +185,7 @@ export const CREWSWARM_CLAUDE_CODE = (() => {
|
|
|
185
185
|
export const CREWSWARM_OPENCODE_FORCE = process.env.CREWSWARM_OPENCODE_FORCE === "1";
|
|
186
186
|
export const CREWSWARM_OPENCODE_BIN = process.env.CREWSWARM_OPENCODE_BIN || path.join(os.homedir(), ".opencode", "bin", "opencode");
|
|
187
187
|
export const CREWSWARM_OPENCODE_AGENT = process.env.CREWSWARM_OPENCODE_AGENT || "admin";
|
|
188
|
-
export const CREWSWARM_OPENCODE_MODEL = process.env.CREWSWARM_OPENCODE_MODEL || "
|
|
188
|
+
export const CREWSWARM_OPENCODE_MODEL = process.env.CREWSWARM_OPENCODE_MODEL || "openai/gpt-5.4";
|
|
189
189
|
export const CREWSWARM_OPENCODE_FALLBACK_DEFAULT = "groq/llama-3.3-70b-versatile";
|
|
190
190
|
export const CREWSWARM_OPENCODE_TIMEOUT_MS = Number(process.env.CREWSWARM_OPENCODE_TIMEOUT_MS || "300000");
|
|
191
191
|
|