crewswarm 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
  3. package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
  4. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  5. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  6. package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
  7. package/apps/dashboard/dist/index.html +82 -11
  8. package/apps/vibe/README.md +2 -2
  9. package/apps/vibe/package.json +1 -1
  10. package/apps/vibe/server.mjs +3 -3
  11. package/crew-lead.mjs +34 -4
  12. package/lib/bridges/gateway-ws.mjs +4 -0
  13. package/lib/crew-lead/chat-handler.mjs +34 -0
  14. package/lib/crew-lead/http-server.mjs +55 -14
  15. package/lib/crew-lead/llm-caller.mjs +24 -8
  16. package/lib/crew-lead/prompts.mjs +7 -0
  17. package/lib/crew-lead/wave-dispatcher.mjs +15 -3
  18. package/lib/crew-lead/ws-router.mjs +219 -27
  19. package/lib/engines/engine-registry.mjs +9 -0
  20. package/lib/engines/rt-envelope.mjs +1 -0
  21. package/lib/engines/runners.mjs +5 -2
  22. package/lib/runtime/paths.mjs +12 -8
  23. package/package.json +35 -15
  24. package/scripts/capture-build-flow.mjs +118 -0
  25. package/scripts/coverage-report.mjs +209 -0
  26. package/scripts/coverage-summary.mjs +47 -0
  27. package/scripts/dashboard-validation.mjs +74 -0
  28. package/scripts/dashboard.mjs +560 -70
  29. package/scripts/live-bridge-matrix.mjs +79 -0
  30. package/scripts/live-cli-matrix.mjs +166 -0
  31. package/scripts/live-crewchat-check.mjs +42 -0
  32. package/scripts/live-engine-matrix.mjs +50 -0
  33. package/scripts/live-provider-failover-matrix.mjs +107 -0
  34. package/scripts/live-provider-matrix.mjs +228 -0
  35. package/scripts/restart-all-from-repo.sh +4 -4
  36. package/scripts/smoke-dispatch.mjs +4 -1
  37. package/scripts/test-blast-radius.mjs +204 -0
  38. package/scripts/test-report-summary.mjs +88 -0
  39. package/scripts/test-reporter.mjs +651 -0
  40. package/scripts/test-rerun.mjs +136 -0
  41. package/scripts/tmux-bridge +130 -0
  42. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  43. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  44. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  45. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  46. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  47. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  48. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  49. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  50. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  52. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  54. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  56. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  57. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  58. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  59. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  60. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  61. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  62. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  63. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  64. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  65. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  66. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  67. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  68. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  69. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  70. package/apps/dashboard/dist/index.html.br +0 -0
  71. package/apps/dashboard/dist/index.html.gz +0 -0
  72. package/apps/dashboard/index.html +0 -6529
  73. package/apps/dashboard/package.json +0 -15
  74. package/apps/dashboard/src/app.js +0 -2828
  75. package/apps/dashboard/src/app.js.br +0 -0
  76. package/apps/dashboard/src/app.js.gz +0 -0
  77. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  78. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  79. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  80. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  81. package/apps/dashboard/src/cli-process.js +0 -208
  82. package/apps/dashboard/src/cli-process.js.br +0 -0
  83. package/apps/dashboard/src/cli-process.js.gz +0 -0
  84. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  85. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  86. package/apps/dashboard/src/core/api.js +0 -18
  87. package/apps/dashboard/src/core/api.js.br +0 -0
  88. package/apps/dashboard/src/core/dom.js +0 -228
  89. package/apps/dashboard/src/core/dom.js.br +0 -0
  90. package/apps/dashboard/src/core/state.js +0 -91
  91. package/apps/dashboard/src/core/state.js.br +0 -0
  92. package/apps/dashboard/src/core/task-manager.js +0 -134
  93. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  94. package/apps/dashboard/src/orchestration-status.js +0 -127
  95. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  96. package/apps/dashboard/src/setup-wizard.js +0 -562
  97. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  98. package/apps/dashboard/src/styles.css +0 -2085
  99. package/apps/dashboard/src/styles.css.br +0 -0
  100. package/apps/dashboard/src/styles.css.gz +0 -0
  101. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  102. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  104. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  106. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  108. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  110. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  111. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  112. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  113. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  114. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  115. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  116. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  117. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  118. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  119. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  120. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  121. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  123. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  125. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  127. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  129. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  131. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  133. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  135. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  137. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  139. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  140. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  141. package/apps/vibe/.crew/cost.json +0 -17
  142. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  143. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  158. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  159. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  160. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  161. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  162. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  163. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  164. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  172. package/apps/vibe/.crew/sandbox.json +0 -7
  173. package/apps/vibe/.crew/session.json +0 -330
  174. package/apps/vibe/.crew/training-data.jsonl +0 -0
  175. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  176. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  177. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  178. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  179. package/apps/vibe/ARCHITECTURE.md +0 -3393
  180. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  181. package/apps/vibe/ROADMAP.md +0 -41
  182. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  183. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  184. package/apps/vibe/capture-demo.mjs +0 -160
  185. package/apps/vibe/capture-full-demo.mjs +0 -255
  186. package/apps/vibe/capture-quickstart.mjs +0 -256
  187. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  188. package/apps/vibe/capture-vibe-video.mjs +0 -260
  189. package/apps/vibe/check-buttons.js +0 -41
  190. package/apps/vibe/diagnose.html +0 -106
  191. package/apps/vibe/fix-buttons.js +0 -103
  192. package/apps/vibe/index.html +0 -3404
  193. package/apps/vibe/package-lock.json +0 -920
  194. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  195. package/apps/vibe/src/main.js +0 -2940
  196. package/apps/vibe/src/register-all-languages.js +0 -98
  197. package/apps/vibe/start-studio.sh +0 -11
  198. package/apps/vibe/test/accessibility-tests.js +0 -77
  199. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  200. package/apps/vibe/test/performance-tests.js +0 -120
  201. package/apps/vibe/test/security-tests.js +0 -213
  202. package/apps/vibe/tests/e2e.local.mjs +0 -54
  203. package/apps/vibe/tests/server.smoke.mjs +0 -106
  204. package/apps/vibe/update_website.mjs +0 -74
  205. package/apps/vibe/vite.config.js +0 -19
  206. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  207. 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
- }