crewswarm 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +34 -4
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +55 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +15 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +5 -2
- package/lib/runtime/paths.mjs +12 -8
- package/package.json +35 -15
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +74 -0
- package/scripts/dashboard.mjs +560 -70
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
|
@@ -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
|
-
}
|
|
Binary file
|