crewswarm 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +48 -5
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/bridges/tmux-bridge.mjs +200 -0
- package/lib/cli-process-tracker.mjs +2 -1
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +340 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +53 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +26 -2
- package/lib/runtime/config.mjs +7 -0
- package/lib/runtime/paths.mjs +12 -8
- package/lib/sessions/session-manager.mjs +287 -0
- package/package.json +35 -15
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +74 -0
- package/scripts/dashboard.mjs +560 -70
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/index.html +0 -6459
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2823
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -803
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
package/lib/engines/runners.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { initEngineRegistry, selectEngine as registrySelectEngine, getEngineById
|
|
|
21
21
|
import { runCrewCLITask } from "./crew-cli.mjs";
|
|
22
22
|
import { normalizeProjectDir } from "../runtime/project-dir.mjs";
|
|
23
23
|
import { resolveCursorLaunchSpec } from "./cursor-launcher.mjs";
|
|
24
|
+
import * as tmuxBridge from "../bridges/tmux-bridge.mjs";
|
|
24
25
|
|
|
25
26
|
function which(bin) {
|
|
26
27
|
try { execSync(`which ${bin}`, { stdio: "ignore" }); return true; } catch { return false; }
|
|
@@ -357,6 +358,11 @@ export async function runGeminiCliTask(prompt, payload = {}) {
|
|
|
357
358
|
stdio: ["ignore", "pipe", "pipe"],
|
|
358
359
|
});
|
|
359
360
|
|
|
361
|
+
// Label tmux pane with agent ID for cross-agent discovery
|
|
362
|
+
if (payload?.tmuxSessionId && tmuxBridge.detect()) {
|
|
363
|
+
try { tmuxBridge.label(agentId, payload.tmuxSessionId); } catch {}
|
|
364
|
+
}
|
|
365
|
+
|
|
360
366
|
let lineBuffer = "";
|
|
361
367
|
let accumulatedText = "";
|
|
362
368
|
let orphanStream = "";
|
|
@@ -683,6 +689,11 @@ export async function runCursorCliTask(prompt, payload = {}) {
|
|
|
683
689
|
stdio: ["ignore", "pipe", "pipe"],
|
|
684
690
|
});
|
|
685
691
|
|
|
692
|
+
// Label tmux pane with agent ID for cross-agent discovery
|
|
693
|
+
if (payload?.tmuxSessionId && tmuxBridge.detect()) {
|
|
694
|
+
try { tmuxBridge.label(agentId, payload.tmuxSessionId); } catch {}
|
|
695
|
+
}
|
|
696
|
+
|
|
686
697
|
let lineBuffer = "";
|
|
687
698
|
let accumulatedText = "";
|
|
688
699
|
let lastCursorAssistantNorm = "";
|
|
@@ -915,6 +926,11 @@ export async function runCodexTask(prompt, payload = {}) {
|
|
|
915
926
|
stdio: ["ignore", "pipe", "pipe"],
|
|
916
927
|
});
|
|
917
928
|
|
|
929
|
+
// Label tmux pane with agent ID for cross-agent discovery
|
|
930
|
+
if (payload?.tmuxSessionId && tmuxBridge.detect()) {
|
|
931
|
+
try { tmuxBridge.label(agentId, payload.tmuxSessionId); } catch {}
|
|
932
|
+
}
|
|
933
|
+
|
|
918
934
|
let lineBuffer = "";
|
|
919
935
|
let accumulatedText = "";
|
|
920
936
|
/** Non-JSON lines (stderr, errors, usage) — previously swallowed by catch {} */
|
|
@@ -1165,6 +1181,7 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1165
1181
|
const agentPrefix = agentId ? `[${agentId}]` : "";
|
|
1166
1182
|
const titledPrompt = agentPrefix ? `${agentPrefix} ${String(prompt)}` : String(prompt);
|
|
1167
1183
|
|
|
1184
|
+
const emptyMcpCfg = path.join(os.homedir(), ".crewswarm", "config", "empty-mcp.json");
|
|
1168
1185
|
const args = [
|
|
1169
1186
|
"-p",
|
|
1170
1187
|
"--dangerously-skip-permissions",
|
|
@@ -1189,8 +1206,10 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1189
1206
|
}
|
|
1190
1207
|
}
|
|
1191
1208
|
|
|
1192
|
-
//
|
|
1193
|
-
args.push(
|
|
1209
|
+
// Skip user MCP servers to avoid 30s+ init hangs
|
|
1210
|
+
args.push("--strict-mcp-config", "--mcp-config", emptyMcpCfg);
|
|
1211
|
+
// -- separates flags from prompt (--mcp-config is variadic and eats positional args)
|
|
1212
|
+
args.push("--", titledPrompt);
|
|
1194
1213
|
|
|
1195
1214
|
if (!which(CLAUDE_CODE_BIN)) {
|
|
1196
1215
|
throw new Error(`Claude Code CLI not found: "${CLAUDE_CODE_BIN}". Install with: npm i -g @anthropic-ai/claude-code`);
|
|
@@ -1207,6 +1226,11 @@ export async function runClaudeCodeTask(prompt, payload = {}) {
|
|
|
1207
1226
|
stdio: ["ignore", "pipe", "pipe"], // Changed from "pipe" to "ignore" for stdin since we use args
|
|
1208
1227
|
});
|
|
1209
1228
|
|
|
1229
|
+
// Label tmux pane with agent ID for cross-agent discovery
|
|
1230
|
+
if (payload?.tmuxSessionId && tmuxBridge.detect()) {
|
|
1231
|
+
try { tmuxBridge.label(agentId, payload.tmuxSessionId); } catch {}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1210
1234
|
let lineBuffer = "";
|
|
1211
1235
|
let accumulatedText = "";
|
|
1212
1236
|
let stderrText = "";
|
package/lib/runtime/config.mjs
CHANGED
|
@@ -290,6 +290,13 @@ export function loadClaudeCodeEnabled() {
|
|
|
290
290
|
if (typeof cfg.claudeCode === "boolean") return cfg.claudeCode;
|
|
291
291
|
return false;
|
|
292
292
|
}
|
|
293
|
+
|
|
294
|
+
export function loadTmuxBridgeEnabled() {
|
|
295
|
+
if (process.env.CREWSWARM_TMUX_BRIDGE) return /^1|true|yes$/i.test(String(process.env.CREWSWARM_TMUX_BRIDGE));
|
|
296
|
+
const cfg = loadSystemConfig();
|
|
297
|
+
if (typeof cfg.tmuxBridge === "boolean") return cfg.tmuxBridge;
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
293
300
|
// ── Configuration Parsers (Migrated from registry.mjs) ───────────────────
|
|
294
301
|
export function resolveConfig() {
|
|
295
302
|
const paths = [CREWSWARM_CONFIG_PATH, path.join(LEGACY_STATE_DIR, "openclaw.json")];
|
package/lib/runtime/paths.mjs
CHANGED
|
@@ -21,14 +21,16 @@ let _stateDir = null;
|
|
|
21
21
|
*/
|
|
22
22
|
export function getConfigDir() {
|
|
23
23
|
if (_configDir) return _configDir;
|
|
24
|
-
|
|
25
|
-
if (process.env.
|
|
24
|
+
|
|
25
|
+
if (process.env.CREWSWARM_CONFIG_DIR) {
|
|
26
|
+
_configDir = process.env.CREWSWARM_CONFIG_DIR;
|
|
27
|
+
} else if (process.env.CREWSWARM_TEST_MODE === "true") {
|
|
26
28
|
// Use a consistent temp dir for the entire test process (not per-call)
|
|
27
29
|
_configDir = path.join(os.tmpdir(), `crewswarm-test-${process.pid}`);
|
|
28
30
|
} else {
|
|
29
|
-
_configDir =
|
|
31
|
+
_configDir = path.join(os.homedir(), ".crewswarm");
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
|
|
32
34
|
fs.mkdirSync(_configDir, { recursive: true });
|
|
33
35
|
return _configDir;
|
|
34
36
|
}
|
|
@@ -39,14 +41,16 @@ export function getConfigDir() {
|
|
|
39
41
|
*/
|
|
40
42
|
export function getStateDir() {
|
|
41
43
|
if (_stateDir) return _stateDir;
|
|
42
|
-
|
|
43
|
-
if (process.env.
|
|
44
|
+
|
|
45
|
+
if (process.env.CREWSWARM_STATE_DIR) {
|
|
46
|
+
_stateDir = process.env.CREWSWARM_STATE_DIR;
|
|
47
|
+
} else if (process.env.CREWSWARM_TEST_MODE === "true") {
|
|
44
48
|
// Use a consistent temp dir for the entire test process (not per-call)
|
|
45
49
|
_stateDir = path.join(os.tmpdir(), `crewswarm-test-${process.pid}`);
|
|
46
50
|
} else {
|
|
47
|
-
_stateDir =
|
|
51
|
+
_stateDir = path.join(os.homedir(), ".crewswarm");
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
|
|
50
54
|
fs.mkdirSync(_stateDir, { recursive: true });
|
|
51
55
|
return _stateDir;
|
|
52
56
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager — persistent tmux sessions as first-class execution resources
|
|
3
|
+
*
|
|
4
|
+
* Manages session lifecycle: create, attach, exec, lock, handoff, terminate.
|
|
5
|
+
* One writer per session (lock enforcement). Transcripts logged for auditability.
|
|
6
|
+
*
|
|
7
|
+
* Sessions are stored as metadata files under ~/.crewswarm/state/sessions/.
|
|
8
|
+
* The actual tmux sessions are managed via tmux CLI.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
import fs from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { getStatePath } from "../runtime/paths.mjs";
|
|
16
|
+
import * as tmuxBridge from "../bridges/tmux-bridge.mjs";
|
|
17
|
+
|
|
18
|
+
const SESSION_DIR = getStatePath("sessions");
|
|
19
|
+
const TRANSCRIPT_DIR = getStatePath("sessions", "transcripts");
|
|
20
|
+
|
|
21
|
+
try { fs.mkdirSync(SESSION_DIR, { recursive: true }); } catch {}
|
|
22
|
+
try { fs.mkdirSync(TRANSCRIPT_DIR, { recursive: true }); } catch {}
|
|
23
|
+
|
|
24
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function sessionMetaPath(sessionId) {
|
|
27
|
+
return path.join(SESSION_DIR, `${sessionId}.json`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function transcriptPath(sessionId) {
|
|
31
|
+
return path.join(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadMeta(sessionId) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(fs.readFileSync(sessionMetaPath(sessionId), "utf8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function saveMeta(sessionId, meta) {
|
|
43
|
+
try {
|
|
44
|
+
fs.writeFileSync(sessionMetaPath(sessionId), JSON.stringify(meta, null, 2));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.error(`[session-manager] Failed to save meta for ${sessionId}: ${e.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function appendTranscript(sessionId, entry) {
|
|
51
|
+
try {
|
|
52
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...entry });
|
|
53
|
+
fs.appendFileSync(transcriptPath(sessionId), line + "\n");
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function tmuxExec(cmd, timeout = 5000) {
|
|
58
|
+
try {
|
|
59
|
+
return execSync(cmd, { encoding: "utf8", timeout, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create a new persistent tmux session for agent work.
|
|
69
|
+
* @param {object} opts
|
|
70
|
+
* @param {string} opts.workspaceId - Logical workspace name
|
|
71
|
+
* @param {string} opts.agentId - Owning agent
|
|
72
|
+
* @param {string} [opts.cwd] - Working directory
|
|
73
|
+
* @param {Record<string, string>} [opts.env] - Extra env vars
|
|
74
|
+
* @returns {string|null} sessionId or null on failure
|
|
75
|
+
*/
|
|
76
|
+
export function create({ workspaceId, agentId, cwd, env } = {}) {
|
|
77
|
+
if (!tmuxBridge.detect()) return null;
|
|
78
|
+
|
|
79
|
+
const sessionId = `cs-${workspaceId}-${randomUUID().slice(0, 8)}`;
|
|
80
|
+
const sessionName = sessionId;
|
|
81
|
+
|
|
82
|
+
// Create a new tmux session (detached)
|
|
83
|
+
const envStr = env
|
|
84
|
+
? Object.entries(env).map(([k, v]) => `-e ${k}=${v}`).join(" ")
|
|
85
|
+
: "";
|
|
86
|
+
const cwdFlag = cwd ? `-c "${cwd}"` : "";
|
|
87
|
+
const result = tmuxExec(`tmux new-session -d -s "${sessionName}" ${cwdFlag} ${envStr}`);
|
|
88
|
+
if (result === null) {
|
|
89
|
+
console.error(`[session-manager] Failed to create tmux session: ${sessionName}`);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Label the session's first pane with the agent ID
|
|
94
|
+
const paneId = tmuxExec(`tmux list-panes -t "${sessionName}" -F "#{pane_id}" | head -1`);
|
|
95
|
+
if (paneId) {
|
|
96
|
+
tmuxBridge.label(agentId, paneId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const meta = {
|
|
100
|
+
sessionId,
|
|
101
|
+
sessionName,
|
|
102
|
+
workspaceId,
|
|
103
|
+
owner: agentId,
|
|
104
|
+
lockedBy: agentId,
|
|
105
|
+
paneId: paneId || null,
|
|
106
|
+
cwd: cwd || null,
|
|
107
|
+
env: env || null,
|
|
108
|
+
createdAt: new Date().toISOString(),
|
|
109
|
+
status: "active",
|
|
110
|
+
};
|
|
111
|
+
saveMeta(sessionId, meta);
|
|
112
|
+
appendTranscript(sessionId, { action: "created", agent: agentId, cwd });
|
|
113
|
+
|
|
114
|
+
console.log(`[session-manager] Created session ${sessionId} for ${agentId} (pane=${paneId})`);
|
|
115
|
+
return sessionId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Attach an agent to an existing session (for handoff or observation).
|
|
120
|
+
* @param {string} sessionId
|
|
121
|
+
* @param {string} agentId
|
|
122
|
+
* @returns {{ paneId: string, sessionName: string }|null}
|
|
123
|
+
*/
|
|
124
|
+
export function attach(sessionId, agentId) {
|
|
125
|
+
const meta = loadMeta(sessionId);
|
|
126
|
+
if (!meta || meta.status !== "active") return null;
|
|
127
|
+
|
|
128
|
+
appendTranscript(sessionId, { action: "attached", agent: agentId });
|
|
129
|
+
console.log(`[session-manager] ${agentId} attached to session ${sessionId}`);
|
|
130
|
+
|
|
131
|
+
return { paneId: meta.paneId, sessionName: meta.sessionName };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Execute a command in a session's tmux pane.
|
|
136
|
+
* Only the lock owner can execute.
|
|
137
|
+
* @param {string} sessionId
|
|
138
|
+
* @param {string} command
|
|
139
|
+
* @param {object} [opts]
|
|
140
|
+
* @param {string} opts.actorId - Agent executing the command
|
|
141
|
+
* @param {number} [opts.timeout=30000] - Timeout in ms
|
|
142
|
+
* @returns {{ output: string }|null}
|
|
143
|
+
*/
|
|
144
|
+
export function exec(sessionId, command, { actorId, timeout = 30000 } = {}) {
|
|
145
|
+
const meta = loadMeta(sessionId);
|
|
146
|
+
if (!meta || meta.status !== "active") return null;
|
|
147
|
+
|
|
148
|
+
// Enforce lock
|
|
149
|
+
if (meta.lockedBy && meta.lockedBy !== actorId) {
|
|
150
|
+
console.warn(`[session-manager] ${actorId} cannot exec in ${sessionId} — locked by ${meta.lockedBy}`);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const paneId = meta.paneId;
|
|
155
|
+
if (!paneId) return null;
|
|
156
|
+
|
|
157
|
+
// Send keys to the pane
|
|
158
|
+
tmuxExec(`tmux send-keys -t "${paneId}" "${command.replace(/"/g, '\\"')}" Enter`, timeout);
|
|
159
|
+
appendTranscript(sessionId, { action: "exec", agent: actorId, command: command.slice(0, 500) });
|
|
160
|
+
|
|
161
|
+
// Read back output after a short delay
|
|
162
|
+
const output = tmuxBridge.read(meta.owner, 50);
|
|
163
|
+
return { output: output || "" };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Lock a session for exclusive write access.
|
|
168
|
+
* @param {string} sessionId
|
|
169
|
+
* @param {string} ownerId - Agent requesting the lock
|
|
170
|
+
* @returns {boolean} true if lock acquired
|
|
171
|
+
*/
|
|
172
|
+
export function lock(sessionId, ownerId) {
|
|
173
|
+
const meta = loadMeta(sessionId);
|
|
174
|
+
if (!meta || meta.status !== "active") return false;
|
|
175
|
+
|
|
176
|
+
if (meta.lockedBy && meta.lockedBy !== ownerId) {
|
|
177
|
+
console.warn(`[session-manager] Lock denied for ${ownerId} on ${sessionId} — held by ${meta.lockedBy}`);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
meta.lockedBy = ownerId;
|
|
182
|
+
meta.lockedAt = new Date().toISOString();
|
|
183
|
+
saveMeta(sessionId, meta);
|
|
184
|
+
appendTranscript(sessionId, { action: "locked", agent: ownerId });
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Unlock a session.
|
|
190
|
+
* @param {string} sessionId
|
|
191
|
+
* @param {string} ownerId - Must match current lock holder
|
|
192
|
+
* @returns {boolean}
|
|
193
|
+
*/
|
|
194
|
+
export function unlock(sessionId, ownerId) {
|
|
195
|
+
const meta = loadMeta(sessionId);
|
|
196
|
+
if (!meta) return false;
|
|
197
|
+
|
|
198
|
+
if (meta.lockedBy && meta.lockedBy !== ownerId) {
|
|
199
|
+
console.warn(`[session-manager] Unlock denied for ${ownerId} on ${sessionId} — held by ${meta.lockedBy}`);
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
meta.lockedBy = null;
|
|
204
|
+
meta.lockedAt = null;
|
|
205
|
+
saveMeta(sessionId, meta);
|
|
206
|
+
appendTranscript(sessionId, { action: "unlocked", agent: ownerId });
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Hand off a session from one agent to another.
|
|
212
|
+
* Transfers lock ownership and re-labels the pane.
|
|
213
|
+
* @param {string} sessionId
|
|
214
|
+
* @param {string} fromAgent
|
|
215
|
+
* @param {string} toAgent
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
export function handoff(sessionId, fromAgent, toAgent) {
|
|
219
|
+
const meta = loadMeta(sessionId);
|
|
220
|
+
if (!meta || meta.status !== "active") return false;
|
|
221
|
+
|
|
222
|
+
// Only the current lock holder (or unlocked session) can hand off
|
|
223
|
+
if (meta.lockedBy && meta.lockedBy !== fromAgent) {
|
|
224
|
+
console.warn(`[session-manager] Handoff denied: ${sessionId} locked by ${meta.lockedBy}, not ${fromAgent}`);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
meta.owner = toAgent;
|
|
229
|
+
meta.lockedBy = toAgent;
|
|
230
|
+
meta.lockedAt = new Date().toISOString();
|
|
231
|
+
saveMeta(sessionId, meta);
|
|
232
|
+
|
|
233
|
+
// Re-label pane for the new agent
|
|
234
|
+
if (meta.paneId) {
|
|
235
|
+
tmuxBridge.label(toAgent, meta.paneId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
appendTranscript(sessionId, { action: "handoff", from: fromAgent, to: toAgent });
|
|
239
|
+
console.log(`[session-manager] Session ${sessionId} handed off: ${fromAgent} → ${toAgent}`);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Terminate a session and clean up its tmux pane.
|
|
245
|
+
* @param {string} sessionId
|
|
246
|
+
* @returns {boolean}
|
|
247
|
+
*/
|
|
248
|
+
export function terminate(sessionId) {
|
|
249
|
+
const meta = loadMeta(sessionId);
|
|
250
|
+
if (!meta) return false;
|
|
251
|
+
|
|
252
|
+
// Kill the tmux session
|
|
253
|
+
if (meta.sessionName) {
|
|
254
|
+
tmuxExec(`tmux kill-session -t "${meta.sessionName}"`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
meta.status = "terminated";
|
|
258
|
+
meta.terminatedAt = new Date().toISOString();
|
|
259
|
+
saveMeta(sessionId, meta);
|
|
260
|
+
appendTranscript(sessionId, { action: "terminated" });
|
|
261
|
+
console.log(`[session-manager] Session ${sessionId} terminated`);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get metadata for a session.
|
|
267
|
+
* @param {string} sessionId
|
|
268
|
+
* @returns {object|null}
|
|
269
|
+
*/
|
|
270
|
+
export function getSession(sessionId) {
|
|
271
|
+
return loadMeta(sessionId);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* List all active sessions.
|
|
276
|
+
* @returns {Array<object>}
|
|
277
|
+
*/
|
|
278
|
+
export function listSessions() {
|
|
279
|
+
try {
|
|
280
|
+
const files = fs.readdirSync(SESSION_DIR).filter(f => f.endsWith(".json"));
|
|
281
|
+
return files
|
|
282
|
+
.map(f => loadMeta(f.replace(".json", "")))
|
|
283
|
+
.filter(m => m && m.status === "active");
|
|
284
|
+
} catch {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewswarm",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Local-first multi-agent orchestration platform
|
|
3
|
+
"version": "0.9.3",
|
|
4
|
+
"description": "Local-first multi-agent orchestration platform \u2014 coordinate AI coding agents, LLMs, and tools from a single dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -29,10 +29,11 @@
|
|
|
29
29
|
"engines/",
|
|
30
30
|
"prompts/",
|
|
31
31
|
"apps/dashboard/dist/",
|
|
32
|
-
"apps/
|
|
33
|
-
"apps/
|
|
34
|
-
"apps/
|
|
35
|
-
"apps/vibe/",
|
|
32
|
+
"apps/vibe/dist/",
|
|
33
|
+
"apps/vibe/package.json",
|
|
34
|
+
"apps/vibe/public/",
|
|
35
|
+
"apps/vibe/server.mjs",
|
|
36
|
+
"apps/vibe/watch-server.mjs",
|
|
36
37
|
"contrib/openclaw-plugin/",
|
|
37
38
|
"crew-lead.mjs",
|
|
38
39
|
"gateway-bridge.mjs",
|
|
@@ -61,14 +62,25 @@
|
|
|
61
62
|
"@whiskeysockets/baileys": "^7.0.0-rc.9"
|
|
62
63
|
},
|
|
63
64
|
"scripts": {
|
|
64
|
-
"postinstall": "node -e \"try{require('child_process').execSync('git --version',{stdio:'pipe'})}catch{console.log('\\n
|
|
65
|
+
"postinstall": "node -e \"try{require('child_process').execSync('git --version',{stdio:'pipe'})}catch{console.log('\\n\u26a0\ufe0f git not found. Install git for full functionality (WhatsApp bridge, crew-cli git tools).\\n macOS: xcode-select --install\\n Ubuntu: sudo apt install git\\n Alpine: apk add git\\n')}\"",
|
|
65
66
|
"start": "node scripts/start.mjs",
|
|
66
|
-
"test": "node --test test/unit/*.test.mjs test/mention-participants.test.mjs test/project-messages-chat-protocol.test.mjs && npm --prefix crew-cli test",
|
|
67
|
-
"test:unit": "node --test test/unit/*.test.mjs",
|
|
68
|
-
"test:integration": "PM_LOOP_TEST_MODE=1 node --test test/integration/*.test.mjs",
|
|
67
|
+
"test": "node --test --test-reporter=./scripts/test-reporter.mjs test/unit/*.test.mjs test/mention-participants.test.mjs test/project-messages-chat-protocol.test.mjs && npm --prefix crew-cli test",
|
|
68
|
+
"test:unit": "node --test --test-reporter=./scripts/test-reporter.mjs test/unit/*.test.mjs",
|
|
69
|
+
"test:integration": "PM_LOOP_TEST_MODE=1 node --test --test-reporter=./scripts/test-reporter.mjs test/integration/*.test.mjs",
|
|
69
70
|
"test:integration:bounded": "node scripts/run-integration-bounded.mjs",
|
|
70
|
-
"test:e2e": "node --test test/e2e/*.test.mjs",
|
|
71
|
-
"test:all": "PM_LOOP_TEST_MODE=1 node --test test/unit/*.test.mjs test/integration/*.test.mjs test/e2e/*.test.mjs",
|
|
71
|
+
"test:e2e": "node --test --test-reporter=./scripts/test-reporter.mjs test/e2e/*.test.mjs",
|
|
72
|
+
"test:all": "PM_LOOP_TEST_MODE=1 node --test --test-reporter=./scripts/test-reporter.mjs test/unit/*.test.mjs test/integration/*.test.mjs test/e2e/*.test.mjs",
|
|
73
|
+
"test:coverage:root": "node --test --experimental-test-coverage test/unit/*.test.mjs",
|
|
74
|
+
"test:coverage": "node scripts/coverage-report.mjs",
|
|
75
|
+
"test:coverage:summary": "node scripts/coverage-summary.mjs",
|
|
76
|
+
"test:live:providers": "node scripts/live-provider-failover-matrix.mjs",
|
|
77
|
+
"test:live:providers:matrix": "node scripts/live-provider-matrix.mjs",
|
|
78
|
+
"test:live:providers:smoke": "node scripts/live-provider-matrix.mjs --smoke",
|
|
79
|
+
"test:live:clis": "node scripts/live-cli-matrix.mjs",
|
|
80
|
+
"test:live:clis:smoke": "node scripts/live-cli-matrix.mjs --smoke",
|
|
81
|
+
"test:live:bridges": "node scripts/live-bridge-matrix.mjs",
|
|
82
|
+
"test:live:crewchat": "node scripts/live-crewchat-check.mjs",
|
|
83
|
+
"test:live": "node scripts/live-provider-failover-matrix.mjs && node scripts/live-bridge-matrix.mjs && node scripts/live-crewchat-check.mjs",
|
|
72
84
|
"docs:api": "node scripts/generate-api-docs.mjs",
|
|
73
85
|
"changelog:generate": "node scripts/generate-changelog.mjs",
|
|
74
86
|
"changelog:release": "node scripts/generate-changelog.mjs --release",
|
|
@@ -86,7 +98,12 @@
|
|
|
86
98
|
"stop-crew": "node scripts/start-crew.mjs --stop",
|
|
87
99
|
"dashboard": "node scripts/dashboard.mjs",
|
|
88
100
|
"vibe": "node apps/vibe/server.mjs",
|
|
89
|
-
"vibe:
|
|
101
|
+
"vibe:build": "cd apps/vibe && npm run build",
|
|
102
|
+
"vibe:start": "cd apps/vibe && NODE_DISABLE_COMPILE_CACHE=1 npm start",
|
|
103
|
+
"vibe:watch": "NODE_DISABLE_COMPILE_CACHE=1 node apps/vibe/watch-server.mjs",
|
|
104
|
+
"vibe:full": "bash scripts/start-studio-full.sh",
|
|
105
|
+
"test:e2e:vibe": "node node_modules/playwright/cli.js test --config=playwright.config.js",
|
|
106
|
+
"test:e2e:vibe:headed": "node node_modules/playwright/cli.js test --config=playwright.config.js --headed",
|
|
90
107
|
"crew-lead": "node crew-lead.mjs",
|
|
91
108
|
"mcp": "node scripts/mcp-server.mjs",
|
|
92
109
|
"studio": "cd apps/vibe && npm run dev",
|
|
@@ -100,10 +117,13 @@
|
|
|
100
117
|
"smoke:static": "bash scripts/smoke.sh",
|
|
101
118
|
"health": "node scripts/health-check.mjs",
|
|
102
119
|
"doctor": "node scripts/doctor.mjs",
|
|
103
|
-
"release:check": "bash scripts/release-check.sh"
|
|
120
|
+
"release:check": "bash scripts/release-check.sh",
|
|
121
|
+
"test:report": "node scripts/test-report-summary.mjs",
|
|
122
|
+
"test:rerun": "node scripts/test-rerun.mjs",
|
|
123
|
+
"test:stale": "node scripts/test-rerun.mjs --stale"
|
|
104
124
|
},
|
|
105
125
|
"devDependencies": {
|
|
106
126
|
"@playwright/test": "^1.58.2",
|
|
107
127
|
"puppeteer-core": "^24.40.0"
|
|
108
128
|
}
|
|
109
|
-
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* capture-build-flow.mjs — Puppeteer script that captures dashboard
|
|
4
|
+
* screenshots during a build flow for documentation / demo purposes.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/capture-build-flow.mjs
|
|
7
|
+
* Requires: dashboard running at http://127.0.0.1:4319
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { mkdir } from "node:fs/promises";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { dirname, resolve } from "node:path";
|
|
13
|
+
import puppeteer from "puppeteer-core";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const ROOT = resolve(__dirname, "..");
|
|
17
|
+
const OUT_DIR = resolve(ROOT, "website/screenshots/flow");
|
|
18
|
+
const BASE_URL = "http://127.0.0.1:4319";
|
|
19
|
+
|
|
20
|
+
const STEPS = [
|
|
21
|
+
{ name: "step1-build-tab", hash: "build", waitMs: 2000 },
|
|
22
|
+
{ name: "step2-requirement", action: "type" },
|
|
23
|
+
{ name: "step3-plan", action: "plan", waitMs: 20000 },
|
|
24
|
+
{ name: "step4-chat", hash: "chat", waitMs: 3000 },
|
|
25
|
+
{ name: "step5-agents", hash: "swarm", waitMs: 2000 },
|
|
26
|
+
{ name: "step6-rt-messages", hash: "rt", waitMs: 2000 },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
async function screenshot(page, name) {
|
|
30
|
+
const path = resolve(OUT_DIR, `${name}.webp`);
|
|
31
|
+
await page.screenshot({ path, type: "webp", quality: 85 });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function sleep(ms) {
|
|
35
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
await mkdir(OUT_DIR, { recursive: true });
|
|
40
|
+
|
|
41
|
+
const browser = await puppeteer.launch({
|
|
42
|
+
headless: true,
|
|
43
|
+
executablePath:
|
|
44
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
45
|
+
args: ["--no-sandbox"],
|
|
46
|
+
defaultViewport: { width: 1440, height: 960 },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const page = await browser.newPage();
|
|
50
|
+
|
|
51
|
+
// Step 1 — navigate to #build
|
|
52
|
+
await page.goto(`${BASE_URL}/#build`, { waitUntil: "domcontentloaded" });
|
|
53
|
+
await sleep(2000);
|
|
54
|
+
await screenshot(page, "step1-build-tab");
|
|
55
|
+
|
|
56
|
+
// Step 2 — type requirement
|
|
57
|
+
const REQUIREMENT = "Create a simple hello world HTML page";
|
|
58
|
+
// Try a few selectors for the requirement input
|
|
59
|
+
const textareaSelector = await page
|
|
60
|
+
.waitForSelector("textarea, #requirement, .requirement-input", {
|
|
61
|
+
timeout: 5000,
|
|
62
|
+
})
|
|
63
|
+
.catch(() => null);
|
|
64
|
+
|
|
65
|
+
if (textareaSelector) {
|
|
66
|
+
await textareaSelector.click();
|
|
67
|
+
await textareaSelector.type(REQUIREMENT, { delay: 30 });
|
|
68
|
+
}
|
|
69
|
+
await screenshot(page, "step2-requirement");
|
|
70
|
+
|
|
71
|
+
// Step 3 — click Plan and wait for result
|
|
72
|
+
await page.evaluate(() => {
|
|
73
|
+
const buttons = [...document.querySelectorAll("button")];
|
|
74
|
+
const planBtn = buttons.find((b) => /plan/i.test(b.textContent));
|
|
75
|
+
if (planBtn) planBtn.click();
|
|
76
|
+
});
|
|
77
|
+
await sleep(20000);
|
|
78
|
+
await screenshot(page, "step3-plan");
|
|
79
|
+
|
|
80
|
+
// Step 4 — Chat tab
|
|
81
|
+
await page.goto(`${BASE_URL}/#chat`, { waitUntil: "domcontentloaded" });
|
|
82
|
+
await sleep(3000);
|
|
83
|
+
await screenshot(page, "step4-chat");
|
|
84
|
+
|
|
85
|
+
// Step 5 — Swarm / agents tab
|
|
86
|
+
await page.goto(`${BASE_URL}/#swarm`, { waitUntil: "domcontentloaded" });
|
|
87
|
+
await sleep(2000);
|
|
88
|
+
await screenshot(page, "step5-agents");
|
|
89
|
+
|
|
90
|
+
// Step 6 — RT messages tab
|
|
91
|
+
await page.goto(`${BASE_URL}/#rt`, { waitUntil: "domcontentloaded" });
|
|
92
|
+
await sleep(2000);
|
|
93
|
+
await screenshot(page, "step6-rt-messages");
|
|
94
|
+
|
|
95
|
+
await browser.close();
|
|
96
|
+
|
|
97
|
+
const files = [
|
|
98
|
+
"step1-build-tab.webp",
|
|
99
|
+
"step2-requirement.webp",
|
|
100
|
+
"step3-plan.webp",
|
|
101
|
+
"step4-chat.webp",
|
|
102
|
+
"step5-agents.webp",
|
|
103
|
+
"step6-rt-messages.webp",
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
console.log(`\nCaptured ${files.length} build flow frames:`);
|
|
107
|
+
for (const f of files) {
|
|
108
|
+
console.log(` website/screenshots/flow/${f}`);
|
|
109
|
+
}
|
|
110
|
+
console.log(
|
|
111
|
+
`\nTo create animated webp: convert -delay 200 website/screenshots/flow/*.webp website/screenshots/build-flow.webp`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().catch((err) => {
|
|
116
|
+
console.error("Build flow capture failed:", err.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
});
|