crewswarm 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  3. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  5. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  6. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  8. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  9. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  10. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  11. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  12. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  14. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  18. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  22. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  23. package/apps/dashboard/dist/index.html +135 -15
  24. package/apps/dashboard/dist/index.html.br +0 -0
  25. package/apps/dashboard/dist/index.html.gz +0 -0
  26. package/apps/vibe/README.md +2 -2
  27. package/apps/vibe/package.json +1 -1
  28. package/apps/vibe/server.mjs +101 -56
  29. package/crew-lead.mjs +34 -4
  30. package/lib/bridges/cli-executor.mjs +1 -1
  31. package/lib/bridges/gateway-ws.mjs +4 -0
  32. package/lib/browser/passthrough-stderr.js +1 -0
  33. package/lib/chat/project-messages.mjs +3 -5
  34. package/lib/cli-process-tracker.mjs +3 -2
  35. package/lib/contacts/identity-linker.mjs +1 -0
  36. package/lib/crew-judge/judge.mjs +19 -18
  37. package/lib/crew-lead/agent-manager.mjs +1 -1
  38. package/lib/crew-lead/background.mjs +14 -1
  39. package/lib/crew-lead/chat-handler.mjs +38 -1
  40. package/lib/crew-lead/http-server.mjs +106 -57
  41. package/lib/crew-lead/llm-caller.mjs +24 -8
  42. package/lib/crew-lead/prompts.mjs +14 -1
  43. package/lib/crew-lead/tools.mjs +3 -2
  44. package/lib/crew-lead/wave-dispatcher.mjs +19 -5
  45. package/lib/crew-lead/ws-router.mjs +219 -27
  46. package/lib/engines/crew-cli.mjs +1 -1
  47. package/lib/engines/engine-registry.mjs +14 -3
  48. package/lib/engines/rt-envelope.mjs +1 -0
  49. package/lib/engines/runners.mjs +28 -4
  50. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  51. package/lib/integrations/code-search.mjs +4 -3
  52. package/lib/memory/shared-adapter.mjs +23 -10
  53. package/lib/pipeline/manager.mjs +2 -1
  54. package/lib/runtime/config.mjs +1 -1
  55. package/lib/runtime/paths.mjs +12 -8
  56. package/lib/runtime/spending.mjs +2 -1
  57. package/package.json +42 -14
  58. package/scripts/capture-build-flow.mjs +118 -0
  59. package/scripts/coverage-report.mjs +209 -0
  60. package/scripts/coverage-summary.mjs +47 -0
  61. package/scripts/dashboard-validation.mjs +76 -0
  62. package/scripts/dashboard.mjs +1667 -551
  63. package/scripts/generate-openapi.mjs +683 -277
  64. package/scripts/live-bridge-matrix.mjs +79 -0
  65. package/scripts/live-cli-matrix.mjs +166 -0
  66. package/scripts/live-crewchat-check.mjs +42 -0
  67. package/scripts/live-engine-matrix.mjs +50 -0
  68. package/scripts/live-provider-failover-matrix.mjs +107 -0
  69. package/scripts/live-provider-matrix.mjs +228 -0
  70. package/scripts/restart-all-from-repo.sh +4 -4
  71. package/scripts/restart-service.sh +12 -9
  72. package/scripts/smoke-dispatch.mjs +4 -1
  73. package/scripts/test-blast-radius.mjs +204 -0
  74. package/scripts/test-report-summary.mjs +88 -0
  75. package/scripts/test-reporter.mjs +651 -0
  76. package/scripts/test-rerun.mjs +136 -0
  77. package/scripts/tmux-bridge +130 -0
  78. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
  79. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  80. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  81. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  82. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  83. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  84. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  85. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  86. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  87. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  88. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  89. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  90. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  91. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
  92. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  93. package/apps/dashboard/index.html +0 -6529
  94. package/apps/dashboard/package.json +0 -15
  95. package/apps/dashboard/src/app.js +0 -2828
  96. package/apps/dashboard/src/app.js.br +0 -0
  97. package/apps/dashboard/src/app.js.gz +0 -0
  98. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  99. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  100. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  101. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  102. package/apps/dashboard/src/cli-process.js +0 -208
  103. package/apps/dashboard/src/cli-process.js.br +0 -0
  104. package/apps/dashboard/src/cli-process.js.gz +0 -0
  105. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  106. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  107. package/apps/dashboard/src/core/api.js +0 -18
  108. package/apps/dashboard/src/core/api.js.br +0 -0
  109. package/apps/dashboard/src/core/dom.js +0 -228
  110. package/apps/dashboard/src/core/dom.js.br +0 -0
  111. package/apps/dashboard/src/core/state.js +0 -91
  112. package/apps/dashboard/src/core/state.js.br +0 -0
  113. package/apps/dashboard/src/core/task-manager.js +0 -134
  114. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  115. package/apps/dashboard/src/orchestration-status.js +0 -127
  116. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  117. package/apps/dashboard/src/setup-wizard.js +0 -562
  118. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  119. package/apps/dashboard/src/styles.css +0 -2085
  120. package/apps/dashboard/src/styles.css.br +0 -0
  121. package/apps/dashboard/src/styles.css.gz +0 -0
  122. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  123. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  125. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  127. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  129. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  131. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  133. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  135. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  137. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  139. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  140. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  141. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  142. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  143. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  144. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  145. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  146. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  147. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  148. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  149. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  150. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  151. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  152. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  153. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  154. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  155. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  156. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  157. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  158. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  159. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  160. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  161. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  162. package/apps/vibe/.crew/cost.json +0 -17
  163. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  164. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  172. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  173. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  174. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  175. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  176. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  177. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  178. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  179. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  180. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  181. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  182. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  183. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  184. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  185. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  186. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  187. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  188. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  189. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  190. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  191. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  192. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  193. package/apps/vibe/.crew/sandbox.json +0 -7
  194. package/apps/vibe/.crew/session.json +0 -330
  195. package/apps/vibe/.crew/training-data.jsonl +0 -0
  196. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  197. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  198. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  199. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  200. package/apps/vibe/ARCHITECTURE.md +0 -3393
  201. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  202. package/apps/vibe/ROADMAP.md +0 -41
  203. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  204. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  205. package/apps/vibe/capture-demo.mjs +0 -160
  206. package/apps/vibe/capture-full-demo.mjs +0 -255
  207. package/apps/vibe/capture-quickstart.mjs +0 -256
  208. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  209. package/apps/vibe/capture-vibe-video.mjs +0 -260
  210. package/apps/vibe/check-buttons.js +0 -41
  211. package/apps/vibe/diagnose.html +0 -106
  212. package/apps/vibe/fix-buttons.js +0 -103
  213. package/apps/vibe/index.html +0 -3404
  214. package/apps/vibe/package-lock.json +0 -920
  215. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  216. package/apps/vibe/src/main.js +0 -2940
  217. package/apps/vibe/src/register-all-languages.js +0 -98
  218. package/apps/vibe/start-studio.sh +0 -11
  219. package/apps/vibe/test/accessibility-tests.js +0 -77
  220. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  221. package/apps/vibe/test/performance-tests.js +0 -120
  222. package/apps/vibe/test/security-tests.js +0 -213
  223. package/apps/vibe/tests/e2e.local.mjs +0 -54
  224. package/apps/vibe/tests/server.smoke.mjs +0 -106
  225. package/apps/vibe/update_website.mjs +0 -74
  226. package/apps/vibe/vite.config.js +0 -19
  227. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  228. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -1,538 +0,0 @@
1
- /**
2
- * Swarm (sessions), RT Messages, and DLQ tab — extracted from app.js
3
- * Deps: getJSON, postJSON (core/api), escHtml, showNotification, fmt, createdAt (core/dom)
4
- * Inject: initSwarmTab({ hideAllViews, setNavActive })
5
- */
6
-
7
- import { getJSON, postJSON } from '../core/api.js';
8
- import { escHtml, showNotification, fmt, createdAt } from '../core/dom.js';
9
- import { state, persistState, restoreScrollPosition } from '../core/state.js';
10
-
11
- let _hideAllViews = () => {};
12
- let _setNavActive = () => {};
13
-
14
- export function initSwarmTab({ hideAllViews, setNavActive } = {}) {
15
- _hideAllViews = hideAllViews || _hideAllViews;
16
- _setNavActive = setNavActive || _setNavActive;
17
- }
18
-
19
- // ── Swarm (Sessions) ───────────────────────────────────────────────────────────
20
-
21
- let _selected = state.selected || null;
22
- let _selectedEngine = state.selectedEngine || 'opencode'; // opencode, claude, codex, gemini, crew-cli
23
-
24
- export async function loadSessions() {
25
- const box = document.getElementById('sessions');
26
- if (box) box.innerHTML = '<div style="padding:20px;">Loading…</div>';
27
-
28
- // Add engine selector dropdown if not already present
29
- const container = box.parentElement;
30
- let engineSelector = document.getElementById('engine-selector');
31
- if (!engineSelector) {
32
- engineSelector = document.createElement('div');
33
- engineSelector.id = 'engine-selector';
34
- engineSelector.style.cssText = 'padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;';
35
- engineSelector.innerHTML = '<label style="font-size:13px;font-weight:500;color:var(--text-2);">CLI:</label>'
36
- + '<select id="engine-select" style="padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg-1);color:var(--text-1);font-size:13px;cursor:pointer;">'
37
- + '<option value="opencode">OpenCode</option>'
38
- + '<option value="claude">Claude Code</option>'
39
- + '<option value="codex">Codex CLI</option>'
40
- + '<option value="gemini">Gemini CLI</option>'
41
- + '<option value="crew-cli">crew-cli</option>'
42
- + '</select>'
43
- + '<span style="font-size:12px;color:var(--text-3);margin-left:8px;" id="session-count"></span>';
44
- container.insertBefore(engineSelector, box);
45
-
46
- document.getElementById('engine-select').addEventListener('change', (e) => {
47
- _selectedEngine = e.target.value;
48
- state.selectedEngine = _selectedEngine;
49
- persistState();
50
- _selected = null; // Reset selection when switching engines
51
- loadSessions();
52
- });
53
- }
54
-
55
- // Set dropdown to current engine
56
- const select = document.getElementById('engine-select');
57
- if (select) select.value = _selectedEngine;
58
-
59
- try {
60
- const activeProjectId = state.chatActiveProjectId || 'general';
61
- const endpoint =
62
- '/api/engine-sessions?engine=' + encodeURIComponent(_selectedEngine) +
63
- '&projectId=' + encodeURIComponent(activeProjectId);
64
- const result = await getJSON(endpoint);
65
- const data = result.sessions || result || [];
66
-
67
- const box2 = document.getElementById('sessions');
68
- const countEl = document.getElementById('session-count');
69
-
70
- box2.innerHTML = '';
71
-
72
- if (!data.length) {
73
- const engineNames = {
74
- 'opencode': 'OpenCode',
75
- 'claude': 'Claude Code',
76
- 'codex': 'Codex CLI',
77
- 'gemini': 'Gemini CLI',
78
- 'crew-cli': 'crew-cli'
79
- };
80
- const engineName = engineNames[_selectedEngine] || _selectedEngine;
81
-
82
- box2.innerHTML = '<div style="padding:20px 16px;">'
83
- + `<div style="font-size:13px;font-weight:600;margin-bottom:6px;">No ${engineName} sessions</div>`
84
- + '<div style="font-size:12px;color:var(--text-3);line-height:1.6;">'
85
- + `No session history found for <strong>${engineName}</strong>. `
86
- + 'Run a task using this engine to see sessions here.'
87
- + '</div></div>';
88
- if (countEl) countEl.textContent = '';
89
- return;
90
- }
91
-
92
- if (countEl) countEl.textContent = `${data.length} session${data.length !== 1 ? 's' : ''}`;
93
- if (!_selected && data[0]) _selected = data[0].id;
94
-
95
- function crewAgentFromTitle(title) {
96
- if (!title || typeof title !== 'string') return null;
97
- const m = title.match(/\[?(crew-\w+)\]?/);
98
- return m ? m[1] : null;
99
- }
100
- function inferAgentFromTitle(title) {
101
- if (!title || typeof title !== 'string') return null;
102
- if (/\bFixer\b|fixer\s+task|fix\s+.*\.py|syntax\s+error/i.test(title)) return 'fixer';
103
- if (/\bQA\b|qa\s+audit|audit:/i.test(title)) return 'qa';
104
- if (/\bPM\b|crew-pm|roadmap\b/i.test(title)) return 'pm';
105
- if (/\bCoder\b|coder\s+task|frontend\b|backend\b/i.test(title)) return 'coder';
106
- if (/\bSecurity\b|security\s+review/i.test(title)) return 'security';
107
- if (/\bCopywriter\b|copy\s+task/i.test(title)) return 'copywriter';
108
- return null;
109
- }
110
- function isOpencodeCodename(slug) {
111
- return slug && /^[a-z]+-[a-z]+$/.test(slug) && !slug.startsWith('crew-');
112
- }
113
-
114
- data.forEach(s => {
115
- const div = document.createElement('div');
116
- const sessionId = s.id || s.sessionId || '';
117
- div.className = 'row' + (sessionId === _selected ? ' active' : '');
118
- div.onclick = () => { _selected = sessionId; state.selected = sessionId; persistState(); loadSessions(); loadMessages(); };
119
-
120
- // Extract metadata based on engine
121
- let title = s.title || s.slug || sessionId;
122
- let meta = s.directory || '';
123
- let badge = '';
124
-
125
- if (_selectedEngine === 'opencode') {
126
- const crewAgent = crewAgentFromTitle(title);
127
- const inferred = inferAgentFromTitle(title);
128
- const slug = s.slug || '';
129
- const agent = crewAgent || (slug && !isOpencodeCodename(slug) ? slug : null) || inferred;
130
- const slugLabel = isOpencodeCodename(slug) ? ' (' + slug + ')' : '';
131
- badge = agent ? ('Assigned to: ' + agent + slugLabel) : (slug ? ('Assigned to: ' + slug + ' (OpenCode session)') : '');
132
- } else if (_selectedEngine === 'claude') {
133
- meta = s.file ? s.file.split('/').pop().replace('.jsonl', '') : '';
134
- } else if (_selectedEngine === 'codex') {
135
- meta = s.file || '';
136
- } else if (_selectedEngine === 'gemini') {
137
- meta = 'Project: ' + sessionId;
138
- } else if (_selectedEngine === 'crew-cli') {
139
- badge = s.engine + ' / ' + s.project;
140
- meta = s.file || '';
141
- }
142
-
143
- // Limit title length
144
- if (title.length > 80) title = title.slice(0, 77) + '...';
145
-
146
- div.innerHTML = '<div><strong>' + escHtml(title) + '</strong></div>'
147
- + (meta ? '<div class="meta">' + escHtml(meta) + '</div>' : '')
148
- + (badge ? '<div class="meta" style="font-size:11px;color:var(--accent);">' + escHtml(badge) + '</div>' : '');
149
- box2.appendChild(div);
150
- });
151
- } catch(e) {
152
- const box = document.getElementById('sessions');
153
- if (box) box.innerHTML = '<div class="meta" style="padding:20px; color:var(--red-hi);">Error loading sessions.</div>';
154
- }
155
- }
156
-
157
- export async function loadMessages() {
158
- const box = document.getElementById('messages');
159
- if (!_selected) { if (box) box.innerHTML = '<div class="meta">No session selected.</div>'; return; }
160
- try {
161
- // For OpenCode, use the old endpoint. For others, messages are embedded in the session data
162
- if (_selectedEngine === 'opencode') {
163
- const data = await getJSON('/api/messages?session=' + encodeURIComponent(_selected));
164
- box.innerHTML = '';
165
- data.slice(-40).forEach(m => {
166
- const text = (m.parts || []).filter(p => p.type === 'text').map(p => p.text).join('').trim();
167
- if (!text) return;
168
- const div = document.createElement('div');
169
- div.className = 'msg ' + ((m.info && m.info.role) === 'assistant' ? 'a' : 'u');
170
- div.innerHTML = '<div class="meta">' + (m.info && m.info.role) + ' • ' + fmt(createdAt(m.info)) + '</div><div class="t"></div>';
171
- div.querySelector('.t').textContent = text;
172
- box.appendChild(div);
173
- });
174
- } else {
175
- // For other engines, find the session in the cached data
176
- const apiMap = {
177
- 'claude': '/api/claude-sessions',
178
- 'codex': '/api/codex-sessions',
179
- 'gemini': '/api/gemini-sessions',
180
- 'crew-cli': '/api/crew-cli-sessions'
181
- };
182
- const endpoint = apiMap[_selectedEngine];
183
- if (!endpoint) {
184
- box.innerHTML = '<div class="meta">Engine not supported</div>';
185
- return;
186
- }
187
-
188
- const result = await getJSON(endpoint);
189
- const sessions = result.sessions || result || [];
190
- const session = sessions.find(s => s.id === _selected || s.sessionId === _selected);
191
-
192
- if (!session || !session.messages) {
193
- box.innerHTML = '<div class="meta">No messages found</div>';
194
- return;
195
- }
196
-
197
- box.innerHTML = '';
198
- session.messages.slice(-40).forEach(m => {
199
- const div = document.createElement('div');
200
- div.className = 'msg ' + (m.role === 'assistant' ? 'a' : 'u');
201
- const ts = m.ts ? new Date(m.ts).toLocaleString() : '';
202
- div.innerHTML = '<div class="meta">' + m.role + (ts ? ' • ' + ts : '') + '</div><div class="t"></div>';
203
- div.querySelector('.t').textContent = m.text || '';
204
- box.appendChild(div);
205
- });
206
- }
207
- box.scrollTop = box.scrollHeight;
208
- } catch(e) { if (box) box.innerHTML = '<div class="meta">Error: ' + e.message + '</div>'; }
209
- }
210
-
211
- export function showSwarm() {
212
- _hideAllViews();
213
- document.getElementById('sessionsView').classList.add('active');
214
- _setNavActive('navSwarm');
215
- state.activeTab = 'swarm';
216
- persistState();
217
-
218
- // Check if sessions are already rendered (DOM preserved from previous visit)
219
- const sessionsBox = document.getElementById('sessions');
220
- const alreadyLoaded = sessionsBox && sessionsBox.children.length > 1;
221
- if (!alreadyLoaded) {
222
- loadSessions(); loadMessages();
223
- } else {
224
- restoreScrollPosition('swarm');
225
- }
226
- }
227
-
228
- // ── RT Messages ────────────────────────────────────────────────────────────────
229
-
230
- let _rtPaused = false;
231
- let _rtFilter = 'tasks';
232
- let _rtSearch = '';
233
- let _rtSeenIds = new Set();
234
- const RT_SKIP = new Set(['agent.heartbeat','agent.online','agent.offline']);
235
- const RT_TASK_TYPES = new Set(['task.dispatched','task.done','task.completed','task.failed','task.cancelled','task.started','task.reply']);
236
-
237
- function _rtMatchesFilter(m) {
238
- if (RT_SKIP.has(m.type)) return false;
239
- const payload = m.payload || {};
240
- const text = payload.reply || payload.prompt || payload.message || payload.content || '';
241
- if (!text || text === 'run_task') return false;
242
- if (_rtFilter === 'tasks' && !RT_TASK_TYPES.has(m.type)) return false;
243
- if (_rtFilter === 'replies') {
244
- if (!(payload.reply || payload.message || payload.content)) return false;
245
- }
246
- if (_rtSearch) {
247
- const q = _rtSearch.toLowerCase();
248
- if (!(m.from||'').toLowerCase().includes(q) &&
249
- !(m.to ||'').toLowerCase().includes(q) &&
250
- !text.toLowerCase().includes(q) &&
251
- !(m.type||'').toLowerCase().includes(q)) return false;
252
- }
253
- return true;
254
- }
255
-
256
- const RT_PHASE_STYLE = {
257
- 'task.dispatched': { color: 'var(--purple)', label: 'dispatched' },
258
- 'task.started': { color: 'var(--amber)', label: 'started' },
259
- 'task.done': { color: 'var(--green-hi)', label: 'done' },
260
- 'task.completed': { color: 'var(--green-hi)', label: 'completed' },
261
- 'task.reply': { color: 'var(--accent)', label: 'reply' },
262
- 'task.failed': { color: 'var(--red-hi)', label: 'failed' },
263
- 'task.cancelled': { color: 'var(--text-3)', label: 'cancelled' },
264
- };
265
-
266
- function _rtBuildElement(m) {
267
- const payload = m.payload || {};
268
- const fullText = payload.reply || payload.prompt || payload.message || payload.content || '';
269
- const type = m.type || '';
270
- const phase = RT_PHASE_STYLE[type];
271
- const timeStr = m.ts ? new Date(m.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
272
- const firstLine = fullText.split('\n').map(l => l.trim()).find(l => l.length > 2) || fullText;
273
- const summary = firstLine.length > 90 ? firstLine.slice(0, 90) + '…' : firstLine;
274
- const hasMore = fullText.length > summary.length || fullText.split('\n').length > 1;
275
-
276
- const row = document.createElement('div');
277
- row.style.cssText = 'display:grid;grid-template-columns:auto auto 1fr auto;align-items:center;gap:10px;padding:7px 10px;border-radius:6px;cursor:' + (hasMore ? 'pointer' : 'default') + ';transition:background .12s;border-bottom:1px solid var(--border);';
278
- row.onmouseenter = () => { row.style.background = 'var(--bg-2)'; };
279
- row.onmouseleave = () => { row.style.background = ''; };
280
-
281
- const agentsEl = document.createElement('div');
282
- agentsEl.style.cssText = 'display:flex;align-items:center;gap:5px;white-space:nowrap;min-width:0;';
283
- const fromPill = document.createElement('span');
284
- fromPill.style.cssText = 'font-size:11px;font-weight:600;color:var(--text-1);max-width:110px;overflow:hidden;text-overflow:ellipsis;';
285
- fromPill.textContent = (m.from || '?').replace('crew-', '');
286
- fromPill.title = m.from || '';
287
- agentsEl.appendChild(fromPill);
288
- if (m.to && m.to !== m.from) {
289
- const arrow = document.createElement('span');
290
- arrow.style.cssText = 'font-size:10px;color:var(--text-3);flex-shrink:0;';
291
- arrow.textContent = '→';
292
- const toPill = document.createElement('span');
293
- toPill.style.cssText = 'font-size:11px;color:var(--text-2);max-width:110px;overflow:hidden;text-overflow:ellipsis;';
294
- toPill.textContent = (m.to || '').replace('crew-', '');
295
- toPill.title = m.to || '';
296
- agentsEl.appendChild(arrow);
297
- agentsEl.appendChild(toPill);
298
- }
299
-
300
- const badgeContainer = document.createElement('div');
301
- badgeContainer.style.cssText = 'display:flex;align-items:center;gap:4px;flex-shrink:0;';
302
-
303
- const badge = document.createElement('span');
304
- const ps = phase || { color: 'var(--text-3)', label: type.split('.').pop() || type };
305
- badge.style.cssText = 'font-size:10px;font-weight:600;padding:2px 7px;border-radius:20px;white-space:nowrap;flex-shrink:0;color:#fff;background:' + ps.color + ';letter-spacing:.03em;';
306
- badge.textContent = ps.label;
307
- badgeContainer.appendChild(badge);
308
-
309
- // Engine badge for task.done messages
310
- if (type === 'task.done' && payload.engineUsed) {
311
- const engineColors = {
312
- 'claude': '#e07a5f', // warm coral for Claude Code
313
- 'codex': '#8338ec', // purple for Codex
314
- 'cursor': '#3d405b', // dark gray for Cursor
315
- 'opencode': '#06d6a0', // teal for OpenCode
316
- 'gemini': '#4285f4', // Google blue for Gemini
317
- 'docker-sandbox': '#0db7ed' // Docker blue
318
- };
319
- const engineLabels = {
320
- 'claude': '🤖',
321
- 'codex': '🟣',
322
- 'cursor': '🖱',
323
- 'opencode': '⚡',
324
- 'gemini': '✨',
325
- 'docker-sandbox': '🐳'
326
- };
327
- const engine = payload.engineUsed;
328
- const engineBadge = document.createElement('span');
329
- engineBadge.style.cssText = 'font-size:10px;font-weight:600;padding:2px 6px;border-radius:20px;white-space:nowrap;flex-shrink:0;color:#fff;background:' + (engineColors[engine] || 'var(--text-3)') + ';';
330
- engineBadge.textContent = (engineLabels[engine] || '') + ' ' + engine;
331
- engineBadge.title = 'Executed by ' + engine;
332
- badgeContainer.appendChild(engineBadge);
333
- }
334
-
335
- const preview = document.createElement('span');
336
- preview.style.cssText = 'font-size:12px;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;';
337
- preview.textContent = summary;
338
-
339
- const right = document.createElement('div');
340
- right.style.cssText = 'display:flex;align-items:center;gap:6px;flex-shrink:0;';
341
- const timeEl = document.createElement('span');
342
- timeEl.style.cssText = 'font-size:10px;color:var(--text-3);white-space:nowrap;';
343
- timeEl.textContent = timeStr;
344
- right.appendChild(timeEl);
345
- if (hasMore) {
346
- const hint = document.createElement('span');
347
- hint.style.cssText = 'font-size:10px;color:var(--text-3);';
348
- hint.textContent = '▸';
349
- right.appendChild(hint);
350
- }
351
-
352
- row.appendChild(agentsEl);
353
- row.appendChild(badgeContainer);
354
- row.appendChild(preview);
355
- row.appendChild(right);
356
-
357
- if (hasMore) {
358
- const detail = document.createElement('div');
359
- detail.style.cssText = 'display:none;grid-column:1/-1;padding:8px 6px 4px;font-size:12px;color:var(--text-2);white-space:pre-wrap;word-break:break-word;max-height:300px;overflow-y:auto;border-top:1px solid var(--border);margin-top:4px;font-family:monospace;';
360
- detail.textContent = fullText;
361
- const wrap = document.createElement('div');
362
- wrap.style.cssText = 'display:grid;grid-template-columns:1fr;border-radius:6px;overflow:hidden;border-bottom:1px solid var(--border);';
363
- row.style.borderBottom = 'none';
364
- let open = false;
365
- row.onclick = () => {
366
- open = !open;
367
- detail.style.display = open ? 'block' : 'none';
368
- const hint = right.querySelector('span:last-child');
369
- if (hint) hint.textContent = open ? '▾' : '▸';
370
- };
371
- wrap.appendChild(row);
372
- wrap.appendChild(detail);
373
- return wrap;
374
- }
375
- return row;
376
- }
377
-
378
- export async function loadRTMessages() {
379
- if (_rtPaused) return;
380
- const box = document.getElementById('rtMessages');
381
- const rtView = document.getElementById('rtView');
382
- if (!box || !rtView) return;
383
-
384
- // Check if this is first load - only if box has no child elements at all
385
- const firstLoad = box.children.length === 0;
386
- if (firstLoad) {
387
- const loadingDiv = document.createElement('div');
388
- loadingDiv.style.cssText = 'padding:20px;';
389
- loadingDiv.textContent = 'Loading…';
390
- box.replaceChildren(loadingDiv);
391
- }
392
-
393
- const data = await getJSON('/api/rt-messages');
394
- const filtered = data.filter(_rtMatchesFilter);
395
-
396
- // PERFORMANCE FIX: Limit to last 100 messages to prevent DOM bloat
397
- const limited = filtered.slice(-100);
398
-
399
- // Use stable hash based on message content, not timestamp
400
- const newHash = limited.map(m => {
401
- const payload = m.payload || {};
402
- const text = payload.reply || payload.prompt || payload.message || payload.content || '';
403
- return `${m.type}|${m.from}|${m.to}|${text.slice(0, 100)}`;
404
- }).join('::');
405
-
406
- if (newHash === window._rtLastHash && !firstLoad) {
407
- return; // No changes, skip redraw
408
- }
409
- window._rtLastHash = newHash;
410
-
411
- const rtAtBottom = () => rtView.scrollHeight - rtView.scrollTop - rtView.clientHeight < 100;
412
- const wasAtBottom = rtAtBottom();
413
- const scrollPos = rtView.scrollTop; // Save scroll position
414
-
415
- // CRITICAL FIX: Use DocumentFragment + replaceChildren to avoid flash
416
- const fragment = document.createDocumentFragment();
417
-
418
- if (!limited.length) {
419
- const emptyDiv = document.createElement('div');
420
- emptyDiv.style.cssText = 'padding:24px;text-align:center;font-size:12px;color:var(--text-3);';
421
- emptyDiv.textContent = 'No events match the current filter.';
422
- fragment.appendChild(emptyDiv);
423
- } else {
424
- const header = document.createElement('div');
425
- header.style.cssText = 'display:grid;grid-template-columns:auto auto 1fr auto;gap:10px;padding:4px 10px 6px;font-size:10px;font-weight:600;color:var(--text-3);letter-spacing:.06em;text-transform:uppercase;border-bottom:2px solid var(--border);margin-bottom:2px;';
426
- ['Agent', 'Phase', 'Summary', 'Time'].forEach(label => {
427
- const th = document.createElement('span'); th.textContent = label; header.appendChild(th);
428
- });
429
- fragment.appendChild(header);
430
- limited.forEach(m => fragment.appendChild(_rtBuildElement(m)));
431
- }
432
-
433
- // CRITICAL: Use replaceChildren instead of innerHTML = '' to prevent flash
434
- box.replaceChildren(fragment);
435
-
436
- // Restore scroll position: if user was at bottom, stay at bottom; otherwise preserve their scroll position
437
- if (wasAtBottom) {
438
- rtView.scrollTop = rtView.scrollHeight;
439
- } else {
440
- // If saved position is now beyond the new content height, scroll to the bottom of new content minus viewport
441
- const maxScroll = Math.max(0, rtView.scrollHeight - rtView.clientHeight);
442
- rtView.scrollTop = Math.min(scrollPos, maxScroll);
443
- }
444
- const scrollBtn = document.getElementById('rtScrollBtn');
445
- if (scrollBtn) scrollBtn.style.display = rtAtBottom() ? 'none' : 'block';
446
-
447
- if (!rtView._scrollListenerBound) {
448
- rtView._scrollListenerBound = true;
449
- rtView.addEventListener('scroll', () => {
450
- if (scrollBtn) scrollBtn.style.display = rtAtBottom() ? 'none' : 'block';
451
- });
452
- }
453
- }
454
-
455
- export function toggleRTPause() {
456
- _rtPaused = !_rtPaused;
457
- const btn = document.getElementById('rtPauseBtn');
458
- if (btn) { btn.textContent = _rtPaused ? '▶ Resume' : '⏸ Pause'; btn.style.background = _rtPaused ? 'var(--accent)' : ''; btn.style.color = _rtPaused ? '#fff' : ''; }
459
- }
460
-
461
- export function clearRTMessages() {
462
- _rtSeenIds = new Set();
463
- const box = document.getElementById('rtMessages');
464
- if (box) box.innerHTML = '<div class="meta" style="padding:20px;text-align:center;opacity:.6;">Cleared. New messages will appear on next poll.</div>';
465
- }
466
-
467
- function _initRTFilters() {
468
- document.querySelectorAll('.rt-filter-chip').forEach(btn => {
469
- btn.addEventListener('click', () => {
470
- _rtFilter = btn.dataset.filter;
471
- _rtSeenIds = new Set();
472
- document.querySelectorAll('.rt-filter-chip').forEach(b => {
473
- const active = b === btn;
474
- b.style.background = active ? 'var(--accent)' : 'transparent';
475
- b.style.color = active ? '#fff' : 'var(--text-2)';
476
- b.classList.toggle('active', active);
477
- });
478
- loadRTMessages();
479
- });
480
- });
481
- const search = document.getElementById('rtSearch');
482
- if (search) {
483
- search.addEventListener('input', () => {
484
- _rtSearch = search.value.trim();
485
- _rtSeenIds = new Set();
486
- loadRTMessages();
487
- });
488
- }
489
- }
490
-
491
- export function showRT() {
492
- _hideAllViews();
493
- document.getElementById('rtView').classList.add('active');
494
- _setNavActive('navRT');
495
- _initRTFilters();
496
- loadRTMessages();
497
- const scrollBtn = document.getElementById('rtScrollBtn');
498
- if (scrollBtn) scrollBtn.style.display = 'none';
499
- }
500
-
501
- // ── DLQ ───────────────────────────────────────────────────────────────────────
502
-
503
- export async function loadDLQ() {
504
- const box = document.getElementById('dlqMessages');
505
- if (box) box.innerHTML = '<div style="padding:20px;">Loading…</div>';
506
- const data = await getJSON('/api/dlq');
507
- const dlqBadgeEl = document.getElementById('dlqBadge');
508
- if (dlqBadgeEl) { dlqBadgeEl.textContent = data.length; dlqBadgeEl.classList.toggle('hidden', !data.length); }
509
- if (!box) return;
510
- box.innerHTML = data.length ? data.map(entry => {
511
- const key = entry.key || (entry.filename || '').replace('.json', '') || '?';
512
- const keyAttr = escHtml(key);
513
- return '<div class="msg dlq-item"><div class="meta"><strong>⚠️ Failed</strong> | ' + (entry.agent || '?') + ' | ' + (entry.failedAt ? new Date(entry.failedAt).toLocaleString() : '') + ' <button class="replay-btn" data-action="replayDLQ" data-arg="' + keyAttr + '">Replay</button> <button data-action="deleteDLQ" data-arg="' + keyAttr + '" style="font-size:11px;padding:3px 8px;border-radius:4px;border:1px solid var(--red-hi);background:transparent;color:var(--red-hi);cursor:pointer;">Delete</button></div><div class="t">' + (entry.error || '') + '</div></div>';
514
- }).join('') : '<div class="meta" style="padding:20px; text-align:center;">✓ DLQ empty</div>';
515
- }
516
-
517
- export async function replayDLQ(key) {
518
- if (!confirm('Replay?')) return;
519
- await postJSON('/api/dlq/replay', { key });
520
- showNotification('Replayed');
521
- loadDLQ();
522
- }
523
-
524
- export async function deleteDLQ(key) {
525
- if (!confirm('Delete this DLQ entry?')) return;
526
- try {
527
- await fetch('/api/dlq/' + encodeURIComponent(key), { method: 'DELETE' });
528
- showNotification('DLQ entry deleted');
529
- loadDLQ();
530
- } catch(e) { showNotification('Failed: ' + e.message, true); }
531
- }
532
-
533
- export function showDLQ() {
534
- _hideAllViews();
535
- document.getElementById('dlqView').classList.add('active');
536
- _setNavActive('navDLQ');
537
- loadDLQ();
538
- }