crewswarm 0.9.1 → 0.9.3
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-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -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-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +48 -5
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/bridges/tmux-bridge.mjs +200 -0
- package/lib/cli-process-tracker.mjs +2 -1
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +340 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +53 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +26 -2
- package/lib/runtime/config.mjs +7 -0
- package/lib/runtime/paths.mjs +12 -8
- package/lib/sessions/session-manager.mjs +287 -0
- package/package.json +35 -15
- 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 +74 -0
- package/scripts/dashboard.mjs +560 -70
- 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/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.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.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/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- 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-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.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.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/index.html +0 -6459
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2823
- 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 -803
- 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
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux-bridge adapter — thin wrapper around smux's tmux-bridge CLI
|
|
3
|
+
*
|
|
4
|
+
* Provides agent-to-agent pane communication when running inside a tmux session
|
|
5
|
+
* with smux installed. All functions degrade to no-ops when unavailable.
|
|
6
|
+
*
|
|
7
|
+
* Opt-in: requires $TMUX set, `tmux-bridge` on PATH, and
|
|
8
|
+
* CREWSWARM_TMUX_BRIDGE=1 env var (or tmuxBridge: true in system config).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
12
|
+
|
|
13
|
+
function which(bin) {
|
|
14
|
+
try { execSync(`which ${bin}`, { stdio: "ignore" }); return true; } catch { return false; }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── Detection & caching ─────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
let _available = null;
|
|
20
|
+
let _ownPaneId = null;
|
|
21
|
+
const _resolveCache = new Map(); // label → { paneId, ts }
|
|
22
|
+
const RESOLVE_TTL_MS = 30_000;
|
|
23
|
+
const TMUX_BRIDGE_BIN = process.env.SMUX_BRIDGE_BIN || "tmux-bridge";
|
|
24
|
+
|
|
25
|
+
function exec(args, { timeout = 5000 } = {}) {
|
|
26
|
+
try {
|
|
27
|
+
return execFileSync(TMUX_BRIDGE_BIN, args, {
|
|
28
|
+
encoding: "utf8",
|
|
29
|
+
timeout,
|
|
30
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
31
|
+
}).trim();
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if tmux-bridge is available and opted-in.
|
|
39
|
+
* Result is cached for the process lifetime.
|
|
40
|
+
*/
|
|
41
|
+
export function detect() {
|
|
42
|
+
if (_available !== null) return _available;
|
|
43
|
+
|
|
44
|
+
// Must be inside a tmux session
|
|
45
|
+
if (!process.env.TMUX) {
|
|
46
|
+
_available = false;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Must have tmux-bridge binary
|
|
51
|
+
if (!which(TMUX_BRIDGE_BIN)) {
|
|
52
|
+
_available = false;
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Must be opted-in via env var or config
|
|
57
|
+
const envFlag = process.env.CREWSWARM_TMUX_BRIDGE;
|
|
58
|
+
if (!envFlag || envFlag === "0" || envFlag === "false") {
|
|
59
|
+
_available = false;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_available = true;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get this process's own tmux pane ID.
|
|
69
|
+
* @returns {string|null} e.g. "%3"
|
|
70
|
+
*/
|
|
71
|
+
export function id() {
|
|
72
|
+
if (!detect()) return null;
|
|
73
|
+
if (_ownPaneId) return _ownPaneId;
|
|
74
|
+
_ownPaneId = exec(["id"]);
|
|
75
|
+
return _ownPaneId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Label a pane with an agent ID so other agents can discover it.
|
|
80
|
+
* @param {string} agentId - CrewSwarm agent ID (e.g. "crew-coder")
|
|
81
|
+
* @param {string} [paneId] - Target pane ID. Defaults to own pane.
|
|
82
|
+
* @returns {boolean} success
|
|
83
|
+
*/
|
|
84
|
+
export function label(agentId, paneId) {
|
|
85
|
+
if (!detect()) return false;
|
|
86
|
+
const target = paneId || id();
|
|
87
|
+
if (!target) return false;
|
|
88
|
+
const result = exec(["name", target, agentId]);
|
|
89
|
+
if (result !== null) {
|
|
90
|
+
// Update cache
|
|
91
|
+
_resolveCache.set(agentId, { paneId: target, ts: Date.now() });
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve an agent ID to a tmux pane ID.
|
|
99
|
+
* @param {string} agentId
|
|
100
|
+
* @returns {string|null} pane ID or null
|
|
101
|
+
*/
|
|
102
|
+
export function resolve(agentId) {
|
|
103
|
+
if (!detect()) return null;
|
|
104
|
+
|
|
105
|
+
// Check cache
|
|
106
|
+
const cached = _resolveCache.get(agentId);
|
|
107
|
+
if (cached && (Date.now() - cached.ts) < RESOLVE_TTL_MS) {
|
|
108
|
+
return cached.paneId;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const paneId = exec(["resolve", agentId]);
|
|
112
|
+
if (paneId) {
|
|
113
|
+
_resolveCache.set(agentId, { paneId, ts: Date.now() });
|
|
114
|
+
}
|
|
115
|
+
return paneId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Read the last N lines from an agent's pane.
|
|
120
|
+
* Also satisfies the read-guard requirement for subsequent sends.
|
|
121
|
+
* @param {string} agentId
|
|
122
|
+
* @param {number} [lines=50]
|
|
123
|
+
* @returns {string|null} pane content or null
|
|
124
|
+
*/
|
|
125
|
+
export function read(agentId, lines = 50) {
|
|
126
|
+
if (!detect()) return null;
|
|
127
|
+
const paneId = resolve(agentId);
|
|
128
|
+
if (!paneId) return null;
|
|
129
|
+
const output = exec(["read", paneId, String(lines)]);
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Send text to an agent's pane followed by Enter.
|
|
135
|
+
* Reads the pane before each write command to satisfy tmux-bridge's read-guard
|
|
136
|
+
* (the guard is consumed on every type/keys call).
|
|
137
|
+
* @param {string} agentId
|
|
138
|
+
* @param {string} text
|
|
139
|
+
* @returns {boolean} success
|
|
140
|
+
*/
|
|
141
|
+
export function send(agentId, text) {
|
|
142
|
+
if (!detect()) return false;
|
|
143
|
+
const paneId = resolve(agentId);
|
|
144
|
+
if (!paneId) return false;
|
|
145
|
+
|
|
146
|
+
// Read-guard is consumed on each type/keys call, so read before each one
|
|
147
|
+
exec(["read", paneId, "1"]);
|
|
148
|
+
const typeResult = exec(["type", paneId, text]);
|
|
149
|
+
if (typeResult === null) return false;
|
|
150
|
+
|
|
151
|
+
exec(["read", paneId, "1"]);
|
|
152
|
+
const keyResult = exec(["keys", paneId, "Enter"]);
|
|
153
|
+
return keyResult !== null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* List all tmux panes with their labels and metadata.
|
|
158
|
+
* @returns {Array<{paneId: string, label: string, raw: string}>}
|
|
159
|
+
*/
|
|
160
|
+
export function list() {
|
|
161
|
+
if (!detect()) return [];
|
|
162
|
+
const raw = exec(["list"]);
|
|
163
|
+
if (!raw) return [];
|
|
164
|
+
|
|
165
|
+
// Parse tmux-bridge list output:
|
|
166
|
+
// TARGET SESSION:WIN SIZE PROCESS LABEL CWD
|
|
167
|
+
// %0 crewtest:0 120x29 -zsh crew-coder ~/CrewSwarm
|
|
168
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
169
|
+
// Skip header row (starts with "TARGET")
|
|
170
|
+
return lines
|
|
171
|
+
.filter(line => line.trimStart().startsWith("%"))
|
|
172
|
+
.map(line => {
|
|
173
|
+
const parts = line.split(/\s+/).filter(Boolean);
|
|
174
|
+
return {
|
|
175
|
+
paneId: parts[0] || "",
|
|
176
|
+
session: parts[1] || "",
|
|
177
|
+
size: parts[2] || "",
|
|
178
|
+
process: parts[3] || "",
|
|
179
|
+
label: (parts[4] && parts[4] !== "-") ? parts[4] : "",
|
|
180
|
+
cwd: parts[5] || "",
|
|
181
|
+
raw: line,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clear the resolve cache (useful after pane layout changes).
|
|
188
|
+
*/
|
|
189
|
+
export function clearCache() {
|
|
190
|
+
_resolveCache.clear();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Reset detection state (for testing).
|
|
195
|
+
*/
|
|
196
|
+
export function _reset() {
|
|
197
|
+
_available = null;
|
|
198
|
+
_ownPaneId = null;
|
|
199
|
+
clearCache();
|
|
200
|
+
}
|
|
@@ -160,7 +160,8 @@ export function getActiveProcesses() {
|
|
|
160
160
|
idleFor: now - proc.lastActivity,
|
|
161
161
|
outputLines: proc.outputLines,
|
|
162
162
|
chatId: proc.chatId,
|
|
163
|
-
sessionId: proc.sessionId
|
|
163
|
+
sessionId: proc.sessionId,
|
|
164
|
+
tmuxSessionId: proc.tmuxSessionId || null
|
|
164
165
|
}));
|
|
165
166
|
}
|
|
166
167
|
|
|
@@ -2577,6 +2577,40 @@ Reply with your answers and I'll turn this into a concrete build plan with file
|
|
|
2577
2577
|
...(usedFallback ? { fallbackReason } : {}),
|
|
2578
2578
|
},
|
|
2579
2579
|
});
|
|
2580
|
+
|
|
2581
|
+
// Route @mentions in assistant replies — crew-lead can autonomously dispatch
|
|
2582
|
+
if (replyMentions.length && channelMode) {
|
|
2583
|
+
try {
|
|
2584
|
+
const {
|
|
2585
|
+
handleAutonomousMentions: routeMentions,
|
|
2586
|
+
detectMentionTargets,
|
|
2587
|
+
} = await import("../chat/autonomous-mentions.mjs");
|
|
2588
|
+
const mentionTargets = detectMentionTargets(historyReply);
|
|
2589
|
+
const mentionRoute = classifySharedChatMention(historyReply);
|
|
2590
|
+
if (mentionTargets.length && mentionRoute.mode === "dispatch") {
|
|
2591
|
+
void routeMentions({
|
|
2592
|
+
message: { content: historyReply },
|
|
2593
|
+
sender: "crew-lead",
|
|
2594
|
+
channel: sharedChannel,
|
|
2595
|
+
projectId: sharedChannel,
|
|
2596
|
+
sessionId,
|
|
2597
|
+
projectDir: explicitProjectDir || activeProjectOutputDir || null,
|
|
2598
|
+
originMessageId: assistantMessageId,
|
|
2599
|
+
originThreadId: sharedThreadId,
|
|
2600
|
+
chatHistory: loadProjectMessages(sharedChannel, {
|
|
2601
|
+
limit: 10,
|
|
2602
|
+
threadId: sharedThreadId,
|
|
2603
|
+
excludeDirect: true,
|
|
2604
|
+
}),
|
|
2605
|
+
broadcastSSE: _deps.broadcastSSE,
|
|
2606
|
+
}).catch((err) => {
|
|
2607
|
+
console.warn(`[chat-handler] Assistant mention routing failed: ${err.message}`);
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
} catch (mentionErr) {
|
|
2611
|
+
console.warn(`[chat-handler] Assistant mention dispatch error: ${mentionErr.message}`);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2580
2614
|
} catch (e) {
|
|
2581
2615
|
console.warn(
|
|
2582
2616
|
`[chat-handler] Failed to save assistant message to project store: ${e.message}`,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { shouldSkipGeminiPassthroughLine } from "../gemini-cli-passthrough-noise.mjs";
|
|
21
21
|
import { applyProjectDirToPipelineSteps } from "../dispatch/parsers.mjs";
|
|
22
22
|
import { normalizeProjectDir } from "../runtime/project-dir.mjs";
|
|
23
|
-
import { CREWSWARM_REPO_ROOT } from "../runtime/config.mjs";
|
|
23
|
+
import { CREWSWARM_REPO_ROOT, loadSwarmConfig } from "../runtime/config.mjs";
|
|
24
24
|
import { resolveCursorLaunchSpec } from "../engines/cursor-launcher.mjs";
|
|
25
25
|
|
|
26
26
|
let _deps = {};
|
|
@@ -1087,6 +1087,7 @@ export function createAndStartServer(PORT) {
|
|
|
1087
1087
|
bgConsciousnessIntervalMs,
|
|
1088
1088
|
cursorWavesRef,
|
|
1089
1089
|
claudeCodeRef,
|
|
1090
|
+
tmuxBridgeRef,
|
|
1090
1091
|
} = _deps;
|
|
1091
1092
|
|
|
1092
1093
|
// Debug: verify critical deps
|
|
@@ -3531,6 +3532,9 @@ export function createAndStartServer(PORT) {
|
|
|
3531
3532
|
sessionId: reqSessionId,
|
|
3532
3533
|
injectHistory,
|
|
3533
3534
|
model: reqModel,
|
|
3535
|
+
permissionMode: reqPermissionMode,
|
|
3536
|
+
sandbox: reqSandbox,
|
|
3537
|
+
forceL2: reqForceL2,
|
|
3534
3538
|
} = JSON.parse(body || "{}");
|
|
3535
3539
|
if (!message) {
|
|
3536
3540
|
json(res, 400, { ok: false, error: "message required" });
|
|
@@ -3761,7 +3765,7 @@ export function createAndStartServer(PORT) {
|
|
|
3761
3765
|
"never",
|
|
3762
3766
|
"exec",
|
|
3763
3767
|
"--sandbox",
|
|
3764
|
-
"danger-full-access",
|
|
3768
|
+
reqSandbox || "danger-full-access",
|
|
3765
3769
|
"--skip-git-repo-check",
|
|
3766
3770
|
"--color",
|
|
3767
3771
|
"never",
|
|
@@ -3858,12 +3862,16 @@ export function createAndStartServer(PORT) {
|
|
|
3858
3862
|
"-p",
|
|
3859
3863
|
"--setting-sources",
|
|
3860
3864
|
"user",
|
|
3861
|
-
"--dangerously-skip-permissions",
|
|
3862
3865
|
"--output-format",
|
|
3863
3866
|
"stream-json",
|
|
3864
3867
|
"--verbose",
|
|
3865
3868
|
"--include-partial-messages",
|
|
3866
3869
|
];
|
|
3870
|
+
if (reqPermissionMode) {
|
|
3871
|
+
args.push("--permission-mode", reqPermissionMode);
|
|
3872
|
+
} else {
|
|
3873
|
+
args.push("--dangerously-skip-permissions");
|
|
3874
|
+
}
|
|
3867
3875
|
if (projectDir) args.push("--add-dir", projectDir);
|
|
3868
3876
|
if (reqModel) args.push("--model", reqModel);
|
|
3869
3877
|
// Prefer --resume <session-id> for per-project isolation; fall back to --continue (most recent global)
|
|
@@ -3876,7 +3884,10 @@ export function createAndStartServer(PORT) {
|
|
|
3876
3884
|
} else if (continueSession) {
|
|
3877
3885
|
args.push("--continue");
|
|
3878
3886
|
}
|
|
3879
|
-
|
|
3887
|
+
// Skip user MCP servers to avoid 30s+ init hangs
|
|
3888
|
+
args.push("--strict-mcp-config", "--mcp-config", path.join(os.homedir(), ".crewswarm", "config", "empty-mcp.json"));
|
|
3889
|
+
// -- separates flags from prompt (--mcp-config is variadic and eats positional args)
|
|
3890
|
+
args.push("--", finalMessage);
|
|
3880
3891
|
}
|
|
3881
3892
|
|
|
3882
3893
|
send({ type: "start", engine, message: message.slice(0, 80) });
|
|
@@ -3900,9 +3911,22 @@ export function createAndStartServer(PORT) {
|
|
|
3900
3911
|
const spawnCwd =
|
|
3901
3912
|
engine === "claude" && projectDir ? "/tmp" : projectDir;
|
|
3902
3913
|
|
|
3914
|
+
// Merge crewswarm.json env vars into spawn env (crew-cli L2, model overrides, etc.)
|
|
3915
|
+
const spawnEnv = { ...process.env };
|
|
3916
|
+
try {
|
|
3917
|
+
const _sysCfg = loadSwarmConfig();
|
|
3918
|
+
if (_sysCfg?.env && typeof _sysCfg.env === "object") {
|
|
3919
|
+
for (const [k, v] of Object.entries(_sysCfg.env)) {
|
|
3920
|
+
if (!spawnEnv[k] && v != null) spawnEnv[k] = String(v);
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
} catch {}
|
|
3924
|
+
// Force L2 planner for crew-cli when requested (enhance-prompt planner path)
|
|
3925
|
+
if (reqForceL2) spawnEnv.CREW_FORCE_L2 = "true";
|
|
3926
|
+
|
|
3903
3927
|
const proc = _spawn(bin, args, {
|
|
3904
3928
|
cwd: spawnCwd,
|
|
3905
|
-
env:
|
|
3929
|
+
env: spawnEnv,
|
|
3906
3930
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3907
3931
|
});
|
|
3908
3932
|
let lineBuffer = "";
|
|
@@ -3915,7 +3939,7 @@ export function createAndStartServer(PORT) {
|
|
|
3915
3939
|
let passthroughGracefulEnd = false;
|
|
3916
3940
|
|
|
3917
3941
|
const passthroughTimeoutMs = Number(
|
|
3918
|
-
process.env.CREWSWARM_PASSTHROUGH_TIMEOUT_MS || "
|
|
3942
|
+
process.env.CREWSWARM_PASSTHROUGH_TIMEOUT_MS || "300000",
|
|
3919
3943
|
);
|
|
3920
3944
|
const passthroughWatchdog = setTimeout(() => {
|
|
3921
3945
|
send({
|
|
@@ -4206,15 +4230,33 @@ export function createAndStartServer(PORT) {
|
|
|
4206
4230
|
// crew-cli special handling: extract JSON from output (skipping logs) and send only response field
|
|
4207
4231
|
if (engine === "crew-cli" && fullOutput.trim()) {
|
|
4208
4232
|
try {
|
|
4209
|
-
// crew-cli
|
|
4210
|
-
//
|
|
4211
|
-
const
|
|
4212
|
-
|
|
4213
|
-
)
|
|
4214
|
-
|
|
4215
|
-
|
|
4233
|
+
// crew-cli emits logs before JSON — find the JSON by scanning for the last
|
|
4234
|
+
// line that starts with { and contains "chat.result"
|
|
4235
|
+
const lines = fullOutput.split("\n");
|
|
4236
|
+
let jsonStart = -1;
|
|
4237
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
4238
|
+
if (lines[i].trimStart().startsWith("{") && fullOutput.indexOf('"kind"') >= 0) {
|
|
4239
|
+
jsonStart = i;
|
|
4240
|
+
break;
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
if (jsonStart < 0) {
|
|
4244
|
+
throw new Error("No JSON object found in crew-cli output");
|
|
4245
|
+
}
|
|
4246
|
+
// Collect from jsonStart to end, parse
|
|
4247
|
+
const jsonCandidate = lines.slice(jsonStart).join("\n");
|
|
4248
|
+
let parsed;
|
|
4249
|
+
try {
|
|
4250
|
+
parsed = JSON.parse(jsonCandidate);
|
|
4251
|
+
} catch {
|
|
4252
|
+
// Try to find just the response field with regex
|
|
4253
|
+
const respMatch = jsonCandidate.match(/"response":\s*"((?:[^"\\]|\\.)*)"/);
|
|
4254
|
+
if (respMatch) {
|
|
4255
|
+
parsed = { response: respMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"') };
|
|
4256
|
+
} else {
|
|
4257
|
+
throw new Error("Failed to parse crew-cli JSON output");
|
|
4258
|
+
}
|
|
4216
4259
|
}
|
|
4217
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
4218
4260
|
if (parsed.response) {
|
|
4219
4261
|
const responseText = typeof parsed.response === 'string'
|
|
4220
4262
|
? parsed.response
|
|
@@ -4580,6 +4622,46 @@ export function createAndStartServer(PORT) {
|
|
|
4580
4622
|
}
|
|
4581
4623
|
}
|
|
4582
4624
|
|
|
4625
|
+
// GET/POST /api/settings/tmux-bridge — toggle tmux-bridge session layer at runtime
|
|
4626
|
+
if (url.pathname === "/api/settings/tmux-bridge") {
|
|
4627
|
+
if (!checkBearer(req)) {
|
|
4628
|
+
json(res, 401, { ok: false, error: "Unauthorized" });
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
if (req.method === "GET") {
|
|
4632
|
+
json(res, 200, { ok: true, enabled: tmuxBridgeRef?.enabled ?? false });
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4635
|
+
if (req.method === "POST") {
|
|
4636
|
+
const body = await readBody(req);
|
|
4637
|
+
const enable =
|
|
4638
|
+
typeof body.enabled === "boolean"
|
|
4639
|
+
? body.enabled
|
|
4640
|
+
: !(tmuxBridgeRef?.enabled ?? false);
|
|
4641
|
+
if (tmuxBridgeRef) tmuxBridgeRef.enabled = enable;
|
|
4642
|
+
try {
|
|
4643
|
+
const cfgPath = path.join(
|
|
4644
|
+
os.homedir(),
|
|
4645
|
+
".crewswarm",
|
|
4646
|
+
"crewswarm.json",
|
|
4647
|
+
);
|
|
4648
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
4649
|
+
cfg.tmuxBridge = enable;
|
|
4650
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
4651
|
+
} catch (e) {
|
|
4652
|
+
console.warn(
|
|
4653
|
+
"[crew-lead] Could not persist tmuxBridge:",
|
|
4654
|
+
e.message,
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
console.log(
|
|
4658
|
+
`[crew-lead] tmux-bridge ${enable ? "ENABLED" : "DISABLED"} via dashboard`,
|
|
4659
|
+
);
|
|
4660
|
+
json(res, 200, { ok: true, enabled: tmuxBridgeRef?.enabled ?? enable });
|
|
4661
|
+
return;
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4583
4665
|
// GET/POST /api/settings/global-fallback — set/get global OpenCode fallback model
|
|
4584
4666
|
if (url.pathname === "/api/settings/global-fallback") {
|
|
4585
4667
|
if (!checkBearer(req)) {
|
|
@@ -4680,6 +4762,250 @@ export function createAndStartServer(PORT) {
|
|
|
4680
4762
|
}
|
|
4681
4763
|
}
|
|
4682
4764
|
|
|
4765
|
+
// ── Missing settings endpoints (bulk implementation) ─────────────────────
|
|
4766
|
+
|
|
4767
|
+
// Helper: read/write crewswarm.json for simple boolean/string settings
|
|
4768
|
+
const cfgPath = path.join(os.homedir(), ".crewswarm", "crewswarm.json");
|
|
4769
|
+
function readCfg() {
|
|
4770
|
+
try { return JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch { return {}; }
|
|
4771
|
+
}
|
|
4772
|
+
function writeCfg(cfg) {
|
|
4773
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
4774
|
+
}
|
|
4775
|
+
function whichBin(bin) {
|
|
4776
|
+
try {
|
|
4777
|
+
const r = spawn("which", [bin], { stdio: "ignore" });
|
|
4778
|
+
// spawn is async but we can check if the binary exists via fs
|
|
4779
|
+
const paths = (process.env.PATH || "").split(":");
|
|
4780
|
+
return paths.some(p => { try { fs.accessSync(path.join(p, bin), fs.constants.X_OK); return true; } catch { return false; } });
|
|
4781
|
+
} catch { return false; }
|
|
4782
|
+
}
|
|
4783
|
+
|
|
4784
|
+
// GET/POST /api/settings/autonomous-mentions
|
|
4785
|
+
if (url.pathname === "/api/settings/autonomous-mentions") {
|
|
4786
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4787
|
+
if (req.method === "GET") {
|
|
4788
|
+
const cfg = readCfg();
|
|
4789
|
+
const enabled = cfg.settings?.autonomousMentionsEnabled !== false;
|
|
4790
|
+
json(res, 200, { ok: true, enabled });
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4793
|
+
if (req.method === "POST") {
|
|
4794
|
+
const body = await readBody(req);
|
|
4795
|
+
const cfg = readCfg();
|
|
4796
|
+
if (!cfg.settings) cfg.settings = {};
|
|
4797
|
+
cfg.settings.autonomousMentionsEnabled = typeof body.enabled === "boolean" ? body.enabled : true;
|
|
4798
|
+
writeCfg(cfg);
|
|
4799
|
+
console.log(`[crew-lead] Autonomous mentions ${cfg.settings.autonomousMentionsEnabled ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4800
|
+
json(res, 200, { ok: true, enabled: cfg.settings.autonomousMentionsEnabled });
|
|
4801
|
+
return;
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4805
|
+
// GET/POST /api/settings/codex
|
|
4806
|
+
if (url.pathname === "/api/settings/codex") {
|
|
4807
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4808
|
+
if (req.method === "GET") {
|
|
4809
|
+
const cfg = readCfg();
|
|
4810
|
+
const enabled = cfg.codexEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_CODEX_ENABLED || ""));
|
|
4811
|
+
json(res, 200, { ok: true, enabled });
|
|
4812
|
+
return;
|
|
4813
|
+
}
|
|
4814
|
+
if (req.method === "POST") {
|
|
4815
|
+
const body = await readBody(req);
|
|
4816
|
+
const cfg = readCfg();
|
|
4817
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.codexEnabled;
|
|
4818
|
+
cfg.codexEnabled = enable;
|
|
4819
|
+
writeCfg(cfg);
|
|
4820
|
+
console.log(`[crew-lead] Codex executor ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4821
|
+
json(res, 200, { ok: true, enabled: enable });
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
// GET/POST /api/settings/gemini-cli
|
|
4827
|
+
if (url.pathname === "/api/settings/gemini-cli") {
|
|
4828
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4829
|
+
if (req.method === "GET") {
|
|
4830
|
+
const cfg = readCfg();
|
|
4831
|
+
const enabled = cfg.geminiCliEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_GEMINI_CLI_ENABLED || ""));
|
|
4832
|
+
const installed = whichBin("gemini");
|
|
4833
|
+
json(res, 200, { ok: true, enabled, installed });
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
if (req.method === "POST") {
|
|
4837
|
+
const body = await readBody(req);
|
|
4838
|
+
const cfg = readCfg();
|
|
4839
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.geminiCliEnabled;
|
|
4840
|
+
cfg.geminiCliEnabled = enable;
|
|
4841
|
+
writeCfg(cfg);
|
|
4842
|
+
console.log(`[crew-lead] Gemini CLI ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4843
|
+
json(res, 200, { ok: true, enabled: enable, installed: whichBin("gemini") });
|
|
4844
|
+
return;
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
|
|
4848
|
+
// GET/POST /api/settings/crew-cli
|
|
4849
|
+
if (url.pathname === "/api/settings/crew-cli") {
|
|
4850
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4851
|
+
if (req.method === "GET") {
|
|
4852
|
+
const cfg = readCfg();
|
|
4853
|
+
const enabled = cfg.crewCliEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_CREW_CLI_ENABLED || ""));
|
|
4854
|
+
json(res, 200, { ok: true, enabled });
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4857
|
+
if (req.method === "POST") {
|
|
4858
|
+
const body = await readBody(req);
|
|
4859
|
+
const cfg = readCfg();
|
|
4860
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.crewCliEnabled;
|
|
4861
|
+
cfg.crewCliEnabled = enable;
|
|
4862
|
+
writeCfg(cfg);
|
|
4863
|
+
console.log(`[crew-lead] Crew CLI ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4864
|
+
json(res, 200, { ok: true, enabled: enable });
|
|
4865
|
+
return;
|
|
4866
|
+
}
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
// GET/POST /api/settings/opencode
|
|
4870
|
+
if (url.pathname === "/api/settings/opencode") {
|
|
4871
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4872
|
+
if (req.method === "GET") {
|
|
4873
|
+
const cfg = readCfg();
|
|
4874
|
+
const enabled = cfg.opencodeEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_OPENCODE_ENABLED || ""));
|
|
4875
|
+
const installed = whichBin("opencode");
|
|
4876
|
+
json(res, 200, { ok: true, enabled, installed });
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
if (req.method === "POST") {
|
|
4880
|
+
const body = await readBody(req);
|
|
4881
|
+
const cfg = readCfg();
|
|
4882
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.opencodeEnabled;
|
|
4883
|
+
cfg.opencodeEnabled = enable;
|
|
4884
|
+
writeCfg(cfg);
|
|
4885
|
+
console.log(`[crew-lead] OpenCode ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4886
|
+
json(res, 200, { ok: true, enabled: enable, installed: whichBin("opencode") });
|
|
4887
|
+
return;
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
// GET/POST /api/settings/global-oc-loop
|
|
4892
|
+
if (url.pathname === "/api/settings/global-oc-loop") {
|
|
4893
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4894
|
+
if (req.method === "GET") {
|
|
4895
|
+
const cfg = readCfg();
|
|
4896
|
+
const enabled = cfg.engineLoop === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_ENGINE_LOOP || ""));
|
|
4897
|
+
const maxRounds = cfg.engineLoopMaxRounds ?? parseInt(process.env.CREWSWARM_ENGINE_LOOP_MAX_ROUNDS || "10", 10);
|
|
4898
|
+
json(res, 200, { ok: true, enabled, maxRounds });
|
|
4899
|
+
return;
|
|
4900
|
+
}
|
|
4901
|
+
if (req.method === "POST") {
|
|
4902
|
+
const body = await readBody(req);
|
|
4903
|
+
const cfg = readCfg();
|
|
4904
|
+
if (typeof body.enabled === "boolean") cfg.engineLoop = body.enabled;
|
|
4905
|
+
if (body.maxRounds !== undefined) cfg.engineLoopMaxRounds = parseInt(body.maxRounds, 10) || 10;
|
|
4906
|
+
writeCfg(cfg);
|
|
4907
|
+
console.log(`[crew-lead] Engine loop: enabled=${cfg.engineLoop ?? false}, maxRounds=${cfg.engineLoopMaxRounds ?? 10}`);
|
|
4908
|
+
json(res, 200, { ok: true, enabled: cfg.engineLoop ?? false, maxRounds: cfg.engineLoopMaxRounds ?? 10 });
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
}
|
|
4912
|
+
|
|
4913
|
+
// GET/POST /api/settings/passthrough-notify
|
|
4914
|
+
if (url.pathname === "/api/settings/passthrough-notify") {
|
|
4915
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4916
|
+
if (req.method === "GET") {
|
|
4917
|
+
const cfg = readCfg();
|
|
4918
|
+
json(res, 200, { ok: true, value: cfg.passthroughNotify || "both" });
|
|
4919
|
+
return;
|
|
4920
|
+
}
|
|
4921
|
+
if (req.method === "POST") {
|
|
4922
|
+
const body = await readBody(req);
|
|
4923
|
+
const cfg = readCfg();
|
|
4924
|
+
cfg.passthroughNotify = body.value || "both";
|
|
4925
|
+
writeCfg(cfg);
|
|
4926
|
+
console.log(`[crew-lead] Passthrough notify → ${cfg.passthroughNotify}`);
|
|
4927
|
+
json(res, 200, { ok: true, value: cfg.passthroughNotify });
|
|
4928
|
+
return;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
// GET/POST /api/settings/loop-brain
|
|
4933
|
+
if (url.pathname === "/api/settings/loop-brain") {
|
|
4934
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4935
|
+
if (req.method === "GET") {
|
|
4936
|
+
const cfg = readCfg();
|
|
4937
|
+
json(res, 200, { ok: true, loopBrain: cfg.loopBrain || "" });
|
|
4938
|
+
return;
|
|
4939
|
+
}
|
|
4940
|
+
if (req.method === "POST") {
|
|
4941
|
+
const body = await readBody(req);
|
|
4942
|
+
const cfg = readCfg();
|
|
4943
|
+
cfg.loopBrain = body.loopBrain || "";
|
|
4944
|
+
writeCfg(cfg);
|
|
4945
|
+
console.log(`[crew-lead] Loop brain → ${cfg.loopBrain || "(cleared)"}`);
|
|
4946
|
+
json(res, 200, { ok: true, loopBrain: cfg.loopBrain });
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
// GET /api/settings/openclaw-status
|
|
4952
|
+
if (url.pathname === "/api/settings/openclaw-status" && req.method === "GET") {
|
|
4953
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4954
|
+
const cfg = readCfg();
|
|
4955
|
+
const installed = !!(cfg.openClaw?.enabled || cfg.openclaw?.gatewayUrl || process.env.OPENCLAW_GATEWAY_URL);
|
|
4956
|
+
json(res, 200, { ok: true, installed });
|
|
4957
|
+
return;
|
|
4958
|
+
}
|
|
4959
|
+
|
|
4960
|
+
// GET/POST /api/settings/rt-token
|
|
4961
|
+
if (url.pathname === "/api/settings/rt-token") {
|
|
4962
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4963
|
+
if (req.method === "GET") {
|
|
4964
|
+
const cfg = readCfg();
|
|
4965
|
+
const token = cfg.rtToken || process.env.CREWSWARM_RT_AUTH_TOKEN || "";
|
|
4966
|
+
json(res, 200, { ok: true, token: token ? true : false });
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
if (req.method === "POST") {
|
|
4970
|
+
const body = await readBody(req);
|
|
4971
|
+
if (!body.token) { json(res, 400, { ok: false, error: "No token provided" }); return; }
|
|
4972
|
+
const cfg = readCfg();
|
|
4973
|
+
cfg.rtToken = body.token;
|
|
4974
|
+
writeCfg(cfg);
|
|
4975
|
+
console.log("[crew-lead] RT token saved via dashboard");
|
|
4976
|
+
json(res, 200, { ok: true, saved: true });
|
|
4977
|
+
return;
|
|
4978
|
+
}
|
|
4979
|
+
}
|
|
4980
|
+
|
|
4981
|
+
// GET /api/config/lock-status, POST /api/config/lock, POST /api/config/unlock
|
|
4982
|
+
if (url.pathname === "/api/config/lock-status" && req.method === "GET") {
|
|
4983
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4984
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
4985
|
+
json(res, 200, { ok: true, locked: fs.existsSync(lockFile) });
|
|
4986
|
+
return;
|
|
4987
|
+
}
|
|
4988
|
+
if (url.pathname === "/api/config/lock" && req.method === "POST") {
|
|
4989
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4990
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
4991
|
+
try {
|
|
4992
|
+
fs.writeFileSync(lockFile, new Date().toISOString());
|
|
4993
|
+
console.log("[crew-lead] Config LOCKED via dashboard");
|
|
4994
|
+
json(res, 200, { ok: true, locked: true });
|
|
4995
|
+
} catch (e) { json(res, 500, { ok: false, error: e.message }); }
|
|
4996
|
+
return;
|
|
4997
|
+
}
|
|
4998
|
+
if (url.pathname === "/api/config/unlock" && req.method === "POST") {
|
|
4999
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
5000
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
5001
|
+
try {
|
|
5002
|
+
fs.unlinkSync(lockFile);
|
|
5003
|
+
console.log("[crew-lead] Config UNLOCKED via dashboard");
|
|
5004
|
+
} catch {}
|
|
5005
|
+
json(res, 200, { ok: true, locked: false });
|
|
5006
|
+
return;
|
|
5007
|
+
}
|
|
5008
|
+
|
|
4683
5009
|
// POST /api/spending/reset — reset today's spending counters
|
|
4684
5010
|
if (url.pathname === "/api/spending/reset" && req.method === "POST") {
|
|
4685
5011
|
if (!checkBearer(req)) {
|