crewswarm 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +2 -2
  2. package/apps/dashboard/dist/assets/{chat-core-CMoqlR6D.js → chat-core-Cx4sTxDd.js} +1 -1
  3. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  5. package/apps/dashboard/dist/assets/{components-CSUb80ze.js → components-BS9fQjE_.js} +1 -1
  6. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  7. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js +1 -0
  8. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  9. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  10. package/apps/dashboard/dist/assets/{index-DqVVQLTW.js → index-DnClJ1ee.js} +2 -2
  11. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  12. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{setup-wizard-D4g5DMhW.js → setup-wizard-CA0Or47w.js} +1 -1
  14. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-agents-tab-BThdsdJY.js → tab-agents-tab-BgpIsjkw.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  17. package/apps/dashboard/dist/assets/{tab-benchmarks-tab-DfCuAClu.js → tab-benchmarks-tab-BHjKCPm3.js} +1 -1
  18. package/apps/dashboard/dist/assets/{tab-comms-tab-eHpOSBhG.js → tab-comms-tab-kguqTIzD.js} +1 -1
  19. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  20. package/apps/dashboard/dist/assets/{tab-contacts-tab-5LHSthJM.js → tab-contacts-tab-DiOyMYth.js} +1 -1
  21. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  22. package/apps/dashboard/dist/assets/{tab-engines-tab-C3DYxTwy.js → tab-engines-tab-BsdZVvU0.js} +1 -1
  23. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  24. package/apps/dashboard/dist/assets/{tab-memory-tab-C59BYFQD.js → tab-memory-tab-Cu6u13EQ.js} +1 -1
  25. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  26. package/apps/dashboard/dist/assets/{tab-models-tab-CQzvaeVh.js → tab-models-tab-BLEjmd19.js} +1 -1
  27. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  28. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-D7mnDelU.js → tab-pm-loop-tab-Bfd449B4.js} +1 -1
  29. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  30. package/apps/dashboard/dist/assets/{tab-projects-tab-C6h2Mv1K.js → tab-projects-tab-DhNWnlzt.js} +1 -1
  31. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  32. package/apps/dashboard/dist/assets/{tab-prompts-tab-C0wZvWK3.js → tab-prompts-tab-DVkUNaJd.js} +1 -1
  33. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  34. package/apps/dashboard/dist/assets/{tab-services-tab-DBj_w3bc.js → tab-services-tab-DU_LH3uG.js} +1 -1
  35. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  36. package/apps/dashboard/dist/assets/{tab-settings-tab-ezeqAjZk.js → tab-settings-tab-Bn4nXtDe.js} +1 -1
  37. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  38. package/apps/dashboard/dist/assets/{tab-skills-tab-BYdU2whk.js → tab-skills-tab-BpY0uZHW.js} +1 -1
  39. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  40. package/apps/dashboard/dist/assets/{tab-spending-tab-Bg6w9t_p.js → tab-spending-tab-DEccQHnt.js} +1 -1
  41. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  42. package/apps/dashboard/dist/assets/{tab-swarm-chat-tab-BBV9HB2X.js → tab-swarm-chat-tab-BNrd88-r.js} +1 -1
  43. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  44. package/apps/dashboard/dist/assets/{tab-swarm-tab-ChqLlEVs.js → tab-swarm-tab-B1AcjL1W.js} +1 -1
  45. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  46. package/apps/dashboard/dist/assets/{tab-usage-tab-B2UWXenJ.js → tab-usage-tab-BIOOnB-Y.js} +1 -1
  47. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  48. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  49. package/apps/dashboard/dist/assets/{tab-workflows-tab-6QSXLJ0i.js → tab-workflows-tab-B-soSy1k.js} +1 -1
  50. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  51. package/apps/dashboard/dist/index.html +23 -23
  52. package/apps/dashboard/dist/index.html.br +0 -0
  53. package/apps/dashboard/dist/index.html.gz +0 -0
  54. package/apps/dashboard/index.html +71 -1
  55. package/apps/dashboard/src/app.js +5 -0
  56. package/apps/dashboard/src/core/dom.js +8 -0
  57. package/apps/dashboard/src/tabs/settings-tab.js +58 -0
  58. package/apps/vibe/.crew/agent-memory/pipeline.json +12 -1
  59. package/apps/vibe/.crew/cost.json +3 -3
  60. package/apps/vibe/.crew/json-parse-metrics.jsonl +1 -0
  61. package/apps/vibe/.crew/pipeline-metrics.jsonl +1 -0
  62. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +5 -0
  63. package/apps/vibe/.crew/session.json +10 -1
  64. package/apps/vibe/.studio-data/project-messages/general.jsonl +3 -0
  65. package/apps/vibe/index.html +4 -2
  66. package/apps/vibe/server.mjs +75 -3
  67. package/apps/vibe/src/main.js +126 -53
  68. package/crew-lead.mjs +14 -1
  69. package/lib/bridges/cli-executor.mjs +0 -2
  70. package/lib/bridges/tmux-bridge.mjs +200 -0
  71. package/lib/chat/unified-history.mjs +1 -1
  72. package/lib/cli-process-tracker.mjs +2 -1
  73. package/lib/crew-lead/http-server.mjs +286 -1
  74. package/lib/crew-lead/wave-dispatcher.mjs +40 -3
  75. package/lib/engines/crew-cli.mjs +3 -2
  76. package/lib/engines/llm-direct.mjs +4 -1
  77. package/lib/engines/rt-envelope.mjs +14 -5
  78. package/lib/engines/runners.mjs +30 -4
  79. package/lib/runtime/config.mjs +7 -0
  80. package/lib/sessions/session-manager.mjs +287 -0
  81. package/package.json +1 -1
  82. package/scripts/bench/performance_optimization.py +81 -0
  83. package/whatsapp-bridge.mjs +54 -10
  84. package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +0 -1
@@ -286,8 +286,19 @@
286
286
  "pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40"
287
287
  ],
288
288
  "provider": "pipeline"
289
+ },
290
+ {
291
+ "id": "756e1232-e81e-474a-b87d-86e5c7a9bfd1",
292
+ "content": "L2 Decision: direct-answer - User message is a simple greeting 'hi', which explicitly qualifies for DIRECT-ANSWER per constraints",
293
+ "critical": true,
294
+ "timestamp": "2026-03-26T17:46:37.435Z",
295
+ "tags": [
296
+ "l2-decision",
297
+ "pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2"
298
+ ],
299
+ "provider": "pipeline"
289
300
  }
290
301
  ],
291
302
  "createdAt": "2026-03-24T20:43:06.375Z",
292
- "updatedAt": "2026-03-26T07:46:58.174Z"
303
+ "updatedAt": "2026-03-26T17:46:37.435Z"
293
304
  }
@@ -3,10 +3,10 @@
3
3
  "byModel": {},
4
4
  "entries": [],
5
5
  "cacheSavings": {
6
- "hits": 34,
6
+ "hits": 35,
7
7
  "misses": 0,
8
- "tokensSaved": 11136,
9
- "usdSaved": 0.027840000000000007
8
+ "tokensSaved": 11200,
9
+ "usdSaved": 0.028000000000000008
10
10
  },
11
11
  "memoryMetrics": {
12
12
  "recallUsed": 0,
@@ -24,3 +24,4 @@
24
24
  {"ts":"2026-03-26T06:59:43.845Z","label":"L2 router (pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c)","attempt":1,"success":true,"repaired":false,"traceId":"pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c"}
25
25
  {"ts":"2026-03-26T07:44:08.921Z","label":"L2 router (pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842)","attempt":1,"success":true,"repaired":false,"traceId":"pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842"}
26
26
  {"ts":"2026-03-26T07:46:58.171Z","label":"L2 router (pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40)","attempt":1,"success":true,"repaired":false,"traceId":"pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40"}
27
+ {"ts":"2026-03-26T17:46:37.432Z","label":"L2 router (pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2)","attempt":1,"success":true,"repaired":false,"traceId":"pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2"}
@@ -24,3 +24,4 @@
24
24
  {"ts":"2026-03-26T06:59:46.721Z","traceId":"pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c","decision":"execute-direct","qaEnabled":false,"qaApproved":true,"qaRounds":0,"contextChunksUsed":0,"contextCharsSaved":0,"totalCost":0.0007695,"executionPath":["l1-interface","l2-orchestrator","l3-executor-direct"]}
25
25
  {"ts":"2026-03-26T07:44:12.132Z","traceId":"pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842","decision":"execute-direct","qaEnabled":false,"qaApproved":true,"qaRounds":0,"contextChunksUsed":0,"contextCharsSaved":0,"totalCost":0.0008346,"executionPath":["l1-interface","l2-orchestrator","l3-executor-direct"]}
26
26
  {"ts":"2026-03-26T07:46:58.176Z","traceId":"pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40","decision":"direct-answer","qaEnabled":false,"qaApproved":true,"qaRounds":0,"contextChunksUsed":0,"contextCharsSaved":0,"totalCost":0.0001,"executionPath":["l1-interface","l2-orchestrator","l2-direct-response"]}
27
+ {"ts":"2026-03-26T17:46:37.437Z","traceId":"pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2","decision":"direct-answer","qaEnabled":false,"qaApproved":true,"qaRounds":0,"contextChunksUsed":0,"contextCharsSaved":0,"totalCost":0.0001,"executionPath":["l1-interface","l2-orchestrator","l2-direct-response"]}
@@ -0,0 +1,5 @@
1
+ {"ts":"2026-03-26T17:46:33.521Z","phase":"plan","userInput":"[Currently open file: /Users/jeffhobbs/CrewSwarm/apps/vibe/index.html]\n\nhi","sessionId":"a1d73702-1c81-42f8-810e-d8fb5982d103"}
2
+ {"ts":"2026-03-26T17:46:37.433Z","phase":"plan.completed","decision":"direct-answer","plan":{"decision":"direct-answer","reasoning":"User message is a simple greeting 'hi', which explicitly qualifies for DIRECT-ANSWER per constraints","directResponse":"Hi.","traceId":"pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2"}}
3
+ {"ts":"2026-03-26T17:46:37.435Z","phase":"execute","decision":"direct-answer"}
4
+ {"ts":"2026-03-26T17:46:37.436Z","phase":"validate.input","response":"Hi."}
5
+ {"ts":"2026-03-26T17:46:37.436Z","phase":"complete","decision":"direct-answer","totalCost":0.0001,"durationMs":3915,"qaApproved":true}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sessionId": "a1d73702-1c81-42f8-810e-d8fb5982d103",
3
3
  "createdAt": "2026-03-24T20:39:24.089Z",
4
- "updatedAt": "2026-03-26T07:46:58.178Z",
4
+ "updatedAt": "2026-03-26T17:46:37.438Z",
5
5
  "history": [
6
6
  {
7
7
  "input": "hi",
@@ -316,6 +316,15 @@
316
316
  "model": "unknown",
317
317
  "costUsd": 0.0001,
318
318
  "timestamp": "2026-03-26T07:46:58.178Z"
319
+ },
320
+ {
321
+ "input": "[Currently open file: /Users/jeffhobbs/CrewSwarm/apps/vibe/index.html]\n\nhi",
322
+ "response": "Hi.",
323
+ "decision": "direct-answer",
324
+ "agent": "unified-pipeline",
325
+ "model": "unknown",
326
+ "costUsd": 0.0001,
327
+ "timestamp": "2026-03-26T17:46:37.438Z"
319
328
  }
320
329
  ]
321
330
  }
@@ -76,3 +76,6 @@
76
76
  {"role":"user","content":"Vibe turns my local machine into a high-fidelity command center. I’m no longer just writing code; I’m orchestrating a crew in real-time with zero latency and a gorgeous UI that makes shipping feel effortless.\" Gemini","ts":1774516924358,"source":"cli","metadata":{"engine":"gemini","agentName":"You","agentEmoji":"👤"}}
77
77
  {"role":"assistant","content":"✦ **Gemini**\n\"Local files, global crew: the high-fidelity surface for shipping with a multi-agent pulse.\"","ts":1774516954600,"source":"cli","agent":"gemini","metadata":{"engine":"gemini","exitCode":0,"agentName":"gemini","agentEmoji":"⚡"}}
78
78
  {"role":"assistant","content":"\"The local-first command center for orchestrating your AI crew with zero latency and high-fidelity flow.\" — Gemini","ts":1774516964996,"source":"cli","agent":"gemini","metadata":{"engine":"gemini","exitCode":0,"agentName":"gemini","agentEmoji":"⚡"}}
79
+ {"role":"user","content":"[Currently open file: /Users/jeffhobbs/CrewSwarm/apps/vibe/index.html]\n\nhi","ts":1774547192834,"source":"cli","metadata":{"engine":"crew-cli","agentName":"You","agentEmoji":"👤"}}
80
+ {"role":"assistant","content":"Hi.","ts":1774547197540,"source":"cli","agent":"crew-cli","metadata":{"engine":"crew-cli","exitCode":0,"agentName":"crew-cli","agentEmoji":"⚡"}}
81
+ {"role":"user","content":"[Currently open file: /Users/jeffhobbs/CrewSwarm/apps/vibe/index.html]\n\nhi","ts":1774548205770,"source":"cli","metadata":{"engine":"gemini","agentName":"You","agentEmoji":"👤"}}
@@ -2452,9 +2452,11 @@
2452
2452
  <div class="diff-preview-render" id="diff-preview-render"></div>
2453
2453
  </div>
2454
2454
  </div>
2455
- <div class="actions">
2455
+ <div class="actions" style="display:flex;align-items:center;gap:8px;">
2456
2456
  <button class="secondary" onclick="rejectDiff()">Dismiss</button>
2457
- <button onclick="acceptDiff()">Looks Good</button>
2457
+ <button onclick="acceptDiff()">Accept</button>
2458
+ <button id="diff-accept-all-btn" onclick="acceptAllDiffs()" style="display:none;background:#10b981;">Accept All</button>
2459
+ <span id="diff-queue-counter" style="display:none;font-size:11px;color:var(--text-3);margin-left:4px;"></span>
2458
2460
  </div>
2459
2461
  </div>
2460
2462
  </div>
@@ -887,7 +887,61 @@ function createDefaultCliRelay(onChunk) {
887
887
  };
888
888
  }
889
889
 
890
+ function createClaudeStreamRelay(onChunk) {
891
+ let buffer = "";
892
+ let transcript = "";
893
+
894
+ return {
895
+ push(chunk) {
896
+ buffer += chunk.toString("utf8");
897
+ const lines = buffer.split("\n");
898
+ buffer = lines.pop() || "";
899
+ for (const line of lines) {
900
+ const trimmed = line.trim();
901
+ if (!trimmed) continue;
902
+ try {
903
+ const ev = JSON.parse(trimmed);
904
+ // Stream events with text deltas
905
+ if (ev.type === "stream_event") {
906
+ const inner = ev.event;
907
+ if (inner?.type === "content_block_delta" && inner?.delta?.type === "text_delta") {
908
+ const text = inner.delta.text || "";
909
+ transcript += text;
910
+ onChunk?.(text);
911
+ }
912
+ }
913
+ // Final assistant message
914
+ if (ev.type === "assistant") {
915
+ const content = ev.message?.content;
916
+ if (Array.isArray(content)) {
917
+ for (const c of content) {
918
+ if (c.type === "text" && c.text) {
919
+ transcript += c.text;
920
+ onChunk?.(c.text);
921
+ }
922
+ }
923
+ }
924
+ }
925
+ // Result event
926
+ if (ev.type === "result" && ev.result) {
927
+ if (!transcript) {
928
+ transcript = ev.result;
929
+ onChunk?.(ev.result);
930
+ }
931
+ }
932
+ } catch { /* not JSON, skip */ }
933
+ }
934
+ },
935
+ finish() {
936
+ return transcript.trim();
937
+ },
938
+ };
939
+ }
940
+
890
941
  function createCliRelay(engine, onChunk, onDone) {
942
+ if (engine === "claude") {
943
+ return createClaudeStreamRelay(onChunk);
944
+ }
891
945
  if (engine === "cursor") {
892
946
  return createCursorStreamRelay(onChunk, onDone);
893
947
  }
@@ -1044,17 +1098,18 @@ export function getCliCommand(engine, projectDir, message, modelOverride, resume
1044
1098
  case "claude":
1045
1099
  // Claude Code uses OAuth — no API key needed
1046
1100
  {
1047
- const args = ["-p", "--setting-sources", "user"];
1101
+ const args = ["-p", "--setting-sources", "user", "--output-format", "stream-json", "--verbose"];
1048
1102
  const model = modelOverride || process.env.CREWSWARM_CLAUDE_CODE_MODEL || "";
1049
1103
  // Add workspace directory context
1050
1104
  if (projectDir) args.push("--add-dir", projectDir);
1051
1105
  if (model) args.push("--model", model);
1052
1106
  // Resume: claude supports --resume <sessionId> for conversation continuity
1053
1107
  if (resumeSession?.sessionId) args.push("--resume", resumeSession.sessionId);
1108
+ args.push(message);
1054
1109
  return {
1055
1110
  command: "claude",
1056
1111
  args,
1057
- stdin: message,
1112
+ stdin: null,
1058
1113
  stripEnv: ["CLAUDECODE", "CLAUDE_CODE"],
1059
1114
  };
1060
1115
  }
@@ -1314,8 +1369,25 @@ function handleCliChatLocally(req, res, body) {
1314
1369
  }
1315
1370
  },
1316
1371
  })
1317
- .then(({ exitCode, transcript }) => {
1372
+ .then(async ({ exitCode, transcript }) => {
1318
1373
  if (!clientClosed) {
1374
+ // Scan for files changed during CLI execution and notify frontend for diff preview
1375
+ try {
1376
+ const since = Date.now() - 120_000; // Last 2 minutes
1377
+ const { execSync } = await import("node:child_process");
1378
+ const sinceUnix = Math.floor(since / 1000);
1379
+ const changedFiles = execSync(
1380
+ `find "${projectDir}" -maxdepth 5 -type f -newermt "@${sinceUnix}" ! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/.crew/*" 2>/dev/null | head -20`,
1381
+ { encoding: "utf8", timeout: 3000 }
1382
+ ).trim().split("\n").filter(Boolean);
1383
+ for (const filePath of changedFiles) {
1384
+ try {
1385
+ const relPath = path.relative(projectDir, filePath);
1386
+ const content = fs.readFileSync(filePath, "utf8");
1387
+ sendSseEvent(res, { type: "file-changed", path: relPath, content });
1388
+ } catch { /* binary or unreadable */ }
1389
+ }
1390
+ } catch { /* scan failed, non-fatal */ }
1319
1391
  sendSseEvent(res, { type: "done", exitCode, transcript });
1320
1392
  res.end();
1321
1393
  }
@@ -37,6 +37,7 @@ let chatMode = "crew-lead"; // 'crew-lead', 'direct', or 'cli'
37
37
  let selectedAgent = null;
38
38
  let ws = null;
39
39
  let watchWs = null; // WebSocket for CLI file changes
40
+ let cliTaskActive = false; // True while a CLI engine is working (for diff preview)
40
41
  let crewLeadEvents = null;
41
42
  let crewLeadEventsReconnectTimer = null;
42
43
  let lastAppendedAssistantContent = "";
@@ -1493,6 +1494,9 @@ async function sendChatMessage() {
1493
1494
  renderImagePreview();
1494
1495
  lastAppendedUserContent = message;
1495
1496
 
1497
+ // Track CLI task for diff preview
1498
+ cliTaskActive = true;
1499
+
1496
1500
  // Typing indicator
1497
1501
  const typingDiv = document.createElement("div");
1498
1502
  typingDiv.id = "typing-indicator";
@@ -1658,6 +1662,20 @@ async function sendChatMessage() {
1658
1662
  updateStreamingChatBubble(bubble, rawTranscript, { pending: true });
1659
1663
  }
1660
1664
  }
1665
+ } else if (event.type === "file-changed" && event.path && event.content) {
1666
+ // CLI engine wrote a file — show diff preview
1667
+ const oldContent = (activeTab?.path === event.path) ? activeTab.content : "";
1668
+ if (oldContent !== event.content) {
1669
+ showDiffPreview({ path: event.path, newContent: event.content });
1670
+ addTerminalLine(`🔄 ${event.path} changed — diff preview shown`, "info");
1671
+ } else {
1672
+ // Content same or file is new — just refresh
1673
+ if (activeTab?.path === event.path) {
1674
+ activeTab.content = event.content;
1675
+ editor?.setValue(event.content);
1676
+ }
1677
+ scheduleFileTreeRefresh();
1678
+ }
1661
1679
  } else if (event.type === "done") {
1662
1680
  exitCode = event.exitCode ?? 0;
1663
1681
  const stderrTail = stderrFilter.flush();
@@ -1821,7 +1839,9 @@ async function sendChatMessage() {
1821
1839
  }
1822
1840
 
1823
1841
  chatMessages.scrollTop = chatMessages.scrollHeight;
1842
+ cliTaskActive = false;
1824
1843
  } catch (err) {
1844
+ cliTaskActive = false;
1825
1845
  document.getElementById("typing-indicator")?.remove();
1826
1846
  // sourceInfo is now accessible here since it's declared outside the try block
1827
1847
  const respondingAgent = getErrorBubbleAgentId();
@@ -2371,44 +2391,42 @@ window.addEventListener("resize", () => {
2371
2391
 
2372
2392
  let diffEditor = null;
2373
2393
  let pendingChange = null;
2394
+ let diffQueue = []; // Queue of { path, newContent, oldContent } for multi-file diffs
2374
2395
 
2375
2396
  function parseFileChanges(agentReply) {
2376
- // Parse agent response for @@WRITE_FILE markers
2377
2397
  const fileRegex =
2378
2398
  /@@WRITE_FILE\s+(.+?)\n([\s\S]+?)(?=@@END_FILE|@@WRITE_FILE|$)/g;
2379
2399
  const changes = [];
2380
-
2381
2400
  let match;
2382
2401
  while ((match = fileRegex.exec(agentReply)) !== null) {
2383
- changes.push({
2384
- path: match[1].trim(),
2385
- newContent: match[2].trim(),
2386
- });
2402
+ changes.push({ path: match[1].trim(), newContent: match[2].trim() });
2387
2403
  }
2388
-
2389
2404
  return changes;
2390
2405
  }
2391
2406
 
2392
2407
  async function showDiffPreview(change) {
2408
+ // If a diff is already showing, queue this one
2409
+ const overlay = document.getElementById("diff-preview-overlay");
2410
+ if (overlay.classList.contains("visible") && pendingChange) {
2411
+ diffQueue.push(change);
2412
+ updateDiffCounter();
2413
+ return;
2414
+ }
2415
+
2393
2416
  pendingChange = change;
2394
2417
  await ensureEditorReady();
2395
2418
  await ensureEditorLanguage(detectLanguage(change.path));
2396
2419
 
2397
- const overlay = document.getElementById("diff-preview-overlay");
2398
2420
  const container = document.getElementById("diff-editor");
2399
2421
  const filePathEl = document.getElementById("diff-file-path");
2400
2422
 
2401
2423
  // Get current file content
2402
- const oldContent =
2403
- activeTab?.path === change.path
2404
- ? activeTab.content
2405
- : await readFile(change.path);
2406
-
2407
- // Create diff editor
2408
- if (diffEditor) {
2409
- diffEditor.dispose();
2410
- }
2424
+ const oldContent = activeTab?.path === change.path
2425
+ ? activeTab.content
2426
+ : await readFile(change.path).catch(() => "");
2427
+ pendingChange.oldContent = oldContent;
2411
2428
 
2429
+ if (diffEditor) diffEditor.dispose();
2412
2430
  diffEditor = monaco.editor.createDiffEditor(container, {
2413
2431
  theme: getPreferredMonacoTheme(),
2414
2432
  readOnly: false,
@@ -2416,59 +2434,101 @@ async function showDiffPreview(change) {
2416
2434
  renderSideBySide: true,
2417
2435
  automaticLayout: true,
2418
2436
  });
2419
-
2420
2437
  diffEditor.setModel({
2421
- original: monaco.editor.createModel(
2422
- oldContent,
2423
- detectLanguage(change.path),
2424
- ),
2425
- modified: monaco.editor.createModel(
2426
- change.newContent,
2427
- detectLanguage(change.path),
2428
- ),
2438
+ original: monaco.editor.createModel(oldContent, detectLanguage(change.path)),
2439
+ modified: monaco.editor.createModel(change.newContent, detectLanguage(change.path)),
2429
2440
  });
2430
2441
 
2431
2442
  filePathEl.textContent = change.path;
2432
2443
  overlay.classList.add("visible");
2444
+ updateDiffCounter();
2433
2445
  }
2434
2446
 
2435
- window.acceptDiff = async function () {
2436
- if (!pendingChange) return;
2447
+ function updateDiffCounter() {
2448
+ const counter = document.getElementById("diff-queue-counter");
2449
+ const acceptAllBtn = document.getElementById("diff-accept-all-btn");
2450
+ const total = diffQueue.length + (pendingChange ? 1 : 0);
2451
+ if (counter) {
2452
+ counter.textContent = total > 1 ? `${total} files changed` : "";
2453
+ counter.style.display = total > 1 ? "inline" : "none";
2454
+ }
2455
+ if (acceptAllBtn) {
2456
+ acceptAllBtn.style.display = total > 1 ? "inline-block" : "none";
2457
+ }
2458
+ }
2437
2459
 
2460
+ async function applyOneChange(change) {
2438
2461
  try {
2439
2462
  await fetchJSON(`${STUDIO_API}/api/studio/file-content`, {
2440
2463
  method: "POST",
2441
- headers: {
2442
- "Content-Type": "application/json",
2443
- },
2444
- body: JSON.stringify({
2445
- path: pendingChange.path,
2446
- content: pendingChange.newContent,
2447
- }),
2464
+ headers: { "Content-Type": "application/json" },
2465
+ body: JSON.stringify({ path: change.path, content: change.newContent }),
2448
2466
  });
2449
-
2450
- const openTab = openTabs.find((tab) => tab.path === pendingChange.path);
2467
+ const openTab = openTabs.find((tab) => tab.path === change.path);
2451
2468
  if (openTab) {
2452
- openTab.content = pendingChange.newContent;
2453
- if (activeTab?.path === pendingChange.path) {
2454
- editor.setValue(pendingChange.newContent);
2455
- }
2469
+ openTab.content = change.newContent;
2470
+ if (activeTab?.path === change.path) editor?.setValue(change.newContent);
2456
2471
  }
2472
+ addTerminalLine(`✅ Applied ${change.path}`, "success");
2473
+ } catch (err) {
2474
+ addTerminalLine(`❌ Failed to apply ${change.path}: ${err.message}`, "error");
2475
+ }
2476
+ }
2457
2477
 
2458
- await loadFileTree();
2459
- addTerminalLine(`✅ Applied proposed changes to ${pendingChange.path}`, "success");
2478
+ async function showNextDiff() {
2479
+ if (diffQueue.length > 0) {
2480
+ const next = diffQueue.shift();
2481
+ pendingChange = null;
2482
+ await showDiffPreview(next);
2483
+ } else {
2460
2484
  closeDiffPreview();
2461
- } catch (err) {
2462
- addTerminalLine(`❌ Failed to apply diff: ${err.message}`, "error");
2485
+ await loadFileTree();
2463
2486
  }
2487
+ }
2488
+
2489
+ window.acceptDiff = async function () {
2490
+ if (!pendingChange) return;
2491
+ await applyOneChange(pendingChange);
2492
+ pendingChange = null;
2493
+ await showNextDiff();
2464
2494
  };
2465
2495
 
2466
- window.rejectDiff = function () {
2467
- addTerminalLine(
2468
- `🗑️ Dismissed proposed changes for ${pendingChange?.path}`,
2469
- "warning",
2470
- );
2496
+ window.acceptAllDiffs = async function () {
2497
+ // Accept current + all queued
2498
+ if (pendingChange) {
2499
+ await applyOneChange(pendingChange);
2500
+ pendingChange = null;
2501
+ }
2502
+ for (const change of diffQueue) {
2503
+ await applyOneChange(change);
2504
+ }
2505
+ diffQueue = [];
2471
2506
  closeDiffPreview();
2507
+ await loadFileTree();
2508
+ addTerminalLine(`✅ All changes applied`, "success");
2509
+ };
2510
+
2511
+ window.rejectDiff = async function () {
2512
+ if (pendingChange) {
2513
+ const openTab = openTabs.find((tab) => tab.path === pendingChange.path);
2514
+ if (openTab && pendingChange.oldContent !== undefined && openTab.content !== pendingChange.newContent) {
2515
+ try {
2516
+ await fetchJSON(`${STUDIO_API}/api/studio/file-content`, {
2517
+ method: "POST",
2518
+ headers: { "Content-Type": "application/json" },
2519
+ body: JSON.stringify({ path: pendingChange.path, content: pendingChange.oldContent }),
2520
+ });
2521
+ if (activeTab?.path === pendingChange.path) editor?.setValue(pendingChange.oldContent);
2522
+ addTerminalLine(`↩️ Reverted ${pendingChange.path}`, "warning");
2523
+ } catch {
2524
+ addTerminalLine(`⚠️ Could not revert ${pendingChange.path}`, "error");
2525
+ }
2526
+ } else {
2527
+ addTerminalLine(`🗑️ Dismissed ${pendingChange.path}`, "warning");
2528
+ }
2529
+ }
2530
+ pendingChange = null;
2531
+ await showNextDiff();
2472
2532
  };
2473
2533
 
2474
2534
  window.addEventListener("studio-themechange", () => {
@@ -2595,8 +2655,15 @@ function connectStudioWatch() {
2595
2655
  // File changed by CLI
2596
2656
  addTerminalLine(`🔄 ${msg.path} updated by CLI`, "info");
2597
2657
 
2598
- // If file is currently open in editor, reload it
2599
- if (activeTab && activeTab.path === msg.path && msg.content) {
2658
+ // If file is open in editor AND a CLI task is active, show diff preview
2659
+ if (cliTaskActive && activeTab && activeTab.path === msg.path && msg.content) {
2660
+ const oldContent = activeTab.content || "";
2661
+ if (oldContent !== msg.content) {
2662
+ showDiffPreview({ path: msg.path, newContent: msg.content });
2663
+ addTerminalLine(` ↳ Diff preview shown — accept or dismiss`, "info");
2664
+ }
2665
+ } else if (activeTab && activeTab.path === msg.path && msg.content) {
2666
+ // No active task — silently reload as before
2600
2667
  activeTab.content = msg.content;
2601
2668
  editor?.setValue(msg.content);
2602
2669
  addTerminalLine(` ↳ Reloaded in editor`, "success");
@@ -2605,7 +2672,13 @@ function connectStudioWatch() {
2605
2672
  // Refresh file tree to show changes
2606
2673
  scheduleFileTreeRefresh();
2607
2674
  } else if (msg.type === "file-created") {
2608
- addTerminalLine(`✨ ${msg.path} created by CLI`, "success");
2675
+ // For new files during active CLI task, show diff (old content is empty)
2676
+ if (cliTaskActive && msg.content) {
2677
+ showDiffPreview({ path: msg.path, newContent: msg.content });
2678
+ addTerminalLine(`✨ ${msg.path} created by CLI — diff preview shown`, "success");
2679
+ } else {
2680
+ addTerminalLine(`✨ ${msg.path} created by CLI`, "success");
2681
+ }
2609
2682
  scheduleFileTreeRefresh();
2610
2683
  } else if (msg.type === "file-deleted") {
2611
2684
  addTerminalLine(`🗑️ ${msg.path} deleted by CLI`, "warning");
package/crew-lead.mjs CHANGED
@@ -33,8 +33,10 @@ import {
33
33
  DISPATCH_TIMEOUT_MS,
34
34
  DISPATCH_CLAIMED_TIMEOUT_MS,
35
35
  loadCursorWavesEnabled,
36
- loadClaudeCodeEnabled
36
+ loadClaudeCodeEnabled,
37
+ loadTmuxBridgeEnabled
37
38
  } from "./lib/runtime/config.mjs";
39
+ import { _reset as resetTmuxBridge } from "./lib/bridges/tmux-bridge.mjs";
38
40
  import {
39
41
  CREWSWARM_TOOL_NAMES,
40
42
  readAgentTools,
@@ -157,6 +159,7 @@ function broadcastSSE(payload) {
157
159
 
158
160
  let _cursorWavesEnabled = loadCursorWavesEnabled();
159
161
  let _claudeCodeEnabled = loadClaudeCodeEnabled();
162
+ let _tmuxBridgeEnabled = loadTmuxBridgeEnabled();
160
163
 
161
164
  const BG_CONSCIOUSNESS_INTERVAL_MS = Number(process.env.CREWSWARM_BG_CONSCIOUSNESS_INTERVAL_MS) || 15 * 60 * 1000;
162
165
  let BG_CONSCIOUSNESS_MODEL = (() => {
@@ -501,6 +504,15 @@ const bgConsciousnessRef = {
501
504
  };
502
505
  const cursorWavesRef = { get enabled() { return _cursorWavesEnabled; }, set enabled(v) { _cursorWavesEnabled = v; } };
503
506
  const claudeCodeRef = { get enabled() { return _claudeCodeEnabled; }, set enabled(v) { _claudeCodeEnabled = v; } };
507
+ const tmuxBridgeRef = {
508
+ get enabled() { return _tmuxBridgeEnabled; },
509
+ set enabled(v) {
510
+ _tmuxBridgeEnabled = v;
511
+ // Sync env var and reset detection cache so tmux-bridge module picks up runtime changes
512
+ process.env.CREWSWARM_TMUX_BRIDGE = v ? "1" : "0";
513
+ resetTmuxBridge();
514
+ },
515
+ };
504
516
 
505
517
  // connectRT is initialized after RT_URL/RT_TOKEN — use a mutable ref so HTTP server can call it
506
518
  let _connectRT = () => { throw new Error("connectRT not initialized yet"); };
@@ -544,6 +556,7 @@ initHttpServer({
544
556
  bgConsciousnessIntervalMs: BG_CONSCIOUSNESS_INTERVAL_MS,
545
557
  cursorWavesRef,
546
558
  claudeCodeRef,
559
+ tmuxBridgeRef,
547
560
  });
548
561
  createAndStartServer(PORT);
549
562
 
@@ -241,8 +241,6 @@ export function buildCLICommand(
241
241
  bin: process.env.CLAUDE_CLI_BIN || join(homedir(), ".local/bin/claude"),
242
242
  args: [
243
243
  "-p",
244
- "--setting-sources",
245
- "user",
246
244
  "--dangerously-skip-permissions",
247
245
  "--output-format",
248
246
  "stream-json",