gabriel / musehub public
agents-coord.ts typescript
119 lines 4.1 KB
Raw
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor ⚠ breaking 1 day ago
1 /**
2 * agents-coord.ts — SSE event stream for the Live Coordination page.
3 *
4 * Reads owner, repoSlug, and cursor from the #page-data JSON block and
5 * connects to the /coord/watch SSE endpoint, appending rows to the stream panel.
6 *
7 * Registered as: window.MusePages['agents-coord']
8 */
9
10 interface AgentsCoordData {
11 owner: string;
12 repoSlug: string;
13 cursor: number;
14 }
15
16 const MAX_ROWS = 60;
17
18 const KIND_DOT_CLASS: Record<string, string> = {
19 reservation: 'agents-stream-dot--reservation',
20 heartbeat: 'agents-stream-dot--heartbeat',
21 claim: 'agents-stream-dot--claim',
22 release: 'agents-stream-dot--release',
23 task: 'agents-stream-dot--task',
24 intent: 'agents-stream-dot--intent',
25 };
26
27 function setStatus(state: 'live' | 'error' | 'connecting'): void {
28 const dot = document.getElementById('sse-dot');
29 const label = document.getElementById('sse-label');
30 if (!dot || !label) return;
31 if (state === 'live') {
32 dot.style.background = 'var(--color-success, #3fb950)';
33 dot.style.boxShadow = '0 0 6px var(--color-success, #3fb950)';
34 label.textContent = 'LIVE';
35 } else if (state === 'error') {
36 dot.style.background = 'var(--color-danger, #f85149)';
37 dot.style.boxShadow = 'none';
38 label.textContent = 'RECONNECTING';
39 } else {
40 dot.style.background = 'var(--color-warning, #e3b341)';
41 dot.style.boxShadow = 'none';
42 label.textContent = 'CONNECTING';
43 }
44 }
45
46 function appendRow(body: Element, kind: string, detail: string): void {
47 const row = document.createElement('div');
48 row.className = 'agents-stream-row';
49
50 const dotEl = document.createElement('span');
51 dotEl.className = 'agents-stream-dot ' + (KIND_DOT_CLASS[kind] ?? 'agents-stream-dot--default');
52
53 const kindEl = document.createElement('span');
54 kindEl.className = 'agents-stream-kind';
55 kindEl.textContent = kind;
56
57 const detailEl = document.createElement('span');
58 detailEl.className = 'agents-stream-detail';
59 detailEl.textContent = detail;
60
61 row.appendChild(dotEl);
62 row.appendChild(kindEl);
63 row.appendChild(detailEl);
64 body.appendChild(row);
65
66 while (body.children.length > MAX_ROWS) {
67 body.removeChild(body.firstChild!);
68 }
69 (body as HTMLElement).scrollTop = (body as HTMLElement).scrollHeight;
70 }
71
72 function connect(owner: string, repoSlug: string, initialCursor: number): void {
73 const body = document.getElementById('stream-body');
74 const cursorEl = document.getElementById('stream-cursor');
75 if (!body) return;
76
77 let cursor = initialCursor;
78 let watchUrl = `/${owner}/${repoSlug}/coord/watch?since_id=${cursor}`;
79
80 setStatus('connecting');
81
82 const es = new EventSource(watchUrl);
83
84 es.addEventListener('coord_record', (e: MessageEvent) => {
85 setStatus('live');
86 try {
87 const rec = JSON.parse(e.data as string) as Record<string, unknown>;
88 cursor = (rec['id'] as number) || cursor;
89 if (cursorEl) cursorEl.textContent = `cursor: ${cursor}`;
90 watchUrl = `/${owner}/${repoSlug}/coord/watch?since_id=${cursor}`;
91
92 const kind = (rec['kind'] as string) || 'unknown';
93 const runId = ((rec['run_id'] as string) || '').substring(0, 22);
94 const payload = (rec['payload'] as Record<string, unknown>) || {};
95 let addr = '';
96 const addrs = payload['addresses'] as string[] | undefined;
97 if (addrs && addrs.length) addr = addrs[0];
98 else if (payload['symbol_address']) addr = payload['symbol_address'] as string;
99 else if (payload['task_id']) addr = 'task:' + ((payload['task_id'] as string).substring(0, 8));
100 const detail = runId + (addr ? ' · ' + addr.substring(0, 40) : '');
101 appendRow(body, kind, detail);
102 } catch { /* ignore parse errors */ }
103 });
104
105 es.onerror = () => {
106 setStatus('error');
107 es.close();
108 setTimeout(() => connect(owner, repoSlug, cursor), 3000);
109 };
110 }
111
112 export function initAgentsCoord(data: Record<string, unknown>): void {
113 const cfg = data as unknown as AgentsCoordData;
114 const owner = String(cfg.owner ?? '');
115 const repoSlug = String(cfg.repoSlug ?? '');
116 const cursor = Number(cfg.cursor ?? 0);
117 if (!owner || !repoSlug) return;
118 connect(owner, repoSlug, cursor);
119 }
File History 1 commit
sha256:3ff9c9863a9891bdcde71b4a43228f66d0493e38b7cc1d09fe9eb7de774046b2 feat: add repair-commit wire endpoint (API parity with repa… Opus 4.8 minor 1 day ago