crewswarm 0.9.0 → 0.9.2
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 +2 -2
- package/apps/dashboard/dist/assets/{chat-core-CMoqlR6D.js → chat-core-Cx4sTxDd.js} +1 -1
- 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-CSUb80ze.js → components-BS9fQjE_.js} +1 -1
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js +1 -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-DqVVQLTW.js → index-DnClJ1ee.js} +2 -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-D4g5DMhW.js → setup-wizard-CA0Or47w.js} +1 -1
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-agents-tab-BThdsdJY.js → tab-agents-tab-BgpIsjkw.js} +1 -1
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-benchmarks-tab-DfCuAClu.js → tab-benchmarks-tab-BHjKCPm3.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-comms-tab-eHpOSBhG.js → tab-comms-tab-kguqTIzD.js} +1 -1
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-contacts-tab-5LHSthJM.js → tab-contacts-tab-DiOyMYth.js} +1 -1
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-engines-tab-C3DYxTwy.js → tab-engines-tab-BsdZVvU0.js} +1 -1
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-memory-tab-C59BYFQD.js → tab-memory-tab-Cu6u13EQ.js} +1 -1
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-models-tab-CQzvaeVh.js → tab-models-tab-BLEjmd19.js} +1 -1
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-D7mnDelU.js → tab-pm-loop-tab-Bfd449B4.js} +1 -1
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-projects-tab-C6h2Mv1K.js → tab-projects-tab-DhNWnlzt.js} +1 -1
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-prompts-tab-C0wZvWK3.js → tab-prompts-tab-DVkUNaJd.js} +1 -1
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-services-tab-DBj_w3bc.js → tab-services-tab-DU_LH3uG.js} +1 -1
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-settings-tab-ezeqAjZk.js → tab-settings-tab-Bn4nXtDe.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-skills-tab-BYdU2whk.js → tab-skills-tab-BpY0uZHW.js} +1 -1
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-spending-tab-Bg6w9t_p.js → tab-spending-tab-DEccQHnt.js} +1 -1
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-swarm-chat-tab-BBV9HB2X.js → tab-swarm-chat-tab-BNrd88-r.js} +1 -1
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-swarm-tab-ChqLlEVs.js → tab-swarm-tab-B1AcjL1W.js} +1 -1
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-usage-tab-B2UWXenJ.js → tab-usage-tab-BIOOnB-Y.js} +1 -1
- 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-6QSXLJ0i.js → tab-workflows-tab-B-soSy1k.js} +1 -1
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html +23 -23
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +71 -1
- package/apps/dashboard/src/app.js +5 -0
- package/apps/dashboard/src/core/dom.js +8 -0
- package/apps/dashboard/src/tabs/settings-tab.js +58 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +12 -1
- package/apps/vibe/.crew/cost.json +3 -3
- package/apps/vibe/.crew/json-parse-metrics.jsonl +1 -0
- package/apps/vibe/.crew/pipeline-metrics.jsonl +1 -0
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +5 -0
- package/apps/vibe/.crew/session.json +10 -1
- package/apps/vibe/.studio-data/project-messages/general.jsonl +3 -0
- package/apps/vibe/index.html +4 -2
- package/apps/vibe/server.mjs +75 -3
- package/apps/vibe/src/main.js +126 -53
- package/crew-lead.mjs +14 -1
- package/lib/bridges/cli-executor.mjs +0 -2
- package/lib/bridges/tmux-bridge.mjs +200 -0
- package/lib/chat/unified-history.mjs +1 -1
- package/lib/cli-process-tracker.mjs +2 -1
- package/lib/crew-lead/http-server.mjs +286 -1
- package/lib/crew-lead/wave-dispatcher.mjs +40 -3
- package/lib/engines/crew-cli.mjs +3 -2
- package/lib/engines/llm-direct.mjs +4 -1
- package/lib/engines/rt-envelope.mjs +14 -5
- package/lib/engines/runners.mjs +30 -4
- package/lib/runtime/config.mjs +7 -0
- package/lib/sessions/session-manager.mjs +287 -0
- package/package.json +1 -1
- package/scripts/bench/performance_optimization.py +81 -0
- package/whatsapp-bridge.mjs +54 -10
- package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +0 -1
|
@@ -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
|
+
}
|
|
@@ -66,7 +66,7 @@ export function loadUnifiedHistory(contactId, maxMessages = 2000) {
|
|
|
66
66
|
const files = readdirSync(telegramDir);
|
|
67
67
|
|
|
68
68
|
// Find all topic files for this chat
|
|
69
|
-
// Format: -
|
|
69
|
+
// Format: -100XXXXXXXXXX-topic-20.jsonl, -100XXXXXXXXXX-topic-94.jsonl, etc.
|
|
70
70
|
const topicPattern = new RegExp(`^-?\\d+-topic-\\d+\\.jsonl$`);
|
|
71
71
|
const topicFiles = files.filter(f => topicPattern.test(f));
|
|
72
72
|
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -1208,7 +1209,7 @@ export function createAndStartServer(PORT) {
|
|
|
1208
1209
|
projectId = body.projectId || url.searchParams.get("projectId"); // Support query param too
|
|
1209
1210
|
projectDir = body.projectDir || null;
|
|
1210
1211
|
userId = body.userId || "default";
|
|
1211
|
-
targetAgent = body.targetAgent; // Per-user routing from WhatsApp/Telegram
|
|
1212
|
+
targetAgent = body.targetAgent || body.agentId || null; // Per-user routing from WhatsApp/Telegram/Dashboard
|
|
1212
1213
|
channelMode = body.channelMode === true;
|
|
1213
1214
|
} catch (e) {
|
|
1214
1215
|
json(res, 400, {
|
|
@@ -4580,6 +4581,46 @@ export function createAndStartServer(PORT) {
|
|
|
4580
4581
|
}
|
|
4581
4582
|
}
|
|
4582
4583
|
|
|
4584
|
+
// GET/POST /api/settings/tmux-bridge — toggle tmux-bridge session layer at runtime
|
|
4585
|
+
if (url.pathname === "/api/settings/tmux-bridge") {
|
|
4586
|
+
if (!checkBearer(req)) {
|
|
4587
|
+
json(res, 401, { ok: false, error: "Unauthorized" });
|
|
4588
|
+
return;
|
|
4589
|
+
}
|
|
4590
|
+
if (req.method === "GET") {
|
|
4591
|
+
json(res, 200, { ok: true, enabled: tmuxBridgeRef?.enabled ?? false });
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4594
|
+
if (req.method === "POST") {
|
|
4595
|
+
const body = await readBody(req);
|
|
4596
|
+
const enable =
|
|
4597
|
+
typeof body.enabled === "boolean"
|
|
4598
|
+
? body.enabled
|
|
4599
|
+
: !(tmuxBridgeRef?.enabled ?? false);
|
|
4600
|
+
if (tmuxBridgeRef) tmuxBridgeRef.enabled = enable;
|
|
4601
|
+
try {
|
|
4602
|
+
const cfgPath = path.join(
|
|
4603
|
+
os.homedir(),
|
|
4604
|
+
".crewswarm",
|
|
4605
|
+
"crewswarm.json",
|
|
4606
|
+
);
|
|
4607
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
4608
|
+
cfg.tmuxBridge = enable;
|
|
4609
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
4610
|
+
} catch (e) {
|
|
4611
|
+
console.warn(
|
|
4612
|
+
"[crew-lead] Could not persist tmuxBridge:",
|
|
4613
|
+
e.message,
|
|
4614
|
+
);
|
|
4615
|
+
}
|
|
4616
|
+
console.log(
|
|
4617
|
+
`[crew-lead] tmux-bridge ${enable ? "ENABLED" : "DISABLED"} via dashboard`,
|
|
4618
|
+
);
|
|
4619
|
+
json(res, 200, { ok: true, enabled: tmuxBridgeRef?.enabled ?? enable });
|
|
4620
|
+
return;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
|
|
4583
4624
|
// GET/POST /api/settings/global-fallback — set/get global OpenCode fallback model
|
|
4584
4625
|
if (url.pathname === "/api/settings/global-fallback") {
|
|
4585
4626
|
if (!checkBearer(req)) {
|
|
@@ -4680,6 +4721,250 @@ export function createAndStartServer(PORT) {
|
|
|
4680
4721
|
}
|
|
4681
4722
|
}
|
|
4682
4723
|
|
|
4724
|
+
// ── Missing settings endpoints (bulk implementation) ─────────────────────
|
|
4725
|
+
|
|
4726
|
+
// Helper: read/write crewswarm.json for simple boolean/string settings
|
|
4727
|
+
const cfgPath = path.join(os.homedir(), ".crewswarm", "crewswarm.json");
|
|
4728
|
+
function readCfg() {
|
|
4729
|
+
try { return JSON.parse(fs.readFileSync(cfgPath, "utf8")); } catch { return {}; }
|
|
4730
|
+
}
|
|
4731
|
+
function writeCfg(cfg) {
|
|
4732
|
+
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
|
|
4733
|
+
}
|
|
4734
|
+
function whichBin(bin) {
|
|
4735
|
+
try {
|
|
4736
|
+
const r = spawn("which", [bin], { stdio: "ignore" });
|
|
4737
|
+
// spawn is async but we can check if the binary exists via fs
|
|
4738
|
+
const paths = (process.env.PATH || "").split(":");
|
|
4739
|
+
return paths.some(p => { try { fs.accessSync(path.join(p, bin), fs.constants.X_OK); return true; } catch { return false; } });
|
|
4740
|
+
} catch { return false; }
|
|
4741
|
+
}
|
|
4742
|
+
|
|
4743
|
+
// GET/POST /api/settings/autonomous-mentions
|
|
4744
|
+
if (url.pathname === "/api/settings/autonomous-mentions") {
|
|
4745
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4746
|
+
if (req.method === "GET") {
|
|
4747
|
+
const cfg = readCfg();
|
|
4748
|
+
const enabled = cfg.settings?.autonomousMentionsEnabled !== false;
|
|
4749
|
+
json(res, 200, { ok: true, enabled });
|
|
4750
|
+
return;
|
|
4751
|
+
}
|
|
4752
|
+
if (req.method === "POST") {
|
|
4753
|
+
const body = await readBody(req);
|
|
4754
|
+
const cfg = readCfg();
|
|
4755
|
+
if (!cfg.settings) cfg.settings = {};
|
|
4756
|
+
cfg.settings.autonomousMentionsEnabled = typeof body.enabled === "boolean" ? body.enabled : true;
|
|
4757
|
+
writeCfg(cfg);
|
|
4758
|
+
console.log(`[crew-lead] Autonomous mentions ${cfg.settings.autonomousMentionsEnabled ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4759
|
+
json(res, 200, { ok: true, enabled: cfg.settings.autonomousMentionsEnabled });
|
|
4760
|
+
return;
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
// GET/POST /api/settings/codex
|
|
4765
|
+
if (url.pathname === "/api/settings/codex") {
|
|
4766
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4767
|
+
if (req.method === "GET") {
|
|
4768
|
+
const cfg = readCfg();
|
|
4769
|
+
const enabled = cfg.codexEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_CODEX_ENABLED || ""));
|
|
4770
|
+
json(res, 200, { ok: true, enabled });
|
|
4771
|
+
return;
|
|
4772
|
+
}
|
|
4773
|
+
if (req.method === "POST") {
|
|
4774
|
+
const body = await readBody(req);
|
|
4775
|
+
const cfg = readCfg();
|
|
4776
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.codexEnabled;
|
|
4777
|
+
cfg.codexEnabled = enable;
|
|
4778
|
+
writeCfg(cfg);
|
|
4779
|
+
console.log(`[crew-lead] Codex executor ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4780
|
+
json(res, 200, { ok: true, enabled: enable });
|
|
4781
|
+
return;
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
// GET/POST /api/settings/gemini-cli
|
|
4786
|
+
if (url.pathname === "/api/settings/gemini-cli") {
|
|
4787
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4788
|
+
if (req.method === "GET") {
|
|
4789
|
+
const cfg = readCfg();
|
|
4790
|
+
const enabled = cfg.geminiCliEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_GEMINI_CLI_ENABLED || ""));
|
|
4791
|
+
const installed = whichBin("gemini");
|
|
4792
|
+
json(res, 200, { ok: true, enabled, installed });
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4795
|
+
if (req.method === "POST") {
|
|
4796
|
+
const body = await readBody(req);
|
|
4797
|
+
const cfg = readCfg();
|
|
4798
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.geminiCliEnabled;
|
|
4799
|
+
cfg.geminiCliEnabled = enable;
|
|
4800
|
+
writeCfg(cfg);
|
|
4801
|
+
console.log(`[crew-lead] Gemini CLI ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4802
|
+
json(res, 200, { ok: true, enabled: enable, installed: whichBin("gemini") });
|
|
4803
|
+
return;
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
// GET/POST /api/settings/crew-cli
|
|
4808
|
+
if (url.pathname === "/api/settings/crew-cli") {
|
|
4809
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4810
|
+
if (req.method === "GET") {
|
|
4811
|
+
const cfg = readCfg();
|
|
4812
|
+
const enabled = cfg.crewCliEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_CREW_CLI_ENABLED || ""));
|
|
4813
|
+
json(res, 200, { ok: true, enabled });
|
|
4814
|
+
return;
|
|
4815
|
+
}
|
|
4816
|
+
if (req.method === "POST") {
|
|
4817
|
+
const body = await readBody(req);
|
|
4818
|
+
const cfg = readCfg();
|
|
4819
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.crewCliEnabled;
|
|
4820
|
+
cfg.crewCliEnabled = enable;
|
|
4821
|
+
writeCfg(cfg);
|
|
4822
|
+
console.log(`[crew-lead] Crew CLI ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4823
|
+
json(res, 200, { ok: true, enabled: enable });
|
|
4824
|
+
return;
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
|
|
4828
|
+
// GET/POST /api/settings/opencode
|
|
4829
|
+
if (url.pathname === "/api/settings/opencode") {
|
|
4830
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4831
|
+
if (req.method === "GET") {
|
|
4832
|
+
const cfg = readCfg();
|
|
4833
|
+
const enabled = cfg.opencodeEnabled === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_OPENCODE_ENABLED || ""));
|
|
4834
|
+
const installed = whichBin("opencode");
|
|
4835
|
+
json(res, 200, { ok: true, enabled, installed });
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
if (req.method === "POST") {
|
|
4839
|
+
const body = await readBody(req);
|
|
4840
|
+
const cfg = readCfg();
|
|
4841
|
+
const enable = typeof body.enabled === "boolean" ? body.enabled : !cfg.opencodeEnabled;
|
|
4842
|
+
cfg.opencodeEnabled = enable;
|
|
4843
|
+
writeCfg(cfg);
|
|
4844
|
+
console.log(`[crew-lead] OpenCode ${enable ? "ENABLED" : "DISABLED"} via dashboard`);
|
|
4845
|
+
json(res, 200, { ok: true, enabled: enable, installed: whichBin("opencode") });
|
|
4846
|
+
return;
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
// GET/POST /api/settings/global-oc-loop
|
|
4851
|
+
if (url.pathname === "/api/settings/global-oc-loop") {
|
|
4852
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4853
|
+
if (req.method === "GET") {
|
|
4854
|
+
const cfg = readCfg();
|
|
4855
|
+
const enabled = cfg.engineLoop === true || /^1|true|yes$/i.test(String(process.env.CREWSWARM_ENGINE_LOOP || ""));
|
|
4856
|
+
const maxRounds = cfg.engineLoopMaxRounds ?? parseInt(process.env.CREWSWARM_ENGINE_LOOP_MAX_ROUNDS || "10", 10);
|
|
4857
|
+
json(res, 200, { ok: true, enabled, maxRounds });
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
if (req.method === "POST") {
|
|
4861
|
+
const body = await readBody(req);
|
|
4862
|
+
const cfg = readCfg();
|
|
4863
|
+
if (typeof body.enabled === "boolean") cfg.engineLoop = body.enabled;
|
|
4864
|
+
if (body.maxRounds !== undefined) cfg.engineLoopMaxRounds = parseInt(body.maxRounds, 10) || 10;
|
|
4865
|
+
writeCfg(cfg);
|
|
4866
|
+
console.log(`[crew-lead] Engine loop: enabled=${cfg.engineLoop ?? false}, maxRounds=${cfg.engineLoopMaxRounds ?? 10}`);
|
|
4867
|
+
json(res, 200, { ok: true, enabled: cfg.engineLoop ?? false, maxRounds: cfg.engineLoopMaxRounds ?? 10 });
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
}
|
|
4871
|
+
|
|
4872
|
+
// GET/POST /api/settings/passthrough-notify
|
|
4873
|
+
if (url.pathname === "/api/settings/passthrough-notify") {
|
|
4874
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4875
|
+
if (req.method === "GET") {
|
|
4876
|
+
const cfg = readCfg();
|
|
4877
|
+
json(res, 200, { ok: true, value: cfg.passthroughNotify || "both" });
|
|
4878
|
+
return;
|
|
4879
|
+
}
|
|
4880
|
+
if (req.method === "POST") {
|
|
4881
|
+
const body = await readBody(req);
|
|
4882
|
+
const cfg = readCfg();
|
|
4883
|
+
cfg.passthroughNotify = body.value || "both";
|
|
4884
|
+
writeCfg(cfg);
|
|
4885
|
+
console.log(`[crew-lead] Passthrough notify → ${cfg.passthroughNotify}`);
|
|
4886
|
+
json(res, 200, { ok: true, value: cfg.passthroughNotify });
|
|
4887
|
+
return;
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
// GET/POST /api/settings/loop-brain
|
|
4892
|
+
if (url.pathname === "/api/settings/loop-brain") {
|
|
4893
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4894
|
+
if (req.method === "GET") {
|
|
4895
|
+
const cfg = readCfg();
|
|
4896
|
+
json(res, 200, { ok: true, loopBrain: cfg.loopBrain || "" });
|
|
4897
|
+
return;
|
|
4898
|
+
}
|
|
4899
|
+
if (req.method === "POST") {
|
|
4900
|
+
const body = await readBody(req);
|
|
4901
|
+
const cfg = readCfg();
|
|
4902
|
+
cfg.loopBrain = body.loopBrain || "";
|
|
4903
|
+
writeCfg(cfg);
|
|
4904
|
+
console.log(`[crew-lead] Loop brain → ${cfg.loopBrain || "(cleared)"}`);
|
|
4905
|
+
json(res, 200, { ok: true, loopBrain: cfg.loopBrain });
|
|
4906
|
+
return;
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
|
|
4910
|
+
// GET /api/settings/openclaw-status
|
|
4911
|
+
if (url.pathname === "/api/settings/openclaw-status" && req.method === "GET") {
|
|
4912
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4913
|
+
const cfg = readCfg();
|
|
4914
|
+
const installed = !!(cfg.openClaw?.enabled || cfg.openclaw?.gatewayUrl || process.env.OPENCLAW_GATEWAY_URL);
|
|
4915
|
+
json(res, 200, { ok: true, installed });
|
|
4916
|
+
return;
|
|
4917
|
+
}
|
|
4918
|
+
|
|
4919
|
+
// GET/POST /api/settings/rt-token
|
|
4920
|
+
if (url.pathname === "/api/settings/rt-token") {
|
|
4921
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4922
|
+
if (req.method === "GET") {
|
|
4923
|
+
const cfg = readCfg();
|
|
4924
|
+
const token = cfg.rtToken || process.env.CREWSWARM_RT_AUTH_TOKEN || "";
|
|
4925
|
+
json(res, 200, { ok: true, token: token ? true : false });
|
|
4926
|
+
return;
|
|
4927
|
+
}
|
|
4928
|
+
if (req.method === "POST") {
|
|
4929
|
+
const body = await readBody(req);
|
|
4930
|
+
if (!body.token) { json(res, 400, { ok: false, error: "No token provided" }); return; }
|
|
4931
|
+
const cfg = readCfg();
|
|
4932
|
+
cfg.rtToken = body.token;
|
|
4933
|
+
writeCfg(cfg);
|
|
4934
|
+
console.log("[crew-lead] RT token saved via dashboard");
|
|
4935
|
+
json(res, 200, { ok: true, saved: true });
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
// GET /api/config/lock-status, POST /api/config/lock, POST /api/config/unlock
|
|
4941
|
+
if (url.pathname === "/api/config/lock-status" && req.method === "GET") {
|
|
4942
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4943
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
4944
|
+
json(res, 200, { ok: true, locked: fs.existsSync(lockFile) });
|
|
4945
|
+
return;
|
|
4946
|
+
}
|
|
4947
|
+
if (url.pathname === "/api/config/lock" && req.method === "POST") {
|
|
4948
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4949
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
4950
|
+
try {
|
|
4951
|
+
fs.writeFileSync(lockFile, new Date().toISOString());
|
|
4952
|
+
console.log("[crew-lead] Config LOCKED via dashboard");
|
|
4953
|
+
json(res, 200, { ok: true, locked: true });
|
|
4954
|
+
} catch (e) { json(res, 500, { ok: false, error: e.message }); }
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
if (url.pathname === "/api/config/unlock" && req.method === "POST") {
|
|
4958
|
+
if (!checkBearer(req)) { json(res, 401, { ok: false, error: "Unauthorized" }); return; }
|
|
4959
|
+
const lockFile = path.join(os.homedir(), ".crewswarm", ".config.lock");
|
|
4960
|
+
try {
|
|
4961
|
+
fs.unlinkSync(lockFile);
|
|
4962
|
+
console.log("[crew-lead] Config UNLOCKED via dashboard");
|
|
4963
|
+
} catch {}
|
|
4964
|
+
json(res, 200, { ok: true, locked: false });
|
|
4965
|
+
return;
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4683
4968
|
// POST /api/spending/reset — reset today's spending counters
|
|
4684
4969
|
if (url.pathname === "/api/spending/reset" && req.method === "POST") {
|
|
4685
4970
|
if (!checkBearer(req)) {
|
|
@@ -11,6 +11,8 @@ import { randomUUID } from "node:crypto";
|
|
|
11
11
|
import { getStatePath, getConfigPath } from "../runtime/paths.mjs";
|
|
12
12
|
import { normalizeProjectDir } from "../runtime/project-dir.mjs";
|
|
13
13
|
import { loadProjectMessages } from "../chat/project-messages.mjs";
|
|
14
|
+
import * as tmuxBridge from "../bridges/tmux-bridge.mjs";
|
|
15
|
+
import * as sessionManager from "../sessions/session-manager.mjs";
|
|
14
16
|
|
|
15
17
|
let _deps = {};
|
|
16
18
|
|
|
@@ -397,12 +399,11 @@ export function dispatchPipelineWave(pipelineId) {
|
|
|
397
399
|
`Fan out all ${waveSteps.length} tasks simultaneously and return combined results.`,
|
|
398
400
|
].join("\n");
|
|
399
401
|
|
|
400
|
-
console.log(`[crew-lead]
|
|
401
|
-
const taskId = dispatchTask("crew-orchestrator", { task: orchestratorTask
|
|
402
|
+
console.log(`[crew-lead] WAVE_DISPATCH: routing wave ${currentWave + 1} through crew-orchestrator (${waveSteps.length} parallel tasks)`);
|
|
403
|
+
const taskId = dispatchTask("crew-orchestrator", { task: orchestratorTask }, sessionId, {
|
|
402
404
|
pipelineId,
|
|
403
405
|
waveIndex: currentWave,
|
|
404
406
|
projectDir: pipeline.projectDir,
|
|
405
|
-
useCursorCli: true,
|
|
406
407
|
originProjectId: pipeline.originProjectId,
|
|
407
408
|
originChannel: pipeline.originChannel,
|
|
408
409
|
originThreadId: pipeline.originThreadId,
|
|
@@ -426,6 +427,11 @@ export function dispatchPipelineWave(pipelineId) {
|
|
|
426
427
|
...(step.verify ? { verify: step.verify } : {}),
|
|
427
428
|
...(step.done ? { done: step.done } : {}),
|
|
428
429
|
};
|
|
430
|
+
// Build session handoff metadata for tmux-bridge
|
|
431
|
+
const sessionMeta = {};
|
|
432
|
+
if (step.session) sessionMeta.session = step.session;
|
|
433
|
+
else if (pipeline._tmuxSessionId) sessionMeta.session = `handoff:${pipeline._tmuxSessionId}`;
|
|
434
|
+
|
|
429
435
|
const taskId = dispatchTask(step.agent, stepSpec, sessionId, {
|
|
430
436
|
pipelineId,
|
|
431
437
|
waveIndex: currentWave,
|
|
@@ -435,6 +441,7 @@ export function dispatchPipelineWave(pipelineId) {
|
|
|
435
441
|
originThreadId: pipeline.originThreadId,
|
|
436
442
|
originMessageId: pipeline.originMessageId,
|
|
437
443
|
triggeredBy: pipeline.triggeredBy || "pipeline",
|
|
444
|
+
...sessionMeta,
|
|
438
445
|
});
|
|
439
446
|
if (taskId && taskId !== true) pipeline.pendingTaskIds.add(taskId);
|
|
440
447
|
}
|
|
@@ -763,6 +770,36 @@ export function dispatchTask(agent, task, sessionId = "owner", pipelineMeta = nu
|
|
|
763
770
|
if (pipelineMeta?.mentionedBy) extraFlags.mentionedBy = pipelineMeta.mentionedBy;
|
|
764
771
|
if (pipelineMeta?.autonomous !== undefined) extraFlags.autonomous = pipelineMeta.autonomous;
|
|
765
772
|
|
|
773
|
+
// ── tmux session handoff ──────────────────────────────────────────────
|
|
774
|
+
// If pipelineMeta carries a tmux session, hand it off to this agent
|
|
775
|
+
// or create a new one if session: "persist" is set.
|
|
776
|
+
if (tmuxBridge.detect()) {
|
|
777
|
+
const sessionSpec = pipelineMeta?.session;
|
|
778
|
+
if (typeof sessionSpec === "string" && sessionSpec.startsWith("handoff:")) {
|
|
779
|
+
const existingSessionId = sessionSpec.slice("handoff:".length);
|
|
780
|
+
const prevOwner = sessionManager.getSession(existingSessionId)?.owner;
|
|
781
|
+
if (prevOwner) {
|
|
782
|
+
sessionManager.handoff(existingSessionId, prevOwner, agent);
|
|
783
|
+
}
|
|
784
|
+
const meta = sessionManager.getSession(existingSessionId);
|
|
785
|
+
if (meta?.paneId) extraFlags.tmuxSessionId = meta.paneId;
|
|
786
|
+
} else if (sessionSpec === "persist") {
|
|
787
|
+
const newSessionId = sessionManager.create({
|
|
788
|
+
workspaceId: pipelineMeta?.pipelineId || "default",
|
|
789
|
+
agentId: agent,
|
|
790
|
+
cwd: pipelineMeta?.projectDir || undefined,
|
|
791
|
+
});
|
|
792
|
+
if (newSessionId) {
|
|
793
|
+
const meta = sessionManager.getSession(newSessionId);
|
|
794
|
+
if (meta?.paneId) extraFlags.tmuxSessionId = meta.paneId;
|
|
795
|
+
// Store session ID on pipeline meta so next wave can handoff
|
|
796
|
+
if (pipelineMeta) pipelineMeta._tmuxSessionId = newSessionId;
|
|
797
|
+
}
|
|
798
|
+
} else if (pipelineMeta?.tmuxSessionId) {
|
|
799
|
+
extraFlags.tmuxSessionId = pipelineMeta.tmuxSessionId;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
766
803
|
// Log enrichment for verification
|
|
767
804
|
const engineFlags = Object.keys(extraFlags).filter(k => k.startsWith('use') || k.includes('Model') || k === 'engine');
|
|
768
805
|
if (engineFlags.length > 0) {
|
package/lib/engines/crew-cli.mjs
CHANGED
|
@@ -71,11 +71,12 @@ export async function runCrewCLITask(prompt, payload = {}) {
|
|
|
71
71
|
""
|
|
72
72
|
);
|
|
73
73
|
|
|
74
|
-
// Resolve project directory
|
|
75
|
-
const
|
|
74
|
+
// Resolve project directory — must be a string (Sandbox requires it)
|
|
75
|
+
const rawDir =
|
|
76
76
|
payload?.projectDir ||
|
|
77
77
|
(getOpencodeProjectDir ? getOpencodeProjectDir() : null) ||
|
|
78
78
|
process.cwd();
|
|
79
|
+
const projectDir = typeof rawDir === "string" && rawDir.trim() ? rawDir.trim() : process.cwd();
|
|
79
80
|
|
|
80
81
|
// Ensure API keys are in env
|
|
81
82
|
const providers = loadProviders();
|
|
@@ -10,7 +10,10 @@ export async function callLLMDirect(prompt, ocAgentId, systemPrompt) {
|
|
|
10
10
|
recordTokenUsage, loadProviderMap,
|
|
11
11
|
} = _deps;
|
|
12
12
|
const llm = loadAgentLLMConfig(ocAgentId);
|
|
13
|
-
if (!llm)
|
|
13
|
+
if (!llm) {
|
|
14
|
+
console.error(`[llm-direct] loadAgentLLMConfig("${ocAgentId}") returned null. _deps set: ${!!_deps.loadAgentLLMConfig}. CREWSWARM_RT_AGENT=${process.env.CREWSWARM_RT_AGENT}`);
|
|
15
|
+
return null; // fall through to legacy gateway
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
// ── Spending cap pre-check ─────────────────────────────────────────────────
|
|
16
19
|
const capResult = checkSpendingCap(ocAgentId, llm.providerKey || llm.modelId.split("/")[0]);
|
|
@@ -908,19 +908,28 @@ export async function handleRealtimeEnvelope(envelope, client, bridge) {
|
|
|
908
908
|
} else {
|
|
909
909
|
// ── No coding engine assigned — use direct LLM (agent's configured model) ──
|
|
910
910
|
engineUsed = "direct-llm";
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
911
|
+
// Use the DISPATCHED agent's ID, not the gateway's own identity.
|
|
912
|
+
// When a gateway process handles a task for crew-researcher, it needs
|
|
913
|
+
// crew-researcher's model config (perplexity/sonar), not its own.
|
|
914
|
+
// The target agent is in envelope.to, payload.agentId, or payload.agent.
|
|
915
|
+
const dispatchedAgent = String(
|
|
916
|
+
payload?.agentId || payload?.agent || to || ""
|
|
917
|
+
).trim();
|
|
918
|
+
const ocAgentId = (dispatchedAgent && dispatchedAgent !== "broadcast")
|
|
919
|
+
? dispatchedAgent
|
|
920
|
+
: CREWSWARM_RT_AGENT;
|
|
921
|
+
const agentSysPrompt = loadAgentPrompts()[ocAgentId] || loadAgentPrompts()[CREWSWARM_RT_AGENT] || null;
|
|
922
|
+
const agentCfg = getAgentOpenCodeConfig(ocAgentId);
|
|
914
923
|
modelUsed = agentCfg?.model || payload?.model || "unknown";
|
|
915
924
|
|
|
916
925
|
console.error(
|
|
917
926
|
`[${CREWSWARM_RT_AGENT}] 🧠 Direct LLM route (no coding engine): agent=${ocAgentId}, model=${modelUsed}`,
|
|
918
927
|
);
|
|
919
|
-
progress(`Routing to direct LLM (
|
|
928
|
+
progress(`Routing to direct LLM (${ocAgentId} → ${modelUsed})...`);
|
|
920
929
|
telemetry("realtime_route_direct_llm", {
|
|
921
930
|
taskId,
|
|
922
931
|
incomingType,
|
|
923
|
-
agent:
|
|
932
|
+
agent: ocAgentId,
|
|
924
933
|
model: modelUsed,
|
|
925
934
|
});
|
|
926
935
|
|