crewswarm 0.9.2 → 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 +34 -4
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +55 -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 +15 -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 +5 -2
- package/lib/runtime/paths.mjs +12 -8
- 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/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
package/scripts/dashboard.mjs
CHANGED
|
@@ -30,9 +30,25 @@ import {
|
|
|
30
30
|
} from "../lib/agents/tool-instructions.mjs";
|
|
31
31
|
import {
|
|
32
32
|
StartBuildSchema,
|
|
33
|
+
EnhancePromptSchema,
|
|
33
34
|
StartPMLoopSchema,
|
|
34
35
|
ServiceActionSchema,
|
|
35
36
|
ImportSkillSchema,
|
|
37
|
+
AgentConfigCreateSchema,
|
|
38
|
+
AgentConfigDeleteSchema,
|
|
39
|
+
AgentResetSessionSchema,
|
|
40
|
+
ProviderAddSchema,
|
|
41
|
+
ProviderSaveSchema,
|
|
42
|
+
ProviderTestSchema,
|
|
43
|
+
ProviderBuiltinTestSchema,
|
|
44
|
+
ContinuousBuildSchema,
|
|
45
|
+
ReplayDLQSchema,
|
|
46
|
+
DeleteProjectSchema,
|
|
47
|
+
UpdateProjectSchema,
|
|
48
|
+
RoadmapWriteSchema,
|
|
49
|
+
RoadmapRetryFailedSchema,
|
|
50
|
+
ContactDeleteSchema,
|
|
51
|
+
ContactSendSchema,
|
|
36
52
|
validate,
|
|
37
53
|
} from "./dashboard-validation.mjs";
|
|
38
54
|
import { execCrewLeadTools } from "../lib/crew-lead/tools.mjs";
|
|
@@ -960,6 +976,233 @@ Keep the same intent; make it specific enough for a PM to break into small tasks
|
|
|
960
976
|
return content;
|
|
961
977
|
}
|
|
962
978
|
|
|
979
|
+
function getRtAuthToken() {
|
|
980
|
+
try {
|
|
981
|
+
return readSwarmConfigSafe()?.rt?.authToken || "";
|
|
982
|
+
} catch {
|
|
983
|
+
return "";
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function resolvePlannerEngine(preferredEngine = null, preferredModel = null) {
|
|
988
|
+
if (preferredEngine) {
|
|
989
|
+
return {
|
|
990
|
+
engine: preferredEngine,
|
|
991
|
+
model: preferredModel || null,
|
|
992
|
+
permissionMode: null,
|
|
993
|
+
sandbox: preferredEngine === "codex" ? "read-only" : null,
|
|
994
|
+
source: "request",
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const cfg = readSwarmConfigSafe();
|
|
999
|
+
const agents = Array.isArray(cfg?.agents) ? cfg.agents : [];
|
|
1000
|
+
const pm = agents.find((agent) => agent?.id === "crew-pm") || {};
|
|
1001
|
+
|
|
1002
|
+
if (pm.useClaudeCode) {
|
|
1003
|
+
return {
|
|
1004
|
+
engine: "claude",
|
|
1005
|
+
model: pm.claudeCodeModel || null,
|
|
1006
|
+
// Claude's plan mode can exit 0 with no streamed text for this planner path.
|
|
1007
|
+
// Use the normal direct lane here; the build-planner prompt already forbids edits.
|
|
1008
|
+
permissionMode: null,
|
|
1009
|
+
sandbox: null,
|
|
1010
|
+
source: "crew-pm",
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
if (pm.useCodex) {
|
|
1014
|
+
return {
|
|
1015
|
+
engine: "codex",
|
|
1016
|
+
model: pm.codexModel || null,
|
|
1017
|
+
permissionMode: null,
|
|
1018
|
+
sandbox: "read-only",
|
|
1019
|
+
source: "crew-pm",
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
if (pm.useCursorCli) {
|
|
1023
|
+
return {
|
|
1024
|
+
engine: "cursor",
|
|
1025
|
+
model: pm.cursorCliModel || null,
|
|
1026
|
+
permissionMode: null,
|
|
1027
|
+
sandbox: null,
|
|
1028
|
+
source: "crew-pm",
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
if (pm.useGeminiCli) {
|
|
1032
|
+
return {
|
|
1033
|
+
engine: "gemini",
|
|
1034
|
+
model: pm.geminiCliModel || null,
|
|
1035
|
+
permissionMode: null,
|
|
1036
|
+
sandbox: null,
|
|
1037
|
+
source: "crew-pm",
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (pm.useCrewCLI) {
|
|
1041
|
+
return {
|
|
1042
|
+
engine: "crew-cli",
|
|
1043
|
+
model: pm.crewCliModel || null,
|
|
1044
|
+
permissionMode: null,
|
|
1045
|
+
sandbox: null,
|
|
1046
|
+
source: "crew-pm",
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (pm.useOpenCode) {
|
|
1050
|
+
return {
|
|
1051
|
+
engine: "opencode",
|
|
1052
|
+
model: pm.opencodeModel || null,
|
|
1053
|
+
permissionMode: null,
|
|
1054
|
+
sandbox: null,
|
|
1055
|
+
source: "crew-pm",
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const fallbacks = [
|
|
1060
|
+
commandExists(process.env.CLAUDE_CODE_BIN || "claude") && { engine: "claude", model: process.env.CREWSWARM_CLAUDE_CODE_MODEL || null, permissionMode: null, sandbox: null, source: "fallback" },
|
|
1061
|
+
commandExists(process.env.CODEX_CLI_BIN || "codex") && { engine: "codex", model: process.env.CREWSWARM_CODEX_MODEL || null, permissionMode: null, sandbox: "read-only", source: "fallback" },
|
|
1062
|
+
commandExists(process.env.CURSOR_CLI_BIN || path.join(os.homedir(), ".local", "bin", "agent"), [path.join(os.homedir(), ".local", "bin", "agent")]) && { engine: "cursor", model: process.env.CREWSWARM_CURSOR_MODEL || process.env.CURSOR_DEFAULT_MODEL || null, permissionMode: null, sandbox: null, source: "fallback" },
|
|
1063
|
+
commandExists(process.env.GEMINI_CLI_BIN || "gemini") && { engine: "gemini", model: process.env.CREWSWARM_GEMINI_CLI_MODEL || null, permissionMode: null, sandbox: null, source: "fallback" },
|
|
1064
|
+
commandExists(process.env.CREWSWARM_OPENCODE_BIN || "opencode") && { engine: "opencode", model: process.env.CREWSWARM_OPENCODE_MODEL || null, permissionMode: null, sandbox: null, source: "fallback" },
|
|
1065
|
+
].filter(Boolean);
|
|
1066
|
+
|
|
1067
|
+
return fallbacks[0] || null;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function buildRequirementPlanningPrompt(userText, projectDir = null) {
|
|
1071
|
+
return [
|
|
1072
|
+
"You are the planning stage for CrewSwarm's Build tab.",
|
|
1073
|
+
"Transform the user's rough build idea into a concrete build brief that crew-pm can execute.",
|
|
1074
|
+
"If repository context is relevant, inspect the workspace before answering. Do not edit files.",
|
|
1075
|
+
"",
|
|
1076
|
+
"Output format:",
|
|
1077
|
+
"## Build Brief",
|
|
1078
|
+
"A 1-2 paragraph concrete requirement with explicit scope and deliverables.",
|
|
1079
|
+
"",
|
|
1080
|
+
"## Acceptance Criteria",
|
|
1081
|
+
"- 3 to 7 flat bullets",
|
|
1082
|
+
"",
|
|
1083
|
+
"## Constraints / Assumptions",
|
|
1084
|
+
"- Flat bullets only when needed",
|
|
1085
|
+
"",
|
|
1086
|
+
"Rules:",
|
|
1087
|
+
"- Preserve the user's intent; do not invent a different product.",
|
|
1088
|
+
"- Make it specific enough for PM decomposition and agent dispatch.",
|
|
1089
|
+
"- Mention likely subsystems or files only if you have evidence from the repo.",
|
|
1090
|
+
"- Keep it concise and actionable.",
|
|
1091
|
+
"- Do not include implementation code, shell commands, or extra commentary.",
|
|
1092
|
+
projectDir ? `- Current project directory: ${projectDir}` : "",
|
|
1093
|
+
"",
|
|
1094
|
+
"User idea:",
|
|
1095
|
+
userText.trim(),
|
|
1096
|
+
].filter(Boolean).join("\n");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async function collectClaudePlannerTextDirect({ message, projectDir, model = null }) {
|
|
1100
|
+
const claudeBin = resolveCommandPath(process.env.CLAUDE_CODE_BIN || "claude", [
|
|
1101
|
+
path.join(os.homedir(), ".local", "bin", "claude"),
|
|
1102
|
+
"/usr/local/bin/claude",
|
|
1103
|
+
"/opt/homebrew/bin/claude",
|
|
1104
|
+
]) || (process.env.CLAUDE_CODE_BIN || "claude");
|
|
1105
|
+
const { execFile } = await import("node:child_process");
|
|
1106
|
+
const { promisify } = await import("node:util");
|
|
1107
|
+
const execFileAsync = promisify(execFile);
|
|
1108
|
+
const args = ["-p"];
|
|
1109
|
+
if (projectDir) args.push("--add-dir", projectDir);
|
|
1110
|
+
if (model) args.push("--model", model);
|
|
1111
|
+
// Match the fixed engine-passthrough Claude invocation:
|
|
1112
|
+
// skip user MCP startup and terminate option parsing before the prompt.
|
|
1113
|
+
args.push(
|
|
1114
|
+
"--strict-mcp-config",
|
|
1115
|
+
"--mcp-config",
|
|
1116
|
+
path.join(os.homedir(), ".crewswarm", "config", "empty-mcp.json"),
|
|
1117
|
+
"--",
|
|
1118
|
+
message,
|
|
1119
|
+
);
|
|
1120
|
+
const { stdout, stderr } = await execFileAsync(claudeBin, args, {
|
|
1121
|
+
cwd: projectDir || process.cwd(),
|
|
1122
|
+
env: process.env,
|
|
1123
|
+
timeout: Number(process.env.CREWSWARM_PLANNER_TIMEOUT_MS || 300000),
|
|
1124
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
1125
|
+
});
|
|
1126
|
+
const trimmed = String(stdout || "").trim();
|
|
1127
|
+
if (trimmed) return trimmed;
|
|
1128
|
+
const stderrText = String(stderr || "").trim();
|
|
1129
|
+
if (stderrText) throw new Error(stderrText);
|
|
1130
|
+
throw new Error("planner produced no output");
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async function collectPassthroughText({
|
|
1134
|
+
engine,
|
|
1135
|
+
message,
|
|
1136
|
+
projectDir,
|
|
1137
|
+
model = null,
|
|
1138
|
+
permissionMode = null,
|
|
1139
|
+
sandbox = null,
|
|
1140
|
+
forceL2 = false,
|
|
1141
|
+
}) {
|
|
1142
|
+
if (engine === "claude") {
|
|
1143
|
+
return collectClaudePlannerTextDirect({ message, projectDir, model });
|
|
1144
|
+
}
|
|
1145
|
+
const token = getRtAuthToken();
|
|
1146
|
+
const crewLeadPort = process.env.CREW_LEAD_PORT || "5010";
|
|
1147
|
+
let upstream;
|
|
1148
|
+
try {
|
|
1149
|
+
upstream = await fetch(`http://127.0.0.1:${crewLeadPort}/api/engine-passthrough`, {
|
|
1150
|
+
method: "POST",
|
|
1151
|
+
headers: {
|
|
1152
|
+
"content-type": "application/json",
|
|
1153
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
1154
|
+
"x-passthrough-continue": "false",
|
|
1155
|
+
},
|
|
1156
|
+
body: JSON.stringify({
|
|
1157
|
+
engine,
|
|
1158
|
+
message,
|
|
1159
|
+
projectDir: projectDir || process.cwd(),
|
|
1160
|
+
sessionId: "build-planner",
|
|
1161
|
+
...(model ? { model } : {}),
|
|
1162
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
1163
|
+
...(sandbox ? { sandbox } : {}),
|
|
1164
|
+
...(forceL2 ? { forceL2: true } : {}),
|
|
1165
|
+
}),
|
|
1166
|
+
signal: AbortSignal.timeout(300_000),
|
|
1167
|
+
});
|
|
1168
|
+
} catch (fetchErr) {
|
|
1169
|
+
throw new Error(`planner fetch failed for ${engine}: ${fetchErr.message}`);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (!upstream.ok) {
|
|
1173
|
+
throw new Error(`planner upstream ${upstream.status}`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
let rawSSE;
|
|
1177
|
+
try {
|
|
1178
|
+
rawSSE = await upstream.text();
|
|
1179
|
+
} catch (readErr) {
|
|
1180
|
+
throw new Error(`planner SSE read failed for ${engine}: ${readErr.message}`);
|
|
1181
|
+
}
|
|
1182
|
+
let text = "";
|
|
1183
|
+
let stderr = "";
|
|
1184
|
+
let exitCode = 0;
|
|
1185
|
+
|
|
1186
|
+
for (const line of rawSSE.split("\n")) {
|
|
1187
|
+
if (!line.startsWith("data: ")) continue;
|
|
1188
|
+
try {
|
|
1189
|
+
const ev = JSON.parse(line.slice(6));
|
|
1190
|
+
if (ev.type === "chunk" && ev.text) text += ev.text;
|
|
1191
|
+
else if (ev.type === "stderr" && ev.text) stderr += ev.text;
|
|
1192
|
+
else if (ev.type === "done") exitCode = ev.exitCode ?? 0;
|
|
1193
|
+
} catch {}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const trimmed = text.trim();
|
|
1197
|
+
if (!trimmed && stderr.trim()) {
|
|
1198
|
+
throw new Error(stderr.trim());
|
|
1199
|
+
}
|
|
1200
|
+
if (!trimmed) {
|
|
1201
|
+
throw new Error(`planner produced no output${exitCode ? ` (exit ${exitCode})` : ""}`);
|
|
1202
|
+
}
|
|
1203
|
+
return trimmed;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
963
1206
|
async function getPhasedProgress(limit = 80) {
|
|
964
1207
|
const { readFile } = await import("node:fs/promises");
|
|
965
1208
|
const { existsSync } = await import("node:fs");
|
|
@@ -1908,7 +2151,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1908
2151
|
return;
|
|
1909
2152
|
}
|
|
1910
2153
|
|
|
1911
|
-
// ── Auth Token (for
|
|
2154
|
+
// ── Auth Token (for Vibe) ─────────────────────────────────────────────────────
|
|
1912
2155
|
if (url.pathname === "/api/auth/token") {
|
|
1913
2156
|
res.writeHead(200, {
|
|
1914
2157
|
"content-type": "application/json",
|
|
@@ -2335,24 +2578,76 @@ const server = http.createServer(async (req, res) => {
|
|
|
2335
2578
|
if (url.pathname === "/api/enhance-prompt" && req.method === "POST") {
|
|
2336
2579
|
let body = "";
|
|
2337
2580
|
for await (const chunk of req) body += chunk;
|
|
2338
|
-
|
|
2339
|
-
|
|
2581
|
+
let parsed;
|
|
2582
|
+
try { parsed = JSON.parse(body || "{}"); } catch {
|
|
2583
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
2584
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
const vr = validate(EnhancePromptSchema, parsed);
|
|
2588
|
+
if (!vr.ok) {
|
|
2340
2589
|
res.writeHead(400, { "content-type": "application/json" });
|
|
2341
|
-
res.end(JSON.stringify({ error:
|
|
2590
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
2342
2591
|
return;
|
|
2343
2592
|
}
|
|
2593
|
+
const { text, projectId, engine: requestedEngine, model: requestedModel } = vr.data;
|
|
2344
2594
|
try {
|
|
2345
|
-
|
|
2595
|
+
// Default to cwd for planner context — engines need repo access to produce aware briefs.
|
|
2596
|
+
// Claude/Cursor/Codex use --add-dir for safe read access; crew-cli uses --project.
|
|
2597
|
+
// Fall back to temp dir only if no project context is available.
|
|
2598
|
+
let projectDir = process.cwd();
|
|
2599
|
+
if (projectId) {
|
|
2600
|
+
const regPath = path.join(CFG_DIR, "projects.json");
|
|
2601
|
+
if (fs.existsSync(regPath)) {
|
|
2602
|
+
const reg = JSON.parse(fs.readFileSync(regPath, "utf8") || "{}");
|
|
2603
|
+
const proj = reg[projectId];
|
|
2604
|
+
if (proj?.outputDir) projectDir = proj.outputDir;
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
2608
|
+
|
|
2609
|
+
const planner = resolvePlannerEngine(requestedEngine, requestedModel);
|
|
2610
|
+
if (!planner) throw new Error("No planning engine is configured or installed");
|
|
2611
|
+
|
|
2612
|
+
const planned = await collectPassthroughText({
|
|
2613
|
+
engine: planner.engine,
|
|
2614
|
+
message: buildRequirementPlanningPrompt(text, projectDir),
|
|
2615
|
+
projectDir,
|
|
2616
|
+
model: planner.model,
|
|
2617
|
+
permissionMode: planner.permissionMode,
|
|
2618
|
+
sandbox: planner.sandbox,
|
|
2619
|
+
forceL2: planner.engine === "crew-cli",
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2346
2622
|
res.writeHead(200, { "content-type": "application/json" });
|
|
2347
|
-
res.end(JSON.stringify({
|
|
2623
|
+
res.end(JSON.stringify({
|
|
2624
|
+
enhanced: planned,
|
|
2625
|
+
engine: planner.engine,
|
|
2626
|
+
model: planner.model,
|
|
2627
|
+
mode: planner.permissionMode || planner.sandbox || "prompt",
|
|
2628
|
+
source: planner.source,
|
|
2629
|
+
}));
|
|
2348
2630
|
} catch (err) {
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
enhanced
|
|
2354
|
-
|
|
2355
|
-
|
|
2631
|
+
try {
|
|
2632
|
+
const enhanced = await enhancePromptWithGroq(text);
|
|
2633
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
2634
|
+
res.end(JSON.stringify({
|
|
2635
|
+
enhanced,
|
|
2636
|
+
engine: "groq",
|
|
2637
|
+
model: "llama-3.3-70b-versatile",
|
|
2638
|
+
mode: "fallback-rewrite",
|
|
2639
|
+
source: "fallback",
|
|
2640
|
+
warning: err?.message || String(err),
|
|
2641
|
+
}));
|
|
2642
|
+
} catch (fallbackErr) {
|
|
2643
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
2644
|
+
res.end(
|
|
2645
|
+
JSON.stringify({
|
|
2646
|
+
error: fallbackErr?.message || err?.message || String(fallbackErr || err),
|
|
2647
|
+
enhanced: null,
|
|
2648
|
+
}),
|
|
2649
|
+
);
|
|
2650
|
+
}
|
|
2356
2651
|
}
|
|
2357
2652
|
return;
|
|
2358
2653
|
}
|
|
@@ -2457,9 +2752,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
2457
2752
|
if (url.pathname === "/api/continuous-build" && req.method === "POST") {
|
|
2458
2753
|
let body = "";
|
|
2459
2754
|
for await (const chunk of req) body += chunk;
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2755
|
+
let parsed;
|
|
2756
|
+
try { parsed = JSON.parse(body || "{}"); } catch {
|
|
2757
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
2758
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
const vr = validate(ContinuousBuildSchema, parsed);
|
|
2762
|
+
if (!vr.ok) {
|
|
2763
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
2764
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
const { requirement, projectId } = vr.data;
|
|
2463
2768
|
let projectEnv = {};
|
|
2464
2769
|
if (projectId) {
|
|
2465
2770
|
const { existsSync: ex } = await import("node:fs");
|
|
@@ -3097,8 +3402,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
3097
3402
|
if (url.pathname === "/api/projects/delete" && req.method === "POST") {
|
|
3098
3403
|
let body = "";
|
|
3099
3404
|
for await (const chunk of req) body += chunk;
|
|
3100
|
-
|
|
3101
|
-
|
|
3405
|
+
let parsed;
|
|
3406
|
+
try { parsed = JSON.parse(body || "{}"); } catch {
|
|
3407
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3408
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
const vr = validate(DeleteProjectSchema, parsed);
|
|
3412
|
+
if (!vr.ok) {
|
|
3413
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3414
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
const { projectId } = vr.data;
|
|
3102
3418
|
const registryFile = path.join(CFG_DIR, "projects.json");
|
|
3103
3419
|
const { existsSync, rmSync, writeFileSync, unlinkSync } = await import("node:fs");
|
|
3104
3420
|
const { readFile: rf, writeFile: wf } = await import("node:fs/promises");
|
|
@@ -3146,9 +3462,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
3146
3462
|
if (url.pathname === "/api/projects/update" && req.method === "POST") {
|
|
3147
3463
|
let body = "";
|
|
3148
3464
|
for await (const chunk of req) body += chunk;
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3465
|
+
let parsed;
|
|
3466
|
+
try { parsed = JSON.parse(body || "{}"); } catch {
|
|
3467
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3468
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
const vr = validate(UpdateProjectSchema, parsed);
|
|
3472
|
+
if (!vr.ok) {
|
|
3473
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3474
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
const { projectId, autoAdvance, name, description, outputDir } = vr.data;
|
|
3152
3478
|
const registryFile = path.join(CFG_DIR, "projects.json");
|
|
3153
3479
|
const { existsSync } = await import("node:fs");
|
|
3154
3480
|
const { readFile: rf, writeFile: wf } = await import("node:fs/promises");
|
|
@@ -3423,8 +3749,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
3423
3749
|
if (url.pathname === "/api/dlq/replay" && req.method === "POST") {
|
|
3424
3750
|
let body = "";
|
|
3425
3751
|
for await (const chunk of req) body += chunk;
|
|
3426
|
-
|
|
3427
|
-
|
|
3752
|
+
let parsed;
|
|
3753
|
+
try { parsed = JSON.parse(body); } catch {
|
|
3754
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3755
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
const vr = validate(ReplayDLQSchema, parsed);
|
|
3759
|
+
if (!vr.ok) {
|
|
3760
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
3761
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
const { key } = vr.data;
|
|
3428
3765
|
const { execSync } = await import("node:child_process");
|
|
3429
3766
|
execSync(`"${ctlPath}" dlq-replay "${key}"`, {
|
|
3430
3767
|
encoding: "utf8",
|
|
@@ -3779,7 +4116,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
3779
4116
|
) {
|
|
3780
4117
|
let body = "";
|
|
3781
4118
|
for await (const chunk of req) body += chunk;
|
|
3782
|
-
|
|
4119
|
+
let parsed;
|
|
4120
|
+
try { parsed = JSON.parse(body); } catch {
|
|
4121
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
4122
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
const vr = validate(ProviderBuiltinTestSchema, parsed);
|
|
4126
|
+
if (!vr.ok) {
|
|
4127
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
4128
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
4129
|
+
return;
|
|
4130
|
+
}
|
|
4131
|
+
const { providerId } = vr.data;
|
|
3783
4132
|
const apiKey = getBuiltinKey(providerId);
|
|
3784
4133
|
const baseUrl = BUILTIN_URLS[providerId] || "";
|
|
3785
4134
|
if (providerId === "ollama") {
|
|
@@ -4690,7 +5039,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4690
5039
|
return;
|
|
4691
5040
|
}
|
|
4692
5041
|
|
|
4693
|
-
// ── Models API (list available models for
|
|
5042
|
+
// ── Models API (list available models for crewchat dropdown) ─────────────
|
|
4694
5043
|
if (url.pathname === "/api/models" && req.method === "GET") {
|
|
4695
5044
|
try {
|
|
4696
5045
|
const csSwarm = JSON.parse(
|
|
@@ -4780,7 +5129,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
4780
5129
|
return;
|
|
4781
5130
|
}
|
|
4782
5131
|
|
|
4783
|
-
// ── CLI Chat API (
|
|
5132
|
+
// ── CLI Chat API (crewchat CLI mode passthrough) ──────────────────────
|
|
4784
5133
|
if (url.pathname === "/api/cli/chat" && req.method === "POST") {
|
|
4785
5134
|
let body = "";
|
|
4786
5135
|
for await (const chunk of req) body += chunk;
|
|
@@ -5020,7 +5369,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5020
5369
|
return;
|
|
5021
5370
|
}
|
|
5022
5371
|
|
|
5023
|
-
// ── Chat Agent API (
|
|
5372
|
+
// ── Chat Agent API (crewchat direct agent chat) ──────────────────────────
|
|
5024
5373
|
if (url.pathname === "/api/chat-agent" && req.method === "POST") {
|
|
5025
5374
|
let body = "";
|
|
5026
5375
|
for await (const chunk of req) body += chunk;
|
|
@@ -5068,7 +5417,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5068
5417
|
return;
|
|
5069
5418
|
}
|
|
5070
5419
|
|
|
5071
|
-
// ── Dispatch API (
|
|
5420
|
+
// ── Dispatch API (crewchat agent direct mode) ─────────────────────────────
|
|
5072
5421
|
if (url.pathname === "/api/dispatch" && req.method === "POST") {
|
|
5073
5422
|
let body = "";
|
|
5074
5423
|
for await (const chunk of req) body += chunk;
|
|
@@ -5139,7 +5488,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5139
5488
|
}
|
|
5140
5489
|
|
|
5141
5490
|
if (url.pathname === "/api/transcribe-audio" && req.method === "POST") {
|
|
5142
|
-
// Expects multipart/form-data with audio file (
|
|
5491
|
+
// Expects multipart/form-data with audio file (crewchat: m4a, Dashboard: webm)
|
|
5143
5492
|
// Per Groq docs: https://console.groq.com/docs/speech-to-text — file, model required
|
|
5144
5493
|
const sendJson = (status, body) => {
|
|
5145
5494
|
if (res.headersSent) return;
|
|
@@ -5149,7 +5498,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5149
5498
|
try {
|
|
5150
5499
|
const busboy = await import("busboy");
|
|
5151
5500
|
const chunks = [];
|
|
5152
|
-
let mimeType = "audio/m4a"; //
|
|
5501
|
+
let mimeType = "audio/m4a"; // crewchat default
|
|
5153
5502
|
let resolved = false;
|
|
5154
5503
|
const resolveOnce = () => {
|
|
5155
5504
|
if (resolved) return;
|
|
@@ -5371,7 +5720,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5371
5720
|
}
|
|
5372
5721
|
|
|
5373
5722
|
if (wantAgentSSE && upstream.body) {
|
|
5374
|
-
// SSE streaming path (Vibe
|
|
5723
|
+
// SSE streaming path (Dashboard or Vibe)
|
|
5375
5724
|
res.writeHead(200, {
|
|
5376
5725
|
"content-type": "text/event-stream",
|
|
5377
5726
|
"cache-control": "no-cache",
|
|
@@ -5471,7 +5820,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
5471
5820
|
}
|
|
5472
5821
|
|
|
5473
5822
|
if (wantSSE && upstream.body) {
|
|
5474
|
-
// SSE streaming path (Vibe
|
|
5823
|
+
// SSE streaming path (Dashboard or Vibe)
|
|
5475
5824
|
res.writeHead(200, {
|
|
5476
5825
|
"content-type": "text/event-stream",
|
|
5477
5826
|
"cache-control": "no-cache",
|
|
@@ -5820,9 +6169,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
5820
6169
|
const { readFile, writeFile } = await import("node:fs/promises");
|
|
5821
6170
|
let body = "";
|
|
5822
6171
|
for await (const chunk of req) body += chunk;
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
6172
|
+
let parsed;
|
|
6173
|
+
try { parsed = JSON.parse(body); } catch {
|
|
6174
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6175
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
6176
|
+
return;
|
|
6177
|
+
}
|
|
6178
|
+
const vr = validate(ProviderSaveSchema, parsed);
|
|
6179
|
+
if (!vr.ok) {
|
|
6180
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6181
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
6182
|
+
return;
|
|
6183
|
+
}
|
|
6184
|
+
const { providerId, apiKey } = vr.data;
|
|
5826
6185
|
const cfgPath = CFG_FILE;
|
|
5827
6186
|
const cfg = JSON.parse(await readFile(cfgPath, "utf8"));
|
|
5828
6187
|
const fromModels = cfg?.models?.providers?.[providerId];
|
|
@@ -5870,8 +6229,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
5870
6229
|
const { readFile, writeFile } = await import("node:fs/promises");
|
|
5871
6230
|
let body = "";
|
|
5872
6231
|
for await (const chunk of req) body += chunk;
|
|
5873
|
-
|
|
5874
|
-
|
|
6232
|
+
let parsed;
|
|
6233
|
+
try { parsed = JSON.parse(body); } catch {
|
|
6234
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6235
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
const vr = validate(ProviderAddSchema, parsed);
|
|
6239
|
+
if (!vr.ok) {
|
|
6240
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6241
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
6242
|
+
return;
|
|
6243
|
+
}
|
|
6244
|
+
const { id, baseUrl, apiKey, api } = vr.data;
|
|
5875
6245
|
const cfgPath = CFG_FILE;
|
|
5876
6246
|
const cfg = JSON.parse(await readFile(cfgPath, "utf8"));
|
|
5877
6247
|
if (!cfg.models) cfg.models = {};
|
|
@@ -6191,7 +6561,19 @@ const server = http.createServer(async (req, res) => {
|
|
|
6191
6561
|
if (url.pathname === "/api/providers/test" && req.method === "POST") {
|
|
6192
6562
|
let body = "";
|
|
6193
6563
|
for await (const chunk of req) body += chunk;
|
|
6194
|
-
|
|
6564
|
+
let parsed;
|
|
6565
|
+
try { parsed = JSON.parse(body); } catch {
|
|
6566
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6567
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
6568
|
+
return;
|
|
6569
|
+
}
|
|
6570
|
+
const vr = validate(ProviderTestSchema, parsed);
|
|
6571
|
+
if (!vr.ok) {
|
|
6572
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
6573
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
6574
|
+
return;
|
|
6575
|
+
}
|
|
6576
|
+
const { providerId } = vr.data;
|
|
6195
6577
|
const { readFile } = await import("node:fs/promises");
|
|
6196
6578
|
const cfgPath = CFG_FILE;
|
|
6197
6579
|
const cfg = JSON.parse(await readFile(cfgPath, "utf8").catch(() => "{}"));
|
|
@@ -7204,6 +7586,18 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7204
7586
|
const { readFile, writeFile } = await import("node:fs/promises");
|
|
7205
7587
|
let body = "";
|
|
7206
7588
|
for await (const chunk of req) body += chunk;
|
|
7589
|
+
let parsed;
|
|
7590
|
+
try { parsed = JSON.parse(body); } catch {
|
|
7591
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7592
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
7593
|
+
return;
|
|
7594
|
+
}
|
|
7595
|
+
const vr = validate(AgentConfigCreateSchema, parsed);
|
|
7596
|
+
if (!vr.ok) {
|
|
7597
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7598
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
7599
|
+
return;
|
|
7600
|
+
}
|
|
7207
7601
|
const {
|
|
7208
7602
|
id,
|
|
7209
7603
|
model,
|
|
@@ -7212,7 +7606,7 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7212
7606
|
theme,
|
|
7213
7607
|
systemPrompt,
|
|
7214
7608
|
alsoAllow: reqAlsoAllow,
|
|
7215
|
-
} =
|
|
7609
|
+
} = vr.data;
|
|
7216
7610
|
const rawId = String(id || "");
|
|
7217
7611
|
const normalizedId =
|
|
7218
7612
|
rawId && !rawId.startsWith("crew-")
|
|
@@ -7302,8 +7696,19 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7302
7696
|
const { readFile, writeFile } = await import("node:fs/promises");
|
|
7303
7697
|
let body = "";
|
|
7304
7698
|
for await (const chunk of req) body += chunk;
|
|
7305
|
-
|
|
7306
|
-
|
|
7699
|
+
let parsed;
|
|
7700
|
+
try { parsed = JSON.parse(body); } catch {
|
|
7701
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7702
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
const vr = validate(AgentConfigDeleteSchema, parsed);
|
|
7706
|
+
if (!vr.ok) {
|
|
7707
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7708
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
7709
|
+
return;
|
|
7710
|
+
}
|
|
7711
|
+
const { agentId } = vr.data;
|
|
7307
7712
|
const cfgPath = CFG_FILE;
|
|
7308
7713
|
const promptsPath = path.join(CFG_DIR, "agent-prompts.json");
|
|
7309
7714
|
const cfg = JSON.parse(await readFile(cfgPath, "utf8"));
|
|
@@ -7351,12 +7756,19 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7351
7756
|
) {
|
|
7352
7757
|
let body = "";
|
|
7353
7758
|
for await (const chunk of req) body += chunk;
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
res.writeHead(400);
|
|
7357
|
-
res.end(JSON.stringify({ error: "
|
|
7759
|
+
let parsed;
|
|
7760
|
+
try { parsed = JSON.parse(body || "{}"); } catch {
|
|
7761
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7762
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
7763
|
+
return;
|
|
7764
|
+
}
|
|
7765
|
+
const vr = validate(AgentResetSessionSchema, parsed);
|
|
7766
|
+
if (!vr.ok) {
|
|
7767
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7768
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
7358
7769
|
return;
|
|
7359
7770
|
}
|
|
7771
|
+
const { agentId } = vr.data;
|
|
7360
7772
|
const { execFile } = await import("node:child_process");
|
|
7361
7773
|
const bridgePath = path.join(CREWSWARM_DIR, "gateway-bridge.mjs");
|
|
7362
7774
|
// 1. Reset the agent session via gateway-bridge --reset-session
|
|
@@ -7434,9 +7846,19 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7434
7846
|
const { writeFile } = await import("node:fs/promises");
|
|
7435
7847
|
let body = "";
|
|
7436
7848
|
for await (const chunk of req) body += chunk;
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7849
|
+
let parsed;
|
|
7850
|
+
try { parsed = JSON.parse(body); } catch {
|
|
7851
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7852
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
7853
|
+
return;
|
|
7854
|
+
}
|
|
7855
|
+
const vr = validate(RoadmapWriteSchema, parsed);
|
|
7856
|
+
if (!vr.ok) {
|
|
7857
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7858
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
7859
|
+
return;
|
|
7860
|
+
}
|
|
7861
|
+
const { roadmapFile, content } = vr.data;
|
|
7440
7862
|
await writeFile(roadmapFile, content, "utf8");
|
|
7441
7863
|
res.writeHead(200, { "content-type": "application/json" });
|
|
7442
7864
|
res.end(JSON.stringify({ ok: true }));
|
|
@@ -7448,8 +7870,19 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
7448
7870
|
const { readFile, writeFile } = await import("node:fs/promises");
|
|
7449
7871
|
let body = "";
|
|
7450
7872
|
for await (const chunk of req) body += chunk;
|
|
7451
|
-
|
|
7452
|
-
|
|
7873
|
+
let parsed;
|
|
7874
|
+
try { parsed = JSON.parse(body); } catch {
|
|
7875
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7876
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
7877
|
+
return;
|
|
7878
|
+
}
|
|
7879
|
+
const vr = validate(RoadmapRetryFailedSchema, parsed);
|
|
7880
|
+
if (!vr.ok) {
|
|
7881
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
7882
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
7883
|
+
return;
|
|
7884
|
+
}
|
|
7885
|
+
const { roadmapFile } = vr.data;
|
|
7453
7886
|
const content = await readFile(roadmapFile, "utf8");
|
|
7454
7887
|
// Strip [!] markers back to [ ] and remove failure timestamps
|
|
7455
7888
|
const reset = content
|
|
@@ -8117,11 +8550,22 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8117
8550
|
}
|
|
8118
8551
|
|
|
8119
8552
|
if (url.pathname === "/api/contacts/delete" && req.method === "POST") {
|
|
8553
|
+
let raw = "";
|
|
8554
|
+
for await (const chunk of req) raw += chunk;
|
|
8555
|
+
let parsed;
|
|
8556
|
+
try { parsed = JSON.parse(raw || "{}"); } catch {
|
|
8557
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
8558
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
8559
|
+
return;
|
|
8560
|
+
}
|
|
8561
|
+
const vr = validate(ContactDeleteSchema, parsed);
|
|
8562
|
+
if (!vr.ok) {
|
|
8563
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
8564
|
+
res.end(JSON.stringify({ ok: false, error: vr.error }));
|
|
8565
|
+
return;
|
|
8566
|
+
}
|
|
8567
|
+
const { contactId } = vr.data;
|
|
8120
8568
|
try {
|
|
8121
|
-
let raw = "";
|
|
8122
|
-
for await (const chunk of req) raw += chunk;
|
|
8123
|
-
const { contactId } = JSON.parse(raw || "{}");
|
|
8124
|
-
if (!contactId) throw new Error("contactId required");
|
|
8125
8569
|
const { deleteContact } = await import("../lib/contacts/index.mjs");
|
|
8126
8570
|
deleteContact(contactId);
|
|
8127
8571
|
res.writeHead(200, { "content-type": "application/json" });
|
|
@@ -8134,12 +8578,22 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8134
8578
|
}
|
|
8135
8579
|
|
|
8136
8580
|
if (url.pathname === "/api/contacts/send" && req.method === "POST") {
|
|
8581
|
+
let raw = "";
|
|
8582
|
+
for await (const chunk of req) raw += chunk;
|
|
8583
|
+
let parsedSend;
|
|
8584
|
+
try { parsedSend = JSON.parse(raw || "{}"); } catch {
|
|
8585
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
8586
|
+
res.end(JSON.stringify({ ok: false, error: "Invalid JSON" }));
|
|
8587
|
+
return;
|
|
8588
|
+
}
|
|
8589
|
+
const vrSend = validate(ContactSendSchema, parsedSend);
|
|
8590
|
+
if (!vrSend.ok) {
|
|
8591
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
8592
|
+
res.end(JSON.stringify({ ok: false, error: vrSend.error }));
|
|
8593
|
+
return;
|
|
8594
|
+
}
|
|
8595
|
+
const { contactId, platform, message } = vrSend.data;
|
|
8137
8596
|
try {
|
|
8138
|
-
let raw = "";
|
|
8139
|
-
for await (const chunk of req) raw += chunk;
|
|
8140
|
-
const { contactId, platform, message } = JSON.parse(raw || "{}");
|
|
8141
|
-
if (!contactId || !message)
|
|
8142
|
-
throw new Error("contactId and message required");
|
|
8143
8597
|
|
|
8144
8598
|
const { getContact } = await import("../lib/contacts/index.mjs");
|
|
8145
8599
|
const contact = getContact(contactId);
|
|
@@ -8155,12 +8609,19 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8155
8609
|
: platformLinks.whatsapp;
|
|
8156
8610
|
if (waJid) {
|
|
8157
8611
|
// Call WhatsApp bridge's sendMessage function
|
|
8158
|
-
const waUrl = `http://127.0.0.1:${process.env.WA_HTTP_PORT ||
|
|
8159
|
-
await fetch(waUrl, {
|
|
8612
|
+
const waUrl = `http://127.0.0.1:${process.env.WA_HTTP_PORT || 5015}/send`;
|
|
8613
|
+
const waRes = await fetch(waUrl, {
|
|
8160
8614
|
method: "POST",
|
|
8161
8615
|
headers: { "content-type": "application/json" },
|
|
8162
|
-
body: JSON.stringify({ jid: waJid, message }),
|
|
8616
|
+
body: JSON.stringify({ jid: waJid, text: message }),
|
|
8163
8617
|
});
|
|
8618
|
+
const waData = await waRes.json().catch(() => ({}));
|
|
8619
|
+
if (!waRes.ok || !waData.ok) {
|
|
8620
|
+
throw new Error(
|
|
8621
|
+
waData.error ||
|
|
8622
|
+
`WhatsApp send failed (${waRes.status})`,
|
|
8623
|
+
);
|
|
8624
|
+
}
|
|
8164
8625
|
}
|
|
8165
8626
|
}
|
|
8166
8627
|
|
|
@@ -8179,7 +8640,7 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8179
8640
|
const tgCfg = JSON.parse(fs.readFileSync(TG_CONFIG_PATH, "utf8"));
|
|
8180
8641
|
const botToken = tgCfg.token;
|
|
8181
8642
|
if (botToken) {
|
|
8182
|
-
await fetch(
|
|
8643
|
+
const tgRes = await fetch(
|
|
8183
8644
|
`https://api.telegram.org/bot${botToken}/sendMessage`,
|
|
8184
8645
|
{
|
|
8185
8646
|
method: "POST",
|
|
@@ -8187,6 +8648,14 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8187
8648
|
body: JSON.stringify({ chat_id: tgChatId, text: message }),
|
|
8188
8649
|
},
|
|
8189
8650
|
);
|
|
8651
|
+
const tgData = await tgRes.json().catch(() => ({}));
|
|
8652
|
+
if (!tgRes.ok || !tgData.ok) {
|
|
8653
|
+
throw new Error(
|
|
8654
|
+
tgData.description ||
|
|
8655
|
+
tgData.error ||
|
|
8656
|
+
`Telegram send failed (${tgRes.status})`,
|
|
8657
|
+
);
|
|
8658
|
+
}
|
|
8190
8659
|
}
|
|
8191
8660
|
}
|
|
8192
8661
|
}
|
|
@@ -8458,7 +8927,7 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8458
8927
|
{
|
|
8459
8928
|
id: "crew-lead",
|
|
8460
8929
|
label: "crew-lead",
|
|
8461
|
-
description: "Chat commander — dashboard chat,
|
|
8930
|
+
description: "Chat commander — dashboard chat, crewchat, Telegram",
|
|
8462
8931
|
port: crewLeadPort,
|
|
8463
8932
|
running: crewLeadUp,
|
|
8464
8933
|
canRestart: true,
|
|
@@ -8590,7 +9059,7 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8590
9059
|
label: "Vibe UI",
|
|
8591
9060
|
description: studioUp
|
|
8592
9061
|
? "Monaco editor + agent chat — Cursor-like IDE for crewswarm"
|
|
8593
|
-
: "Run: npm run
|
|
9062
|
+
: "Run: npm run vibe:start (port 3333)",
|
|
8594
9063
|
port: 3333,
|
|
8595
9064
|
running: studioUp,
|
|
8596
9065
|
canRestart: true,
|
|
@@ -8602,7 +9071,7 @@ ORDER BY day DESC, cost DESC;`;
|
|
|
8602
9071
|
label: "Vibe Watch Server",
|
|
8603
9072
|
description: watchUp
|
|
8604
9073
|
? "CLI → Vibe live reload WebSocket relay (port 3334)"
|
|
8605
|
-
: "Run: npm run
|
|
9074
|
+
: "Run: npm run vibe:watch — enables live file reload in Vibe",
|
|
8606
9075
|
port: 3334,
|
|
8607
9076
|
running: watchUp,
|
|
8608
9077
|
canRestart: true,
|
|
@@ -9683,14 +10152,35 @@ if (process.argv.includes("--print-html")) {
|
|
|
9683
10152
|
}
|
|
9684
10153
|
|
|
9685
10154
|
process.on("uncaughtException", (err) => {
|
|
10155
|
+
const msg = String(err?.message || err || "");
|
|
9686
10156
|
console.error(
|
|
9687
10157
|
"[dashboard] uncaughtException:",
|
|
9688
|
-
err?.stack ||
|
|
10158
|
+
err?.stack || msg,
|
|
9689
10159
|
);
|
|
9690
10160
|
|
|
9691
|
-
//
|
|
9692
|
-
|
|
9693
|
-
|
|
10161
|
+
// Benign errors from engine passthrough / SSE streams — keep alive
|
|
10162
|
+
if (
|
|
10163
|
+
msg === "terminated" ||
|
|
10164
|
+
msg === "aborted" ||
|
|
10165
|
+
/client.*disconnect/i.test(msg) ||
|
|
10166
|
+
/socket hang up/i.test(msg) ||
|
|
10167
|
+
/ECONNRESET/i.test(msg) ||
|
|
10168
|
+
/EPIPE/i.test(msg) ||
|
|
10169
|
+
/fetch failed/i.test(msg) ||
|
|
10170
|
+
/UND_ERR/i.test(msg)
|
|
10171
|
+
) {
|
|
10172
|
+
console.error("[dashboard] Non-fatal uncaughtException — keeping alive");
|
|
10173
|
+
return;
|
|
10174
|
+
}
|
|
10175
|
+
|
|
10176
|
+
// Fatal errors: port conflicts, permissions, OOM — must exit
|
|
10177
|
+
if (/EADDRINUSE|EACCES|out of memory|cannot allocate/i.test(msg)) {
|
|
10178
|
+
console.error("[dashboard] FATAL — exiting due to uncaught exception");
|
|
10179
|
+
process.exit(1);
|
|
10180
|
+
}
|
|
10181
|
+
|
|
10182
|
+
// Default: log but keep alive — engine passthrough errors shouldn't kill the dashboard
|
|
10183
|
+
console.error("[dashboard] Unexpected uncaughtException — keeping alive (not fatal)");
|
|
9694
10184
|
});
|
|
9695
10185
|
|
|
9696
10186
|
process.on("unhandledRejection", (reason) => {
|