crewswarm 0.9.3 → 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/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/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.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/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-benchmarks-tab-BHjKCPm3.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-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-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.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-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-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-testing-tab-CezZOZcJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.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 +56 -7
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/vibe/server.mjs +98 -53
- package/lib/bridges/cli-executor.mjs +1 -1
- 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 +4 -1
- package/lib/crew-lead/http-server.mjs +73 -65
- package/lib/crew-lead/prompts.mjs +7 -1
- package/lib/crew-lead/tools.mjs +3 -2
- package/lib/crew-lead/wave-dispatcher.mjs +4 -2
- package/lib/engines/crew-cli.mjs +1 -1
- package/lib/engines/engine-registry.mjs +11 -9
- package/lib/engines/runners.mjs +23 -2
- 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/spending.mjs +2 -1
- package/package.json +17 -9
- package/scripts/dashboard-validation.mjs +2 -0
- package/scripts/dashboard.mjs +1110 -484
- package/scripts/generate-openapi.mjs +683 -277
- package/scripts/restart-service.sh +12 -9
- package/apps/dashboard/dist/assets/chat-core-3KirthZA.js +0 -1
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +0 -2
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +0 -1
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
package/lib/crew-judge/judge.mjs
CHANGED
|
@@ -150,25 +150,26 @@ function getJudgeModel() {
|
|
|
150
150
|
|
|
151
151
|
async function callJudgeModel(model, prompt) {
|
|
152
152
|
// Import LLM caller from crew-lead
|
|
153
|
-
const {
|
|
154
|
-
|
|
153
|
+
const { callLLM } = await import('../crew-lead/llm-caller.mjs');
|
|
154
|
+
const { loadProviderMap } = await import('../runtime/config.mjs');
|
|
155
|
+
|
|
155
156
|
try {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
});
|
|
157
|
+
const providerKey = model.split('/')[0];
|
|
158
|
+
const modelId = model.split('/').slice(1).join('/');
|
|
159
|
+
const providerMap = loadProviderMap();
|
|
160
|
+
const provider = providerMap[providerKey] || null;
|
|
161
|
+
|
|
162
|
+
const messages = [
|
|
163
|
+
{
|
|
164
|
+
role: 'system',
|
|
165
|
+
content: 'You are crew-judge. Return ONLY valid JSON: {"decision": "CONTINUE"|"SHIP"|"RESET", "reasoning": "...", "confidence": 0.0-1.0}'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: prompt
|
|
170
|
+
}
|
|
171
|
+
];
|
|
172
|
+
const { reply: response } = await callLLM(messages, { provider, modelId, providerKey }, { temperature: 0.3, maxTokens: 500 });
|
|
172
173
|
|
|
173
174
|
// Parse JSON response
|
|
174
175
|
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
@@ -79,7 +79,7 @@ export function createAgent({ id, role, displayName, prompt, description, model
|
|
|
79
79
|
const defaultOcModel = (() => {
|
|
80
80
|
const existingCoder = swarm.agents.find(a => a.opencodeModel && a.useOpenCode);
|
|
81
81
|
if (existingCoder) return existingCoder.opencodeModel;
|
|
82
|
-
return process.env.CREWSWARM_OPENCODE_MODEL || "openai/gpt-5.
|
|
82
|
+
return process.env.CREWSWARM_OPENCODE_MODEL || "openai/gpt-5.4";
|
|
83
83
|
})();
|
|
84
84
|
|
|
85
85
|
const agentEntry = {
|
|
@@ -28,6 +28,19 @@ const _timeoutLog = [];
|
|
|
28
28
|
let _lastBgConsciousnessAt = 0;
|
|
29
29
|
let _bgLoopInterval = null;
|
|
30
30
|
|
|
31
|
+
/** Reset internal state for test isolation. */
|
|
32
|
+
export function resetForTesting() {
|
|
33
|
+
_agentTimeoutCounts.clear();
|
|
34
|
+
_timeoutLog.length = 0;
|
|
35
|
+
_lastBgConsciousnessAt = 0;
|
|
36
|
+
if (_bgLoopInterval) { clearInterval(_bgLoopInterval); _bgLoopInterval = null; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Stop the background loop interval (prevents process from hanging in tests). */
|
|
40
|
+
export function stopBackgroundLoop() {
|
|
41
|
+
if (_bgLoopInterval) { clearInterval(_bgLoopInterval); _bgLoopInterval = null; }
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
export function initBackground({
|
|
32
45
|
broadcastSSE,
|
|
33
46
|
appendHistory,
|
|
@@ -267,4 +280,4 @@ export function getRateLimitFallback(agentId) {
|
|
|
267
280
|
return "crew-main";
|
|
268
281
|
}
|
|
269
282
|
|
|
270
|
-
export const RATE_LIMIT_PATTERN = /429|rate\
|
|
283
|
+
export const RATE_LIMIT_PATTERN = /429|rate[\s_]*limit|throttl|quota[\s_]*exceeded|too[\s_]*many[\s_]*requests|resource_exhausted|overloaded/i;
|
|
@@ -750,7 +750,10 @@ Reply with your answers and I'll turn this into a concrete build plan with file
|
|
|
750
750
|
// IMPORTANT: Do not gate on keywords only. Casual replies ("yeah no shit", "ok cool")
|
|
751
751
|
// used to skip the snapshot while long contradictory assistant history stayed in context,
|
|
752
752
|
// so the LLM invented swarm-wide offline/online flips. Fetch for substantive messages.
|
|
753
|
-
|
|
753
|
+
// Custom personas (displayName set to something other than default "crew-lead") are
|
|
754
|
+
// personality/chat personas — they don't need operational system state, skip snapshot.
|
|
755
|
+
const isCustomPersona = cfg.displayName && cfg.displayName.toLowerCase() !== "crew-lead";
|
|
756
|
+
const needsHealth = !isCustomPersona && message.trim().length >= 6;
|
|
754
757
|
const needsBenchmarkCatalog =
|
|
755
758
|
message.length > 4 &&
|
|
756
759
|
/benchmark|zeroeval|leaderboard|llm-stats|swe-bench|livecodebench|mmlu|gpqa|humaneval|gsm8k|what\.tests?|which\.tests?|available\.tests?/i.test(
|
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
import crypto from "node:crypto";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import os from "node:os";
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
11
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
12
12
|
import { executeCLI } from "../bridges/cli-executor.mjs";
|
|
13
13
|
import { applySharedChatPromptOverlay } from "../chat/shared-chat-prompt-overlay.mjs";
|
|
14
14
|
import { classifySharedChatMention } from "../chat/mention-routing-intent.mjs";
|
|
@@ -60,6 +60,25 @@ function resolveCliBinary(configured, candidates = []) {
|
|
|
60
60
|
return configured;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function isClaudeOauthAuthenticated() {
|
|
64
|
+
try {
|
|
65
|
+
const bin = resolveCliBinary("claude", [
|
|
66
|
+
path.join(os.homedir(), ".local", "bin", "claude"),
|
|
67
|
+
"/usr/local/bin/claude",
|
|
68
|
+
"/opt/homebrew/bin/claude",
|
|
69
|
+
]);
|
|
70
|
+
const output = execFileSync(bin, ["auth", "status"], {
|
|
71
|
+
encoding: "utf8",
|
|
72
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
73
|
+
}).trim().toLowerCase();
|
|
74
|
+
if (!output) return false;
|
|
75
|
+
if (/"loggedin"\s*:\s*true/.test(output)) return true;
|
|
76
|
+
return output.includes("logged in");
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
/** Strip SGR / OSC ANSI sequences (OpenCode and CLIs often emit colored TTY junk on stdout). */
|
|
64
83
|
function stripAnsiPassthrough(text) {
|
|
65
84
|
return String(text || "")
|
|
@@ -94,6 +113,7 @@ function shouldDropPassthroughStderrLine(engine, line) {
|
|
|
94
113
|
if (/rmcp::/i.test(l)) return true;
|
|
95
114
|
if (/error decoding response body.*initialized notification/i.test(l))
|
|
96
115
|
return true;
|
|
116
|
+
if (/\[Executor\]\s+\w+\s+\((OAuth|API)\)/i.test(l)) return true;
|
|
97
117
|
if (
|
|
98
118
|
engine === "codex" &&
|
|
99
119
|
/worker quit with fatal/i.test(l) &&
|
|
@@ -3733,7 +3753,7 @@ export function createAndStartServer(PORT) {
|
|
|
3733
3753
|
const ocModel =
|
|
3734
3754
|
reqModel ||
|
|
3735
3755
|
process.env.CREWSWARM_OPENCODE_MODEL ||
|
|
3736
|
-
"
|
|
3756
|
+
"openai/gpt-5.4";
|
|
3737
3757
|
args = ["run", finalMessage, "--model", ocModel];
|
|
3738
3758
|
// Add workspace directory context
|
|
3739
3759
|
if (projectDir) args.push("--dir", projectDir);
|
|
@@ -3756,7 +3776,7 @@ export function createAndStartServer(PORT) {
|
|
|
3756
3776
|
|
|
3757
3777
|
if (priorCodexThread) {
|
|
3758
3778
|
// `codex resume <thread_id> "prompt"` continues the prior session
|
|
3759
|
-
args = ["resume", priorCodexThread, finalMessage
|
|
3779
|
+
args = ["resume", priorCodexThread, finalMessage];
|
|
3760
3780
|
if (codexModel) args.push("--model", codexModel);
|
|
3761
3781
|
if (projectDir) args.push("-C", projectDir);
|
|
3762
3782
|
} else {
|
|
@@ -3806,10 +3826,10 @@ export function createAndStartServer(PORT) {
|
|
|
3806
3826
|
const crewCliModel =
|
|
3807
3827
|
reqModel || process.env.CREWSWARM_CREW_CLI_MODEL || null;
|
|
3808
3828
|
if (bin.endsWith(".js")) {
|
|
3809
|
-
args = [bin, "chat", finalMessage, "--direct", "--json"];
|
|
3829
|
+
args = [bin, "chat", finalMessage, "--direct", "--json", "--apply"];
|
|
3810
3830
|
bin = process.execPath;
|
|
3811
3831
|
} else {
|
|
3812
|
-
args = ["chat", finalMessage, "--direct", "--json"];
|
|
3832
|
+
args = ["chat", finalMessage, "--direct", "--json", "--apply"];
|
|
3813
3833
|
}
|
|
3814
3834
|
if (crewCliModel) args.push("--model", crewCliModel);
|
|
3815
3835
|
if (projectDir && projectDir !== process.cwd())
|
|
@@ -3921,6 +3941,25 @@ export function createAndStartServer(PORT) {
|
|
|
3921
3941
|
}
|
|
3922
3942
|
}
|
|
3923
3943
|
} catch {}
|
|
3944
|
+
// Strip API keys AFTER config merge so CLI tools use OAuth (free) not API credits
|
|
3945
|
+
if (engine === "claude") {
|
|
3946
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
3947
|
+
delete spawnEnv.CLAUDECODE;
|
|
3948
|
+
delete spawnEnv.CLAUDE_CODE;
|
|
3949
|
+
delete spawnEnv.CREWSWARM_CLAUDE_CODE;
|
|
3950
|
+
}
|
|
3951
|
+
if (engine === "codex") {
|
|
3952
|
+
delete spawnEnv.OPENAI_API_KEY;
|
|
3953
|
+
}
|
|
3954
|
+
if (engine === "gemini" || engine === "gemini-cli") {
|
|
3955
|
+
delete spawnEnv.GEMINI_API_KEY;
|
|
3956
|
+
delete spawnEnv.GOOGLE_API_KEY;
|
|
3957
|
+
}
|
|
3958
|
+
if (engine === "opencode" || engine === "antigravity") {
|
|
3959
|
+
delete spawnEnv.OPENAI_API_KEY;
|
|
3960
|
+
delete spawnEnv.ANTHROPIC_API_KEY;
|
|
3961
|
+
delete spawnEnv.GEMINI_API_KEY;
|
|
3962
|
+
}
|
|
3924
3963
|
// Force L2 planner for crew-cli when requested (enhance-prompt planner path)
|
|
3925
3964
|
if (reqForceL2) spawnEnv.CREW_FORCE_L2 = "true";
|
|
3926
3965
|
|
|
@@ -4229,56 +4268,39 @@ export function createAndStartServer(PORT) {
|
|
|
4229
4268
|
|
|
4230
4269
|
// crew-cli special handling: extract JSON from output (skipping logs) and send only response field
|
|
4231
4270
|
if (engine === "crew-cli" && fullOutput.trim()) {
|
|
4271
|
+
let extracted = false;
|
|
4232
4272
|
try {
|
|
4233
|
-
//
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4273
|
+
// Strategy 1: cross-line regex to find JSON envelope
|
|
4274
|
+
const jsonMatch = fullOutput.match(/\{[\s\S]*"kind":\s*"[^"]+\.result"[\s\S]*\}/);
|
|
4275
|
+
if (jsonMatch) {
|
|
4276
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
4277
|
+
if (parsed.response) {
|
|
4278
|
+
const responseText = typeof parsed.response === "string"
|
|
4279
|
+
? parsed.response
|
|
4280
|
+
: JSON.stringify(parsed.response, null, 2);
|
|
4281
|
+
console.log(`[crew-cli] ✅ Extracted response: ${responseText.slice(0, 80)}...`);
|
|
4282
|
+
send({ type: "chunk", text: responseText });
|
|
4283
|
+
fullOutput = responseText;
|
|
4284
|
+
extracted = true;
|
|
4241
4285
|
}
|
|
4242
4286
|
}
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
// Collect from jsonStart to end, parse
|
|
4247
|
-
const jsonCandidate = lines.slice(jsonStart).join("\n");
|
|
4248
|
-
let parsed;
|
|
4287
|
+
} catch { /* fall through */ }
|
|
4288
|
+
if (!extracted) {
|
|
4289
|
+
// Strategy 2: regex extract just the response field
|
|
4249
4290
|
try {
|
|
4250
|
-
|
|
4251
|
-
} catch {
|
|
4252
|
-
// Try to find just the response field with regex
|
|
4253
|
-
const respMatch = jsonCandidate.match(/"response":\s*"((?:[^"\\]|\\.)*)"/);
|
|
4291
|
+
const respMatch = fullOutput.match(/"response":\s*"((?:[^"\\]|\\.)*)"/);
|
|
4254
4292
|
if (respMatch) {
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4293
|
+
const responseText = respMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\t/g, "\t");
|
|
4294
|
+
console.log(`[crew-cli] ✅ Extracted response (regex): ${responseText.slice(0, 80)}...`);
|
|
4295
|
+
send({ type: "chunk", text: responseText });
|
|
4296
|
+
fullOutput = responseText;
|
|
4297
|
+
extracted = true;
|
|
4258
4298
|
}
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
console.error(
|
|
4265
|
-
`[crew-cli] ✅ Extracted response: ${responseText.slice(0, 80)}...`,
|
|
4266
|
-
);
|
|
4267
|
-
// NOW send the actual response as a chunk
|
|
4268
|
-
send({ type: "chunk", text: responseText });
|
|
4269
|
-
fullOutput = responseText; // Replace fullOutput for notifications
|
|
4270
|
-
} else {
|
|
4271
|
-
console.error(`[crew-cli] ⚠️ No response field in JSON`);
|
|
4272
|
-
send({ type: "chunk", text: fullOutput }); // Send raw output as fallback
|
|
4273
|
-
}
|
|
4274
|
-
} catch (err) {
|
|
4275
|
-
console.error(
|
|
4276
|
-
`[crew-cli] ❌ Failed to extract JSON: ${err.message}`,
|
|
4277
|
-
);
|
|
4278
|
-
console.error(
|
|
4279
|
-
`[crew-cli] First 200 chars: ${fullOutput.slice(0, 200)}`,
|
|
4280
|
-
);
|
|
4281
|
-
// Send raw output as fallback
|
|
4299
|
+
} catch { /* fall through */ }
|
|
4300
|
+
}
|
|
4301
|
+
if (!extracted) {
|
|
4302
|
+
console.error(`[crew-cli] ❌ Failed to extract response from crew-cli output`);
|
|
4303
|
+
console.error(`[crew-cli] First 200 chars: ${fullOutput.slice(0, 200)}`);
|
|
4282
4304
|
send({ type: "chunk", text: fullOutput });
|
|
4283
4305
|
}
|
|
4284
4306
|
}
|
|
@@ -4536,22 +4558,8 @@ export function createAndStartServer(PORT) {
|
|
|
4536
4558
|
);
|
|
4537
4559
|
enabled = cfg.claudeCode === true;
|
|
4538
4560
|
} catch { }
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4541
|
-
(() => {
|
|
4542
|
-
try {
|
|
4543
|
-
return JSON.parse(
|
|
4544
|
-
fs.readFileSync(
|
|
4545
|
-
path.join(os.homedir(), ".crewswarm", "crewswarm.json"),
|
|
4546
|
-
"utf8",
|
|
4547
|
-
),
|
|
4548
|
-
)?.providers?.anthropic?.apiKey;
|
|
4549
|
-
} catch {
|
|
4550
|
-
return null;
|
|
4551
|
-
}
|
|
4552
|
-
})()
|
|
4553
|
-
);
|
|
4554
|
-
json(res, 200, { ok: true, enabled, hasKey });
|
|
4561
|
+
const hasAuth = isClaudeOauthAuthenticated();
|
|
4562
|
+
json(res, 200, { ok: true, enabled, hasKey: hasAuth, hasAuth });
|
|
4555
4563
|
return;
|
|
4556
4564
|
}
|
|
4557
4565
|
if (req.method === "POST") {
|
|
@@ -41,6 +41,12 @@ export function initPrompts({
|
|
|
41
41
|
let _sysPromptCache = null;
|
|
42
42
|
let _sysPromptKey = "";
|
|
43
43
|
|
|
44
|
+
/** Clear the system prompt memoization cache (for test isolation). */
|
|
45
|
+
export function resetPromptCache() {
|
|
46
|
+
_sysPromptCache = null;
|
|
47
|
+
_sysPromptKey = "";
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
export function buildSystemPrompt(cfg) {
|
|
45
51
|
// Memoize — only rebuild when config files or agent prompts change
|
|
46
52
|
const keyParts = [cfg.providerKey, cfg.modelId, cfg.displayName];
|
|
@@ -137,7 +143,7 @@ export function buildSystemPrompt(cfg) {
|
|
|
137
143
|
"DEFAULT: CHAT. You are a conversational assistant first.",
|
|
138
144
|
"- Questions, explanations, status, clarifications, follow-ups, 'how does X work', 'what is X', 'can you', 'show me' → ANSWER. Never dispatch.",
|
|
139
145
|
"- Short messages under ~8 words ('i mean X', 'no, X', 'about X') → corrections, not directives. ANSWER.",
|
|
140
|
-
"- Greetings ('hi', 'yo', 'hey') → reply
|
|
146
|
+
"- Greetings ('hi', 'yo', 'hey', 'hello', 'sup', 'what's up') → reply with 1-2 SHORT sentences. Do NOT report system status, agent counts, RT bus, skills, or health data. Just be conversational. Only report health/status when the user EXPLICITLY asks ('status', 'health', 'how is the system', 'what's running').",
|
|
141
147
|
"- Self-audit requests (find bugs, review, inspect) → DO IT YOURSELF with @@READ_FILE / @@RUN_CMD. No asking permission. Only dispatch to crew-qa/crew-coder if user explicitly says 'have X do it'.",
|
|
142
148
|
"",
|
|
143
149
|
"DISPATCH: when user gives explicit action language.",
|
package/lib/crew-lead/tools.mjs
CHANGED
|
@@ -154,10 +154,11 @@ export async function execCrewLeadTools(reply) {
|
|
|
154
154
|
} catch { return null; }
|
|
155
155
|
})();
|
|
156
156
|
|
|
157
|
-
// Fallback: check search-tools.json
|
|
157
|
+
// Fallback: check search-tools.json (same dir as config file)
|
|
158
158
|
if (!braveKey) {
|
|
159
159
|
try {
|
|
160
|
-
const
|
|
160
|
+
const cfgDir = _crewswarmCfgFile ? path.dirname(_crewswarmCfgFile) : path.join(os.homedir(), ".crewswarm");
|
|
161
|
+
const searchTools = JSON.parse(fs.readFileSync(path.join(cfgDir, "search-tools.json"), "utf8"));
|
|
161
162
|
braveKey = searchTools.brave?.apiKey;
|
|
162
163
|
} catch {}
|
|
163
164
|
}
|
|
@@ -535,10 +535,12 @@ export function checkWaveQualityGate(pipeline, pipelineId) {
|
|
|
535
535
|
const hasWriteFile = /@@WRITE_FILE/.test(result);
|
|
536
536
|
const wroteFile = /(?:wrote|created|saved|written|updated|enhanced|implemented|added)\s+(?:to\s+)?(?:\/\S+|[a-zA-Z][\w/.-]+\.(?:html|css|js|ts|py|json|md|txt|sh|yaml|yml))/i.test(result);
|
|
537
537
|
const opencodeWrote = /(?:←\s*Write|Wrote file|Write\s+\.\.[\w/.-]+\.(?:html|css|js|ts|py|json|md)|Created\s+`\/)/i.test(result);
|
|
538
|
+
// Cursor outputs "`filename` is in place at:" or "Created the file at:\n`/path`"
|
|
539
|
+
const cursorInPlace = /`[\w/.-]+`\s+is\s+in\s+place\s+at|Created\s+the\s+file\s+at:/i.test(result);
|
|
538
540
|
const explicitDone = /Done\.\s+(?:Created|Updated|Enhanced|Implemented|The\s+(?:file|prototype|component))/i.test(result);
|
|
539
541
|
// OpenCode agents write silently — check if any src files changed in last 20 min as fallback
|
|
540
542
|
let opencodeFilesChanged = false;
|
|
541
|
-
if (!hasWriteFile && !wroteFile && !opencodeWrote && !explicitDone) {
|
|
543
|
+
if (!hasWriteFile && !wroteFile && !opencodeWrote && !cursorInPlace && !explicitDone) {
|
|
542
544
|
try {
|
|
543
545
|
const cutoff = Date.now() - 20 * 60 * 1000;
|
|
544
546
|
// Use dynamic project directory from pipeline metadata or environment
|
|
@@ -565,7 +567,7 @@ export function checkWaveQualityGate(pipeline, pipelineId) {
|
|
|
565
567
|
opencodeFilesChanged = dirs.some(checkDir);
|
|
566
568
|
} catch {}
|
|
567
569
|
}
|
|
568
|
-
if (!hasWriteFile && !wroteFile && !opencodeWrote && !explicitDone && !opencodeFilesChanged) {
|
|
570
|
+
if (!hasWriteFile && !wroteFile && !opencodeWrote && !cursorInPlace && !explicitDone && !opencodeFilesChanged) {
|
|
569
571
|
issues.push(`${agent} (builder) did not write any files`);
|
|
570
572
|
}
|
|
571
573
|
}
|
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, {
|
|
@@ -154,15 +154,9 @@ function evaluateShouldUse(engineDef, payload, incomingType) {
|
|
|
154
154
|
* Select engine: payload flags → payload runtime → per-agent crewswarm.json → fallback rules
|
|
155
155
|
*/
|
|
156
156
|
export function selectEngine(payload, incomingType) {
|
|
157
|
-
// PRIORITY 0: Chat messages always use direct LLM, not CLI engines.
|
|
158
|
-
// CLI engines (claude-code, cursor, codex, etc.) are only for coding tasks.
|
|
159
|
-
// The agent's configured model handles conversational replies directly.
|
|
160
157
|
const prompt = payload?.prompt || payload?.task || payload?.message || "";
|
|
161
|
-
if (!isCodingTask(incomingType, prompt, payload)) {
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
158
|
|
|
165
|
-
// PRIORITY 1: Payload flags (from enriched task)
|
|
159
|
+
// PRIORITY 1: Payload flags (from enriched task) — always respected regardless of prompt content
|
|
166
160
|
const explicitEngines = [
|
|
167
161
|
{ key: 'useCodex', id: 'codex' },
|
|
168
162
|
{ key: 'useCursor', id: 'cursor' },
|
|
@@ -195,7 +189,9 @@ export function selectEngine(payload, incomingType) {
|
|
|
195
189
|
}
|
|
196
190
|
}
|
|
197
191
|
|
|
198
|
-
// 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".
|
|
199
195
|
const preferredFromCfg = resolveAgentPreferredEngineId(payload);
|
|
200
196
|
if (preferredFromCfg) {
|
|
201
197
|
const preferredEngine = _engines.find((e) => e.id === preferredFromCfg);
|
|
@@ -204,7 +200,13 @@ export function selectEngine(payload, incomingType) {
|
|
|
204
200
|
}
|
|
205
201
|
}
|
|
206
202
|
|
|
207
|
-
// 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
|
|
208
210
|
const agentId = String(payload?.agentId || payload?.agent || "").toLowerCase();
|
|
209
211
|
if (agentId && _loadAgentList) {
|
|
210
212
|
try {
|
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 {
|
|
@@ -1214,15 +1227,23 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1214
1227
|
if (!which(CLAUDE_CODE_BIN)) {
|
|
1215
1228
|
throw new Error(`Claude Code CLI not found: "${CLAUDE_CODE_BIN}". Install with: npm i -g @anthropic-ai/claude-code`);
|
|
1216
1229
|
}
|
|
1230
|
+
if (!isClaudeOauthAuthenticated()) {
|
|
1231
|
+
console.error(`[ClaudeCode:${agentId}] auth preflight reported logged out; continuing and letting Claude decide at runtime.`);
|
|
1232
|
+
}
|
|
1217
1233
|
|
|
1218
1234
|
console.error(`[ClaudeCode:${agentId}] Running: ${CLAUDE_CODE_BIN} -p --dangerously-skip-permissions (cwd=${projectDir})`);
|
|
1219
1235
|
|
|
1220
1236
|
_rtClientForApprovals?.publish({ channel: "events", type: "agent_working", to: "broadcast",
|
|
1221
1237
|
payload: { agent: agentId, model: model || "claude/auto", ts: Date.now() } });
|
|
1222
1238
|
|
|
1239
|
+
const childEnv = { ...process.env };
|
|
1240
|
+
delete childEnv.ANTHROPIC_API_KEY;
|
|
1241
|
+
delete childEnv.CLAUDECODE;
|
|
1242
|
+
delete childEnv.CLAUDE_CODE;
|
|
1243
|
+
|
|
1223
1244
|
const child = spawn(CLAUDE_CODE_BIN, args, {
|
|
1224
1245
|
cwd: projectDir,
|
|
1225
|
-
env:
|
|
1246
|
+
env: childEnv,
|
|
1226
1247
|
stdio: ["ignore", "pipe", "pipe"], // Changed from "pipe" to "ignore" for stdin since we use args
|
|
1227
1248
|
});
|
|
1228
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
|
|
package/lib/runtime/spending.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from "fs";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import os from "os";
|
|
9
|
+
import { getConfigPath } from "./paths.mjs";
|
|
9
10
|
|
|
10
11
|
const SPENDING_FILE = path.join(os.homedir(), ".crewswarm", "spending.json");
|
|
11
12
|
const TOKEN_USAGE_FILE = path.join(os.homedir(), ".crewswarm", "token-usage.json");
|
|
@@ -86,7 +87,7 @@ export function addAgentSpend(agentId, tokens, costUSD) {
|
|
|
86
87
|
|
|
87
88
|
export function checkSpendingCap(agentId, providerKey) {
|
|
88
89
|
try {
|
|
89
|
-
const csw = JSON.parse(fs.readFileSync(
|
|
90
|
+
const csw = JSON.parse(fs.readFileSync(getConfigPath("crewswarm.json"), "utf8"));
|
|
90
91
|
const s = loadSpending();
|
|
91
92
|
const gl = csw.globalSpendingCaps || {};
|
|
92
93
|
if (gl.dailyTokenLimit && s.global.tokens >= gl.dailyTokenLimit)
|