(function () { const buffers = {}; let liveEs = null; function esc(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; } function nowIso() { return new Date().toISOString(); } function lineClass(type) { if (!type) return 'line-action'; if (type === 'command') return 'line-command'; if (type === 'output') return 'line-output'; if (String(type).indexOf('handoff') >= 0) return 'line-handoff'; if (type === 'error') return 'line-error'; if (String(type).indexOf('monitor') >= 0) return 'line-handoff'; if (String(type).indexOf('browse') >= 0) return 'line-action'; return 'line-action'; } function formatLine(entry) { const t = (entry.at || '').substring(11, 19) || (entry.at || '').substring(11, 16) || '--:--'; const msg = entry.message || entry.title || ''; const detail = entry.detail ? ' — ' + String(entry.detail).slice(0, 200) : ''; return ( '
' + esc(t) + '' + esc(msg + detail) + '
' ); } function render(key) { const els = document.querySelectorAll('[data-term-key="' + key + '"]'); if (!els.length) return; const lines = buffers[key] || []; const html = !lines.length ? '
Typ help voor commando\'s · Enter om uit te voeren
' : lines.slice(-80).map(formatLine).join(''); els.forEach(function (el) { el.innerHTML = html; el.scrollTop = el.scrollHeight; const win = el.closest('.agent-term-window'); if (win && lines.length) win.classList.add('is-live'); }); } function pushLine(key, entry, skipDup) { if (!key || !entry) return; key = String(key).toLowerCase(); if (!buffers[key]) buffers[key] = []; if (skipDup && entry._id) { const seen = buffers[key].some(function (l) { return l._id === entry._id; }); if (seen) return; } if (!entry.at) entry.at = nowIso(); buffers[key].push(entry); if (buffers[key].length > 120) buffers[key].shift(); render(key); } function setBusy(key, busy) { document.querySelectorAll('[data-term-input="' + key + '"]').forEach(function (input) { input.disabled = !!busy; input.classList.toggle('is-busy', !!busy); }); document.querySelectorAll('[data-term-key="' + key + '"]').forEach(function (body) { const win = body.closest('.agent-term-window'); if (win) win.classList.toggle('is-running', !!busy); }); } async function runCommand(key, cmd) { key = String(key || '').toLowerCase(); cmd = (cmd || '').trim(); if (!cmd) return; if (cmd.toLowerCase() === 'clear') { buffers[key] = []; render(key); return; } pushLine(key, { type: 'command', message: '$ ' + cmd, at: nowIso() }); setBusy(key, true); try { const r = await fetch('/api/agents/souls/' + encodeURIComponent(key) + '/terminal/command', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command: cmd }), }); const data = await r.json().catch(function () { return {}; }); if (!r.ok) { const err = (typeof data.detail === 'string' && data.detail) || (Array.isArray(data.detail) && data.detail[0]?.msg) || 'Commando mislukt'; pushLine(key, { type: 'error', message: err, at: nowIso() }); return; } (data.lines || []).forEach(function (line) { pushLine(key, { type: line.type || 'output', message: line.message || '', detail: line.detail || '', at: nowIso(), }); }); } catch (e) { pushLine(key, { type: 'error', message: e.message || 'Netwerkfout', at: nowIso() }); } finally { setBusy(key, false); } } function bindInputs() { document.querySelectorAll('[data-term-input]').forEach(function (input) { if (input._termBound) return; input._termBound = true; const key = input.getAttribute('data-term-input'); input.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); const val = input.value; input.value = ''; runCommand(key, val); } }); input.addEventListener('click', function (e) { e.stopPropagation(); }); }); } async function loadHistory(key) { try { const r = await fetch('/api/agents/souls/' + encodeURIComponent(key) + '/events?limit=30'); if (!r.ok) return; const data = await r.json(); buffers[key] = (data.items || []).map(function (ev) { const meta = ev.metadata || {}; let type = 'action'; const et = ev.event_type || ''; if (et.indexOf('handoff') >= 0) type = 'handoff_out'; if (meta.target_agent) type = 'handoff_out'; if (meta.source_agent) type = 'handoff_in'; if (et.indexOf('monitor') >= 0) type = 'monitor'; if (et.indexOf('browse') >= 0) type = 'browse'; if (et === 'terminal_in') type = 'command'; if (et === 'terminal_out' || et === 'terminal_note') type = 'output'; return { _id: ev.id, type: type, message: ev.title || ev.event_type, detail: ev.body, at: ev.created_at, }; }); render(key); } catch (e) {} } function handleLivePayload(d) { if (!d || d.type === 'connected' || d.type === 'error') return; const key = (d.agent || '').toLowerCase(); if (!key) return; pushLine( key, { _id: d.id, type: d.type, message: d.message, detail: d.detail, at: d.at, }, true ); document.querySelectorAll('[data-term-key="' + key + '"]').forEach(function (el) { const win = el.closest('.agent-term-window'); if (win) win.classList.add('is-live'); }); } function startLiveStream() { if (liveEs || typeof EventSource === 'undefined') return; liveEs = new EventSource('/api/agents/live/stream'); liveEs.onmessage = function (ev) { try { handleLivePayload(JSON.parse(ev.data)); } catch (e) {} }; liveEs.onerror = function () { /* EventSource auto-reconnects */ }; } function stopLiveStream() { if (liveEs) { try { liveEs.close(); } catch (e) {} liveEs = null; } } async function mountAll() { const nodes = document.querySelectorAll('[data-term-key]'); const keys = []; nodes.forEach(function (n) { const k = n.getAttribute('data-term-key'); if (k && keys.indexOf(k) < 0) keys.push(k); }); await Promise.all(keys.map(loadHistory)); bindInputs(); startLiveStream(); } function stopAll() { stopLiveStream(); } window.AgentsTerminals = { mountAll, stopAll, pushLine, render, runCommand, bindInputs, startLiveStream, stopLiveStream, }; })();