Conduit Silver

Plan · TM3 Max 1,090+ reasoning capabilities · Seven Trees · UMA
Codegraph: Unknown
Connecting...
Disconnected. Requests will be queued.

One-Click Commands

You've saved ~0 tokens
Agent's Computer
🖥
No active CUA session
Start a task or open a browser session
to see the agent's screen here.
0 / 0 live
Operator Takeover
Describe what you changed so the agent can continue with context:
Explorer
No results
Open a file from the explorer or press Ctrl+P
Conduit Silver IDE — Full Compression Stack
$
No problems have been detected in the workspace.
Loading Sentrux quality…
Agent Chat Keys: /configure-llm — not in chat text
⑃ main ↑0 ↓0 0 errors
Ln 1, Col 1 Spaces: 2 UTF-8 LF Plain Text 🔔 Circadian
Health
Agents:
Alerts: 0
Orchestrators:
Agent Fleet
Live Activity

Demo Center

No active session

Autonomous demos via demo.* — spawns dev servers from demos/catalog.yaml (set CONDUIT_DEMO_WORKSPACE_ROOT to your Circadian_Unified_Platform path if needed).

Event log

          

LLM Observatory

Same-origin GET /api/v10/llm/unified-dashboard on (Conduit Silver proxies to Circadian when needed). Chat archive / other APIs can target a remote Circadian host via ?archive=https://…. With ?archive=, set localStorage.llm_observatory_admin_key for X-Admin-Key (credentials include).

LLM dash: Unknown
Not loaded yet.
Full dashboard JSON

          

Trader cockpit

Uses the same API base as archive (). GET /api/v9/trader/pm/family-office-dashboard requires Authorization: Bearer … or X-Api-Key when CIRCADIAN_SILVER_TRADER_REQUIRE_AUTH=1 or in production. Token is stored in localStorage as silver_trader_bearer.

Not loaded yet.

          
SSE: /api/v9/trader/pm/dashboard-stream — events appended below when enabled.

        

Cursor Imports

1:1 archive of ~/.cursor/projects/<project>/agent-transcripts/*.jsonl and <workspace>/.cursor/plans/*.plan.md stored in circadian.cursor_imported_chats and circadian.cursor_imported_plans with 5-layer compression (LZ4 TOAST + symbolic mirror). Auto-sync every 60s by default — disable with CURSOR_IMPORTS_AUTOSYNC=0.

Conversations
messages
Plans
todos
In-progress
todos across plans
Compression savings
raw → symbolic

        
' + html + ''; if (frame) frame.srcdoc = doc; if (wrap) wrap.style.display = ''; if (btnClose) btnClose.style.display = ''; } function mcCloseEducationalSandbox() { const wrap = document.getElementById('mcSiteBundleSandboxWrap'); const frame = document.getElementById('mcSiteBundleSandboxFrame'); const btnClose = document.getElementById('mcSiteBundleCloseSandbox'); if (frame) frame.srcdoc = ''; if (wrap) wrap.style.display = 'none'; if (btnClose) btnClose.style.display = 'none'; } function getCircadianJwt() { try { const qp = new URLSearchParams(window.location.search).get('circadian_jwt'); if (qp && qp.trim()) return qp.trim(); } catch (_) {} return (localStorage.getItem('circadian_jwt') || '').trim(); } function mcCircadianAuthHeaders(jsonBody) { const t = getCircadianJwt(); const h = {}; if (jsonBody) h['Content-Type'] = 'application/json'; if (t) h['Authorization'] = 'Bearer ' + t; return h; } function mcStopRunEventStream() { if (mcRunsEventAbort) { try { mcRunsEventAbort.abort(); } catch (_) {} mcRunsEventAbort = null; } } function mcRevokeBrowserLiveBlob() { if (mcBrowserLiveBlobUrl) { try { URL.revokeObjectURL(mcBrowserLiveBlobUrl); } catch (_) {} mcBrowserLiveBlobUrl = null; } } function mcStopBrowserLivePoll() { if (mcBrowserLiveTimer) { clearInterval(mcBrowserLiveTimer); mcBrowserLiveTimer = null; } mcRevokeBrowserLiveBlob(); const img = document.getElementById('mcBrowserLiveImg'); if (img) img.removeAttribute('src'); } function mcScheduleBrowserLivePoll() { mcStopBrowserLivePoll(); const auto = document.getElementById('mcBrowserLiveAuto'); if (!auto || !auto.checked || !mcSelectedBrowserSessionId) return; mcBrowserLiveTimer = setInterval(() => { mcRefreshBrowserLivePng(); }, 3000); mcRefreshBrowserLivePng(); } async function mcRefreshBrowserLivePng() { const sid = mcSelectedBrowserSessionId; const img = document.getElementById('mcBrowserLiveImg'); if (!sid || !img) return; const liveUrlInput = document.getElementById('mcBrowserLiveUrlInput'); const pageUrl = (liveUrlInput && liveUrlInput.value.trim()) || ''; let url = CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/live.png'; if (pageUrl) url += '?url=' + encodeURIComponent(pageUrl); const token = getCircadianJwt(); const headers = {}; if (token) headers['Authorization'] = 'Bearer ' + token; try { const resp = await fetch(url, { headers }); if (!resp.ok) { mcRevokeBrowserLiveBlob(); img.removeAttribute('src'); return; } const blob = await resp.blob(); mcRevokeBrowserLiveBlob(); mcBrowserLiveBlobUrl = URL.createObjectURL(blob); img.src = mcBrowserLiveBlobUrl; } catch (_) { mcRevokeBrowserLiveBlob(); img.removeAttribute('src'); } } async function mcFetchBrowserSessions() { const msg = document.getElementById('mcBrowserMsg'); const tbody = document.getElementById('mcBrowserTableBody'); const countEl = document.getElementById('mcBrowserCount'); const token = getCircadianJwt(); if (!token) { if (msg) { msg.style.display = ''; msg.textContent = 'Set a Circadian JWT to manage cloud browser sessions.'; } if (tbody) tbody.innerHTML = ''; if (countEl) countEl.textContent = ''; mcState.data.browserSessions = []; return; } if (msg) msg.style.display = 'none'; try { const resp = await fetch(CP_BROWSER_API, { headers: { Authorization: 'Bearer ' + token } }); const raw = await resp.text(); let data = {}; try { data = JSON.parse(raw); } catch (_) {} if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = 'browser sessions: ' + (data.error || raw || resp.status); } return; } const sessions = data.sessions || []; mcState.data.browserSessions = sessions; if (countEl) countEl.textContent = '(' + sessions.length + ')'; if (!tbody) return; tbody.innerHTML = sessions.map(s => { const id = String(s.session_id || ''); const mode = (s.input_mode || '—').toLowerCase(); const exp = s.expires_at ? new Date(s.expires_at).toLocaleString() : '—'; const lu = (s.last_live_url || '').slice(0, 48) + ((s.last_live_url || '').length > 48 ? '…' : ''); const sel = id === mcSelectedBrowserSessionId ? 'background:#1e293b' : ''; return '' + '' + esc(id.slice(0, 8)) + '…' + '' + esc(mode) + '' + '' + esc(exp) + '' + '' + esc(lu || '—') + ''; }).join(''); if (mcSelectedBrowserSessionId && !sessions.some(s => String(s.session_id) === mcSelectedBrowserSessionId)) { mcSelectedBrowserSessionId = null; mcStopBrowserLivePoll(); const hint = document.getElementById('mcBrowserDetailHint'); if (hint) { hint.style.display = ''; hint.textContent = 'Select a session for live preview and handoff.'; } const pre = document.getElementById('mcBrowserAuditPre'); if (pre) { pre.style.display = 'none'; pre.textContent = ''; } } } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = 'browser sessions error: ' + e.message; } } } async function mcSelectBrowserSession(sessionId) { mcSelectedBrowserSessionId = sessionId || null; const token = getCircadianJwt(); const hint = document.getElementById('mcBrowserDetailHint'); const pre = document.getElementById('mcBrowserAuditPre'); const urlInp = document.getElementById('mcBrowserLiveUrlInput'); if (!sessionId || !token) { if (hint) { hint.style.display = ''; hint.textContent = 'Select a session for live preview and handoff.'; } if (urlInp) urlInp.value = ''; if (pre) { pre.style.display = 'none'; pre.textContent = ''; } mcStopBrowserLivePoll(); return; } if (hint) hint.style.display = 'none'; try { const resp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sessionId), { headers: { Authorization: 'Bearer ' + token } }); const data = await resp.json(); if (!resp.ok) { if (hint) { hint.style.display = ''; hint.textContent = data.error || 'Failed to load session'; } return; } if (urlInp) urlInp.value = (data.last_live_url || '').trim(); const aresp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sessionId) + '/audit', { headers: { Authorization: 'Bearer ' + token } }); const ad = await aresp.json(); if (pre && aresp.ok && ad.audit) { pre.style.display = ''; pre.textContent = (ad.audit || []).join('\n'); } else if (pre) { pre.style.display = 'none'; } } catch (e) { if (hint) { hint.style.display = ''; hint.textContent = e.message; } } await mcFetchBrowserSessions(); mcScheduleBrowserLivePoll(); } async function mcStartBrowserSession() { const token = getCircadianJwt(); const msg = document.getElementById('mcBrowserMsg'); if (!token) { if (msg) { msg.style.display = ''; msg.textContent = 'JWT required to create a session.'; } return; } try { const resp = await fetch(CP_BROWSER_API, { method: 'POST', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ ttl_sec: 900 }), }); const data = await resp.json(); if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = data.error || 'create failed'; } return; } const sid = String(data.session_id || ''); if (sid) await mcSelectBrowserSession(sid); } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = e.message; } } } async function mcMintBrowserViewerToken() { const sid = mcSelectedBrowserSessionId; const token = getCircadianJwt(); const msg = document.getElementById('mcBrowserMsg'); if (!sid || !token) return; try { const resp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/viewer-token', { method: 'POST', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ ttl_sec: 600 }), }); const data = await resp.json(); if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = data.error || 'viewer-token failed'; } return; } const vt = data.viewer_token || ''; if (navigator.clipboard && vt) { await navigator.clipboard.writeText(vt); if (msg) { msg.style.display = ''; msg.textContent = 'Viewer JWT copied (' + (data.expires_in_sec || '') + 's TTL). Use Authorization: Bearer for live.png only.'; } } else if (msg) { msg.style.display = ''; msg.textContent = 'viewer_token: ' + vt; } } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = e.message; } } } async function mcPatchBrowserInputMode(mode) { const sid = mcSelectedBrowserSessionId; const token = getCircadianJwt(); const msg = document.getElementById('mcBrowserMsg'); if (!sid || !token) return; try { const resp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/input-mode', { method: 'PATCH', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ input_mode: mode }), }); const data = await resp.json(); if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = data.error || 'input-mode failed'; } return; } await mcFetchBrowserSessions(); const aresp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/audit', { headers: { Authorization: 'Bearer ' + token } }); const ad = await aresp.json(); const pre = document.getElementById('mcBrowserAuditPre'); if (pre && aresp.ok && ad.audit) { pre.style.display = ''; pre.textContent = (ad.audit || []).join('\n'); } } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = e.message; } } } async function mcPatchBrowserLiveUrl() { const sid = mcSelectedBrowserSessionId; const token = getCircadianJwt(); const msg = document.getElementById('mcBrowserMsg'); const urlInp = document.getElementById('mcBrowserLiveUrlInput'); if (!sid || !token || !urlInp) return; const url = urlInp.value.trim(); if (!url) { if (msg) { msg.style.display = ''; msg.textContent = 'Enter a URL first.'; } return; } try { const resp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/live-url', { method: 'PATCH', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ url }), }); const data = await resp.json(); if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = data.error || 'live-url failed'; } return; } await mcFetchBrowserSessions(); mcRefreshBrowserLivePng(); } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = e.message; } } } async function mcStartRunEventStream(taskId) { mcStopRunEventStream(); const logEl = document.getElementById('mcRunEventsLog'); if (logEl) logEl.textContent = ''; const token = getCircadianJwt(); if (!token || !taskId) return; mcRunsEventAbort = new AbortController(); const url = CP_COMPUTE_API + '/jobs/' + encodeURIComponent(taskId) + '/events'; try { const resp = await fetch(url, { headers: { Authorization: 'Bearer ' + token }, signal: mcRunsEventAbort.signal }); if (!resp.ok || !resp.body) { if (logEl) logEl.textContent = 'events: HTTP ' + resp.status; return; } const reader = resp.body.getReader(); const dec = new TextDecoder(); let buf = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); let sep; while ((sep = buf.indexOf('\n\n')) >= 0) { const block = buf.slice(0, sep); buf = buf.slice(sep + 2); for (const line of block.split('\n')) { if (line.startsWith('data:')) { const payload = line.slice(5).trim(); if (logEl) { logEl.textContent += payload + '\n'; logEl.scrollTop = logEl.scrollHeight; } } } } } } catch (e) { if (e.name !== 'AbortError' && logEl) logEl.textContent += '\n[stream ended: ' + e.message + ']'; } } async function mcFetchRuns() { const msg = document.getElementById('mcRunsMsg'); const tbody = document.getElementById('mcRunsTableBody'); const countEl = document.getElementById('mcRunsCount'); const token = getCircadianJwt(); if (!token) { if (msg) { msg.style.display = ''; msg.textContent = 'Set a Circadian JWT (or open the portal with ?circadian_jwt=...) to list org-scoped compute runs. Mission Control overview still works without it.'; } if (tbody) tbody.innerHTML = ''; if (countEl) countEl.textContent = ''; return; } if (msg) msg.style.display = 'none'; try { const resp = await fetch(MC_API + '/compute-runs?limit=100', { headers: { Authorization: 'Bearer ' + token } }); const raw = await resp.text(); let data = {}; try { data = JSON.parse(raw); } catch (_) {} if (!resp.ok) { if (msg) { msg.style.display = ''; msg.textContent = 'compute-runs failed: ' + (data.error || raw || resp.status); } return; } const jobs = data.jobs || []; mcState.data.computeRuns = jobs; if (countEl) countEl.textContent = '(' + jobs.length + ')'; if (!tbody) return; tbody.innerHTML = jobs.map(j => { const id = j.task_id || j.taskId || ''; const st = (j.status || '').toLowerCase(); const pool = j.worker_pool || j.workerPool || '—'; const img = (j.image || '').slice(0, 36) + ((j.image || '').length > 36 ? '…' : ''); const cr = j.created_at ? new Date(j.created_at).toLocaleString() : '—'; const canCancel = ['pending', 'queued', 'running', 'claimed', 'in_progress', 'processing'].includes(st); return '' + '' + esc(String(id).slice(0, 8)) + '…' + '' + esc(j.tier || '—') + '' + '' + esc(pool) + '' + '' + esc(j.status || '—') + '' + '' + esc(img || '—') + '' + '' + esc(cr) + '' + '' + (canCancel ? '' : '') + ''; }).join(''); } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = 'compute-runs error: ' + e.message; } } } async function mcSelectRun(taskId) { mcRunsSelectedTaskId = taskId; const token = getCircadianJwt(); const hint = document.getElementById('mcRunDetailHint'); const pre = document.getElementById('mcRunDetailJson'); const tlPre = document.getElementById('mcRunTimelinePre'); const tlBtn = document.getElementById('mcRunTimelineBtn'); const cancelBtn = document.getElementById('mcRunCancelBtn'); if (tlPre) { tlPre.style.display = 'none'; tlPre.textContent = ''; } if (hint) hint.style.display = 'none'; if (!taskId || !token) { if (pre) pre.textContent = ''; if (tlBtn) tlBtn.style.display = 'none'; if (cancelBtn) cancelBtn.style.display = 'none'; mcStopRunEventStream(); return; } if (tlBtn) tlBtn.style.display = ''; if (cancelBtn) cancelBtn.style.display = ''; try { const resp = await fetch(CP_COMPUTE_API + '/jobs/' + encodeURIComponent(taskId), { headers: { Authorization: 'Bearer ' + token } }); const data = await resp.json(); if (pre) pre.textContent = JSON.stringify(data, null, 2); const st = (data.status || '').toLowerCase(); const canCancel = ['pending', 'queued', 'running', 'claimed', 'in_progress', 'processing'].includes(st); if (cancelBtn) cancelBtn.style.display = canCancel ? '' : 'none'; } catch (e) { if (pre) pre.textContent = String(e); } mcStartRunEventStream(taskId); } async function mcCancelComputeRun(taskId) { if (!taskId || !confirm('Cancel this compute run?')) return; const token = getCircadianJwt(); if (!token) return; try { const resp = await fetch(CP_TASKS_API_BASE + '/' + encodeURIComponent(taskId) + '/cancel', { method: 'POST', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ fail_pending_dependents: false }), }); const data = await resp.json().catch(() => ({})); if (resp.ok) { mcAddFeedItem('info', 'Compute', 'Run cancelled', taskId); mcFetchRuns(); if (mcRunsSelectedTaskId === taskId) mcSelectRun(taskId); } else { mcAddFeedItem('warn', 'Compute', 'Cancel failed', data.error || resp.status); } } catch (e) { mcAddFeedItem('warn', 'Compute', 'Cancel error', e.message); } } async function mcLoadRunTimeline() { const taskId = mcRunsSelectedTaskId; const token = getCircadianJwt(); const tlPre = document.getElementById('mcRunTimelinePre'); if (!taskId || !token || !tlPre) return; tlPre.style.display = ''; tlPre.textContent = 'Loading…'; try { const resp = await fetch(CP_TASKS_API_BASE + '/' + encodeURIComponent(taskId) + '/timeline', { headers: { Authorization: 'Bearer ' + token } }); const data = await resp.json(); tlPre.textContent = JSON.stringify(data, null, 2); } catch (e) { tlPre.textContent = String(e); } } async function mcFetchKanban() { const msg = document.getElementById('mcKanbanMsg'); const board = document.getElementById('mcKanbanBoard'); const countEl = document.getElementById('mcKanbanCount'); const token = getCircadianJwt(); if (!token) { if (msg) { msg.style.display = ''; msg.textContent = 'Save a Circadian JWT (Runs tab) to load the kanban.'; } if (board) board.innerHTML = ''; if (countEl) countEl.textContent = ''; return; } if (msg) msg.style.display = 'none'; try { const h = { Authorization: 'Bearer ' + token }; const [stResp, tkResp] = await Promise.all([ fetch(MC_API + '/ticket-statuses', { headers: h }), fetch(MC_API + '/tickets?limit=200', { headers: h }), ]); const stData = stResp.ok ? await stResp.json().catch(() => ({})) : {}; const tkData = tkResp.ok ? await tkResp.json().catch(() => ({})) : {}; if (!stResp.ok || !tkResp.ok) { if (msg) { msg.style.display = ''; msg.textContent = 'kanban: ' + (stData.error || tkData.error || stResp.status + ' / ' + tkResp.status); } return; } const statuses = stData.statuses || []; const tickets = tkData.tickets || []; mcState.data.kanbanStatuses = statuses; mcState.data.kanbanTickets = tickets; if (countEl) countEl.textContent = '(' + tickets.length + ')'; if (!board) return; const byStatus = {}; statuses.forEach(s => { byStatus[s.slug] = []; }); tickets.forEach(t => { const slug = t.status_slug || 'backlog'; if (!byStatus[slug]) byStatus[slug] = []; byStatus[slug].push(t); }); board.innerHTML = statuses.map(s => { const col = byStatus[s.slug] || []; const cards = col.map(t => { const sel = ''; const meta = (t.metadata && typeof t.metadata === 'object') ? JSON.stringify(t.metadata).slice(0, 120) : ''; return '
' + '
' + esc(t.title || '') + '
' + '
' + esc((t.body || '').slice(0, 200)) + '
' + '
' + esc(t.priority || '') + ' · ' + esc(t.source || '') + '
' + (meta ? '
' + esc(meta) + (meta.length >= 120 ? '…' : '') + '
' : '') + sel + '
'; }).join(''); const border = s.color ? 'border-top:3px solid ' + esc(s.color) : ''; return '
' + '
' + esc(s.name) + ' (' + col.length + ')
' + cards + '
'; }).join(''); board.querySelectorAll('.mc-kanban-status').forEach(el => { el.addEventListener('change', async ev => { ev.stopPropagation(); const id = el.getAttribute('data-mc-ticket-id'); const status_slug = el.value; try { const resp = await fetch(MC_API + '/tickets/' + encodeURIComponent(id), { method: 'PATCH', headers: Object.assign({ 'Content-Type': 'application/json' }, mcCircadianAuthHeaders(true)), body: JSON.stringify({ status_slug }), }); const d = await resp.json().catch(() => ({})); if (resp.ok) { mcAddFeedItem('info', 'Kanban', 'Status updated', id); mcFetchKanban(); } else { mcAddFeedItem('warn', 'Kanban', 'PATCH failed', d.error || resp.status); } } catch (e) { mcAddFeedItem('warn', 'Kanban', 'PATCH error', e.message); } }); }); board.querySelectorAll('.mc-kanban-card').forEach(el => { el.addEventListener('click', ev => { if (ev.target.closest('.mc-kanban-status')) return; mcKanbanSelectedId = el.getAttribute('data-mc-ticket-id'); mcFetchKanbanComments(); }); }); } catch (e) { if (msg) { msg.style.display = ''; msg.textContent = 'kanban error: ' + e.message; } } } async function mcFetchKanbanComments() { const el = document.getElementById('mcKanbanComments'); const token = getCircadianJwt(); if (!el || !mcKanbanSelectedId || !token) { if (el && !mcKanbanSelectedId) el.textContent = 'Select a ticket card.'; return; } el.textContent = 'Loading…'; try { const resp = await fetch(MC_API + '/tickets/' + encodeURIComponent(mcKanbanSelectedId) + '/comments', { headers: { Authorization: 'Bearer ' + token } }); const data = await resp.json().catch(() => ({})); if (!resp.ok) { el.textContent = data.error || String(resp.status); return; } const rows = data.comments || []; el.innerHTML = rows.length ? rows.map(c => '
' + (c.created_at || '') + '
' + esc(c.body || '') + '
').join('') : 'No comments yet.'; } catch (e) { el.textContent = String(e); } } async function mcKanbanSendComment() { const body = (document.getElementById('mcKanbanCommentBody') && document.getElementById('mcKanbanCommentBody').value || '').trim(); if (!mcKanbanSelectedId || !body) return; try { const resp = await fetch(MC_API + '/tickets/' + encodeURIComponent(mcKanbanSelectedId) + '/comments', { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, mcCircadianAuthHeaders(true)), body: JSON.stringify({ body }), }); const d = await resp.json().catch(() => ({})); if (resp.ok) { if (document.getElementById('mcKanbanCommentBody')) document.getElementById('mcKanbanCommentBody').value = ''; mcFetchKanbanComments(); mcAddFeedItem('info', 'Kanban', 'Comment added', mcKanbanSelectedId); } else { mcAddFeedItem('warn', 'Kanban', 'Comment failed', d.error || resp.status); } } catch (e) { mcAddFeedItem('warn', 'Kanban', 'Comment error', e.message); } } async function mcKanbanCreateTicket() { const title = (document.getElementById('mcKanbanNewTitle') && document.getElementById('mcKanbanNewTitle').value || '').trim(); const body = (document.getElementById('mcKanbanNewBody') && document.getElementById('mcKanbanNewBody').value || '').trim(); if (!title) return; try { const resp = await fetch(MC_API + '/tickets', { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, mcCircadianAuthHeaders(true)), body: JSON.stringify({ title, body, source: 'manual', status_slug: 'backlog', priority: 'p2' }), }); const d = await resp.json().catch(() => ({})); if (resp.ok) { if (document.getElementById('mcKanbanNewTitle')) document.getElementById('mcKanbanNewTitle').value = ''; if (document.getElementById('mcKanbanNewBody')) document.getElementById('mcKanbanNewBody').value = ''; mcAddFeedItem('info', 'Kanban', 'Ticket created', d.id || ''); mcFetchKanban(); } else { mcAddFeedItem('warn', 'Kanban', 'Create failed', d.error || resp.status); } } catch (e) { mcAddFeedItem('warn', 'Kanban', 'Create error', e.message); } } // Always initialize UI wiring immediately (even if WS is down). // This prevents the portal from feeling "unclickable" when connection fails. setUiConnected(false); bindUi(); updateMicButton(); initTabs(); initModelPicker(); if (typeof marked !== 'undefined') { marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false }); } initCodeDefaults(); renderCommands(); // #region agent log — must be declared before any function that calls dbgLog const __DBG_INGEST = window.__CONDUIT_CONFIG?.debug_ingest_url || null; const __DBG_SESSION = window.__CONDUIT_CONFIG?.debug_session_id || null; function dbgLog(loc, msg, data) { if (!__DBG_INGEST) return; fetch(__DBG_INGEST,{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':__DBG_SESSION||''},body:JSON.stringify({sessionId:__DBG_SESSION,location:loc,message:msg,data:data||{},timestamp:Date.now()})}).catch(()=>{}); } // #endregion initIDE(); initThreadList(); refreshAgentSelector(); connect(); async function mcInit() { await awaitPaired(); const grid = document.getElementById('mcAgentGrid'); if (grid && !mcState.initialized) grid.innerHTML = '
Connecting…
'; if (mcState.initialized) { mcSchedulePoll(); return; } mcState.initialized = true; document.getElementById('mcNav')?.addEventListener('click', e => { const btn = e.target.closest('.mc-nav-btn'); if (!btn || !btn.dataset.mcView) return; mcSwitchView(btn.dataset.mcView); }); document.getElementById('mcCircadianJwtSave')?.addEventListener('click', () => { const v = document.getElementById('mcCircadianJwtInput')?.value?.trim() || ''; localStorage.setItem('circadian_jwt', v); mcAddFeedItem('info', 'Mission Control', 'Circadian JWT saved', 'Stored in localStorage as circadian_jwt'); mcFetchRuns(); mcFetchBrowserSessions(); if (mcState.currentView === 'kanban') mcFetchKanban(); }); document.getElementById('mcKanbanRefresh')?.addEventListener('click', () => mcFetchKanban()); document.getElementById('mcKanbanCreate')?.addEventListener('click', () => mcKanbanCreateTicket()); document.getElementById('mcKanbanCommentSend')?.addEventListener('click', () => mcKanbanSendComment()); document.getElementById('mcRunsRefresh')?.addEventListener('click', () => mcFetchRuns()); document.getElementById('mcRunsTableBody')?.addEventListener('click', e => { const cancel = e.target.closest('.mc-run-cancel'); if (cancel) { e.stopPropagation(); mcCancelComputeRun(cancel.dataset.mcRunId); return; } const tr = e.target.closest('tr[data-mc-run-id]'); if (tr) mcSelectRun(tr.dataset.mcRunId); }); document.getElementById('mcRunCancelBtn')?.addEventListener('click', () => mcCancelComputeRun(mcRunsSelectedTaskId)); document.getElementById('mcRunTimelineBtn')?.addEventListener('click', () => mcLoadRunTimeline()); document.getElementById('mcBrowserNewSession')?.addEventListener('click', () => mcStartBrowserSession()); document.getElementById('mcBrowserRefresh')?.addEventListener('click', () => mcFetchBrowserSessions()); document.getElementById('mcBrowserTableBody')?.addEventListener('click', e => { const tr = e.target.closest('tr[data-mc-browser-id]'); if (tr) mcSelectBrowserSession(tr.dataset.mcBrowserId); }); document.getElementById('mcBrowserHandoffAgent')?.addEventListener('click', () => mcPatchBrowserInputMode('agent')); document.getElementById('mcBrowserHandoffOperator')?.addEventListener('click', () => mcPatchBrowserInputMode('operator')); document.getElementById('mcBrowserViewerToken')?.addEventListener('click', () => mcMintBrowserViewerToken()); document.getElementById('mcBrowserSaveLiveUrl')?.addEventListener('click', () => mcPatchBrowserLiveUrl()); document.getElementById('mcBrowserLiveAuto')?.addEventListener('change', () => { if (mcState.currentView === 'browsers' && mcSelectedBrowserSessionId) mcScheduleBrowserLivePoll(); }); document.getElementById('mcSiteBundleCapture')?.addEventListener('click', () => { mcCaptureEducationalBundle(); }); document.getElementById('mcSiteBundleHandoffZip')?.addEventListener('click', () => { mcDownloadEducationalHandoffZip(); }); document.getElementById('mcSiteBundleTabSum')?.addEventListener('click', () => { mcSiteBundleSetTab('sum'); }); document.getElementById('mcSiteBundleTabApi')?.addEventListener('click', () => { mcSiteBundleSetTab('api'); }); document.getElementById('mcSiteBundleDownload')?.addEventListener('click', () => { mcDownloadEducationalBundle(); }); document.getElementById('mcSiteBundleSandbox')?.addEventListener('click', () => { mcOpenEducationalSandbox(); }); document.getElementById('mcSiteBundleCloseSandbox')?.addEventListener('click', () => { mcCloseEducationalSandbox(); }); // ═══ CUA PANEL EVENT WIRING ═══ cuaInitPanel(); document.getElementById('btnCuaPanel')?.addEventListener('click', () => cuaTogglePanel()); mcFleetInit(); mcSchedulePoll(); } // ════════════════════════════════════════════════════════════════════════ // CUA LIVE PANEL — Manus "My Computer" style right sidebar // ════════════════════════════════════════════════════════════════════════ let cuaSessionId = null; let cuaMode = 'agent'; let cuaLiveTimer = null; let cuaStepHistory = []; let cuaIsLive = true; let cuaTotalSteps = 0; function cuaInitPanel() { document.getElementById('cuaPanelClose')?.addEventListener('click', () => cuaClosePanel()); document.getElementById('cuaJumpLive')?.addEventListener('click', () => cuaJumpToLive()); document.getElementById('cuaModeAgent')?.addEventListener('click', () => cuaSetMode('agent')); document.getElementById('cuaModeOperator')?.addEventListener('click', () => cuaSetMode('operator')); document.getElementById('cuaTakeoverSubmit')?.addEventListener('click', () => cuaSubmitTakeover()); document.getElementById('cuaPanelToggle')?.addEventListener('click', () => cuaTogglePanel()); document.getElementById('cuaTimelineSlider')?.addEventListener('input', (e) => cuaScrubTo(parseInt(e.target.value))); } function cuaOpenPanel(sessionId) { cuaSessionId = sessionId || null; const panel = document.getElementById('cuaPanel'); if (panel) panel.classList.add('open'); cuaUpdateStatus('idle', "Agent's Computer"); cuaStepHistory = []; cuaTotalSteps = 0; cuaIsLive = true; cuaUpdateTimeline(); if (sessionId) cuaStartLivePoll(); } function cuaClosePanel() { const panel = document.getElementById('cuaPanel'); if (panel) panel.classList.remove('open'); cuaStopLivePoll(); cuaSessionId = null; } function cuaTogglePanel() { const panel = document.getElementById('cuaPanel'); if (panel && panel.classList.contains('open')) { cuaClosePanel(); } else { cuaOpenPanel(cuaSessionId || mcSelectedBrowserSessionId); } } function cuaUpdateStatus(state, text) { const dot = document.getElementById('cuaStatusDot'); const txt = document.getElementById('cuaStatusText'); if (dot) { dot.className = 'cua-status-dot'; if (state === 'acting') dot.classList.add('acting'); else if (state === 'idle') dot.classList.add('idle'); } if (txt) txt.textContent = text || ''; } function cuaSetMode(mode) { cuaMode = mode; const agentBtn = document.getElementById('cuaModeAgent'); const operatorBtn = document.getElementById('cuaModeOperator'); const takeoverBox = document.getElementById('cuaTakeoverInput'); if (agentBtn) agentBtn.classList.toggle('active', mode === 'agent'); if (operatorBtn) operatorBtn.classList.toggle('active', mode === 'operator'); if (takeoverBox) takeoverBox.classList.toggle('open', mode === 'operator'); if (mode === 'operator') { cuaUpdateStatus('idle', 'Operator takeover — agent paused'); cuaStopLivePoll(); } else { cuaUpdateStatus('acting', 'Agent is working...'); cuaStartLivePoll(); } if (cuaSessionId) { const token = getCircadianJwt(); if (token) { fetch(CP_BROWSER_API + '/' + encodeURIComponent(cuaSessionId) + '/input-mode', { method: 'PATCH', headers: mcCircadianAuthHeaders(true), body: JSON.stringify({ input_mode: mode }), }).catch(() => {}); } } } async function cuaSubmitTakeover() { const textarea = document.getElementById('cuaTakeoverMsg'); const msg = (textarea && textarea.value || '').trim(); if (!msg) return; if (ws && ws.readyState === WebSocket.OPEN && cuaSessionId) { const rpc = JSON.stringify({ type: 'req', id: 'cua_takeover_' + Date.now(), method: 'cua.execute_step', params: { session_id: cuaSessionId, action: { type: 'operator_message', text: msg }, }, }); ws.send(rpc); } if (textarea) textarea.value = ''; cuaSetMode('agent'); cuaUpdateStatus('acting', 'Agent resuming with operator context...'); } function cuaStartLivePoll() { cuaStopLivePoll(); cuaLiveTimer = setInterval(() => cuaPollLiveScreenshot(), 2000); cuaPollLiveScreenshot(); } function cuaStopLivePoll() { if (cuaLiveTimer) { clearInterval(cuaLiveTimer); cuaLiveTimer = null; } } async function cuaPollLiveScreenshot() { const sid = cuaSessionId || mcSelectedBrowserSessionId; const token = getCircadianJwt(); if (!sid || !token) return; try { const resp = await fetch(CP_BROWSER_API + '/' + encodeURIComponent(sid) + '/live.png', { headers: { Authorization: 'Bearer ' + token }, }); if (!resp.ok) return; const blob = await resp.blob(); const url = URL.createObjectURL(blob); const img = document.getElementById('cuaLiveImg'); const placeholder = document.getElementById('cuaPlaceholder'); if (img) { if (img._prevUrl) URL.revokeObjectURL(img._prevUrl); img.src = url; img._prevUrl = url; img.style.display = ''; } if (placeholder) placeholder.style.display = 'none'; if (cuaIsLive) { cuaTotalSteps++; cuaStepHistory.push({ ts: Date.now(), blobUrl: url }); if (cuaStepHistory.length > 200) { const old = cuaStepHistory.shift(); if (old.blobUrl) URL.revokeObjectURL(old.blobUrl); } cuaUpdateTimeline(); } } catch (_) {} } function cuaUpdateTimeline() { const slider = document.getElementById('cuaTimelineSlider'); const label = document.getElementById('cuaStepLabel'); const max = Math.max(0, cuaStepHistory.length - 1); if (slider) { slider.max = max; if (cuaIsLive) slider.value = max; } if (label) label.textContent = (cuaIsLive ? cuaStepHistory.length : parseInt(slider?.value || 0) + 1) + ' / ' + cuaStepHistory.length; } function cuaScrubTo(step) { cuaIsLive = false; const entry = cuaStepHistory[step]; if (entry && entry.blobUrl) { const img = document.getElementById('cuaLiveImg'); if (img) { img.src = entry.blobUrl; img.style.display = ''; } const placeholder = document.getElementById('cuaPlaceholder'); if (placeholder) placeholder.style.display = 'none'; } cuaUpdateTimeline(); } function cuaJumpToLive() { cuaIsLive = true; const slider = document.getElementById('cuaTimelineSlider'); if (slider) slider.value = slider.max; cuaUpdateTimeline(); cuaPollLiveScreenshot(); } function cuaOpenFromChat(sessionId) { cuaOpenPanel(sessionId); cuaUpdateStatus('acting', 'Agent is using browser...'); } function mcSchedulePoll() { if (mcState.pollTimer) clearTimeout(mcState.pollTimer); mcPoll().then(() => { mcState.pollTimer = setTimeout(mcSchedulePoll, mcPollInterval); }); } function mcSwitchView(view) { if (mcState.currentView === 'runs' && view !== 'runs') { mcStopRunEventStream(); mcRunsSelectedTaskId = null; } if (mcState.currentView === 'browsers' && view !== 'browsers') { mcStopBrowserLivePoll(); mcSelectedBrowserSessionId = null; } mcState.currentView = view; document.querySelectorAll('.mc-nav-btn').forEach(b => b.classList.toggle('active', b.dataset.mcView === view)); document.querySelectorAll('.mc-view').forEach(v => v.style.display = 'none'); const el = document.getElementById('mcView' + view.charAt(0).toUpperCase() + view.slice(1)); if (el) el.style.display = ''; if (view === 'orchestrators' && mcState.data.orchestrators) mcRenderOrchestrators(); if (view === 'decisions') mcFetchDecisions(); if (view === 'alerts') mcFetchAlerts(); if (view === 'trust') mcFetchTrust(); if (view === 'personas') mcFetchPersonas(); if (view === 'chaos') mcFetchChaos(); if (view === 'pipelines') mcRenderPipelines(); if (view === 'thesis') mcInitThesis(); if (view === 'runs') { const inp = document.getElementById('mcCircadianJwtInput'); if (inp && !inp.value) inp.value = getCircadianJwt(); mcFetchRuns(); } if (view === 'browsers') { const inp = document.getElementById('mcCircadianJwtInput'); if (inp && !inp.value) inp.value = getCircadianJwt(); mcFetchBrowserSessions(); } if (view === 'sitebundle') { const inp = document.getElementById('mcCircadianJwtInput'); if (inp && !inp.value) inp.value = getCircadianJwt(); } if (view === 'kanban') { const inp = document.getElementById('mcCircadianJwtInput'); if (inp && !inp.value) inp.value = getCircadianJwt(); mcFetchKanban(); } } function mcRenderRepoQualityCard(payload) { const card = document.getElementById('mcRepoQualityCard'); const scoreEl = document.getElementById('mcRepoQualityScore'); const meta = document.getElementById('mcRepoQualityMeta'); const canvas = document.getElementById('mcRepoQualityThumb'); if (!card || !scoreEl || !meta || !canvas) return; const rep = payload && payload.report; const tm = payload && payload.treemap && payload.treemap.files; if (!rep) { card.style.display = 'none'; return; } card.style.display = 'grid'; const ui = rep.quality_ui != null ? rep.quality_ui : Math.round((rep.quality_signal || 0) * 10000); scoreEl.textContent = 'Quality ' + ui; meta.textContent = (rep.repo_root || '') + ' · ' + (rep.total_files != null ? rep.total_files : '—') + ' files · ' + (rep.language_count != null ? rep.language_count : '—') + ' langs'; if (window.SentruxTreemap && tm && tm.length) { window.SentruxTreemap.drawTreemap(canvas, tm, { colorMode: 'lang', width: 256, height: 160, showEdges: true }); } } async function mcPoll() { // #region agent log dbgLog('portal:mcPoll', 'MC poll attempt', {errors: mcConsecutiveErrors, interval: mcPollInterval, hypothesisId: 'H4'}); // #endregion try { const token = getCircadianJwt(); const ovHeaders = token ? { Authorization: 'Bearer ' + token } : {}; const resp = await fetch(MC_API + '/overview', { headers: ovHeaders }); if (!resp.ok) { mcOffline(); return; } const data = await resp.json(); mcConsecutiveErrors = 0; mcPollInterval = 5000; mcState.data.computeRunsSummary = data.compute_runs_summary ?? null; mcState.data.browser_sessions_summary = data.browser_sessions_summary ?? null; mcState.data.ticketCounts = data.ticket_counts ?? null; mcState.data.health = data.health; mcState.data.agents = data.agents || []; mcState.data.orchestrators = data.orchestrators || []; mcState.data.alerts = data.recent_alerts || []; mcState.data.decisions = data.recent_decisions || []; mcRenderHealthBar(data); mcRenderFleet(); mcRenderFeed(); try { const qr = await fetch(ARCHIVE_API_BASE + '/api/v10/mission-control/codegraph/quality', { headers: ovHeaders }); if (qr.ok) mcRenderRepoQualityCard(await qr.json()); } catch (_) {} refreshAgentSelector(); document.getElementById('mcLastUpdate').textContent = 'Updated ' + new Date().toLocaleTimeString(); } catch (e) { mcOffline(); } } function mcOffline() { mcConsecutiveErrors++; mcPollInterval = Math.min(MC_MAX_POLL_INTERVAL, 5000 * Math.pow(2, mcConsecutiveErrors - 1)); // #region agent log dbgLog('portal:mcOffline', 'MC offline, backing off', {errors: mcConsecutiveErrors, nextInterval: mcPollInterval, hypothesisId: 'H4'}); // #endregion document.getElementById('mcHealthPct').textContent = 'Offline'; document.getElementById('mcGaugeFill').style.width = '0%'; } function mcRenderHealthBar(data) { const h = data.health || {}; const score = h.overall_score ?? 0; const pct = Math.round(score * 100); const fill = document.getElementById('mcGaugeFill'); fill.style.width = pct + '%'; fill.className = 'mc-gauge-fill ' + (pct >= 80 ? 'healthy' : pct >= 50 ? 'degraded' : 'critical'); document.getElementById('mcHealthPct').textContent = pct + '%'; const agents = data.agents || []; const healthy = agents.filter(a => a.status === 'Healthy' || a.status === 'healthy').length; document.getElementById('mcAgentCount').textContent = healthy + '/' + agents.length; const alertCount = (data.recent_alerts || []).length; document.getElementById('mcAlertCount').textContent = alertCount; const alertBadge = document.getElementById('mcAlertBadge'); if (alertBadge) alertBadge.style.display = alertCount > 0 ? '' : 'none'; const orch = data.orchestrators || []; const orchHealthy = orch.filter(o => o.healthy).length; document.getElementById('mcOrchCount').textContent = orchHealthy + '/' + orch.length; const orchDot = document.getElementById('mcOrchDot'); if (orchDot) orchDot.className = 'dot ' + (orchHealthy === orch.length ? 'green' : orchHealthy > 0 ? 'yellow' : 'red'); const bsum = data.browser_sessions_summary; const bwrap = document.getElementById('mcBrowserHealthWrap'); const bbrief = document.getElementById('mcBrowserBrief'); if (bwrap && bbrief) { if (bsum && typeof bsum === 'object') { bwrap.style.display = ''; const a = bsum.active_sessions != null ? bsum.active_sessions : 0; const o = bsum.operator_handoff_sessions != null ? bsum.operator_handoff_sessions : 0; bbrief.textContent = String(a) + (o ? ' (' + o + ' op)' : ''); } else { bwrap.style.display = 'none'; } } } let mcFleetFilter = 'all'; function mcRenderFleet() { const grid = document.getElementById('mcAgentGrid'); if (!grid) return; const allAgents = mcState.data.agents; const agents = allAgents.filter(a => { if (mcFleetFilter === 'all') return true; if (mcFleetFilter === 'running') return ['healthy','degraded','active','restarting','processing'].includes((a.status||'').toLowerCase()); if (mcFleetFilter === 'available') return (a.status||'').toLowerCase() === 'available'; return (a.source || '') === mcFleetFilter; }); document.getElementById('mcFleetCount').textContent = agents.length + ' of ' + allAgents.length; grid.innerHTML = agents.map((a, i) => { const idx = allAgents.indexOf(a); const status = (a.status || 'unknown').toLowerCase(); const source = a.source || 'unknown'; const heartbeatAge = a.last_heartbeat ? Math.round((Date.now() - new Date(a.last_heartbeat).getTime()) / 1000) : '—'; const uptimeMin = a.uptime_secs ? Math.round(a.uptime_secs / 60) : (a.started_at ? Math.round((Date.now() - new Date(a.started_at).getTime()) / 60000) : 0); const healthPct = status === 'healthy' || status === 'active' ? 100 : status === 'degraded' ? 60 : status === 'restarting' || status === 'paused' ? 30 : status === 'available' ? 0 : 0; let actions = ''; if (a.can_start) actions += ``; if (a.can_pause) actions += ``; if (a.can_resume) actions += ``; if (a.can_retire) actions += ``; if (a.can_terminate) actions += ``; return `
${esc(a.name || a.id || 'Agent')}${a.color ? `` : ''}${source}
${esc(a.agent_type || '—')}${a.division ? ' · ' + esc(a.division) : ''}${a.provider ? ' · ' + esc(a.provider) : ''}
${status}
${source !== 'persona' ? `
Heartbeat: ${heartbeatAge === '—' ? '—' : heartbeatAge + 's'} Restarts: ${a.restart_count ?? 0} Uptime: ${uptimeMin}m
` : `
Skills: ${(a.skills||[]).length}${a.nexus_phase ? `Phase: ${esc(a.nexus_phase)}` : ''}
`}
${(() => { const meta = mcGetSessionMeta(a.id); const hasTurns = meta.turnCount > 0; const lastAgo = meta.lastActive ? (() => { const sec = Math.round((Date.now() - new Date(meta.lastActive).getTime()) / 1000); if (sec < 60) return sec + 's ago'; if (sec < 3600) return Math.round(sec/60) + 'm ago'; if (sec < 86400) return Math.round(sec/3600) + 'h ago'; return Math.round(sec/86400) + 'd ago'; })() : null; return hasTurns ? `
💬 ${meta.turnCount} ${lastAgo ? `🕑 ${lastAgo}` : ''} ${meta.sessionId.slice(0, 16)}…
` : ''; })()} ${actions ? `
${actions}
` : ''}
`; }).join(''); } function mcFleetInit() { document.getElementById('mcFleetFilter')?.addEventListener('click', e => { const chip = e.target.closest('.mc-filter-chip'); if (!chip) return; mcFleetFilter = chip.dataset.filter; document.querySelectorAll('#mcFleetFilter .mc-filter-chip').forEach(c => c.classList.toggle('active', c === chip)); mcRenderFleet(); }); document.getElementById('mcAgentGrid')?.addEventListener('click', e => { const actBtn = e.target.closest('.mc-act-btn'); if (actBtn) { e.stopPropagation(); mcLifecycleAction(actBtn.dataset.act, actBtn.dataset.id); return; } const card = e.target.closest('.mc-agent-card'); if (card) { const idx = Number(card.dataset.agentIdx); mcSelectAgent(idx); } }); document.getElementById('mcCreateAgent')?.addEventListener('click', mcShowCreateDialog); } function mcSelectAgent(idx) { mcState.selectedAgent = idx; document.querySelectorAll('.mc-agent-card').forEach(c => c.classList.toggle('selected', Number(c.dataset.agentIdx) === idx)); const agent = mcState.data.agents[idx]; if (!agent) return; const detail = document.getElementById('mcAgentDetail'); detail.style.display = ''; const status = (agent.status || '').toLowerCase(); const source = agent.source || 'unknown'; const decisions = mcState.data.decisions.filter(d => d.agent_id === agent.id || d.agent_name === agent.name ).slice(0, 10); let actionBar = '
'; if (agent.can_start) actionBar += ``; if (agent.can_pause) actionBar += ``; if (agent.can_resume) actionBar += ``; if (agent.can_retire) actionBar += ``; if (agent.can_terminate) actionBar += ``; actionBar += '
'; detail.innerHTML = `
${esc(agent.name || agent.id)}${agent.color ? `` : ''}${source}
${status} ${esc(agent.agent_type || '')}${agent.division ? ' · ' + esc(agent.division) : ''}${agent.provider ? ' · ' + esc(agent.provider) : ''}${agent.model ? ' · ' + esc(agent.model) : ''}
${actionBar}
${(() => { const meta = mcGetSessionMeta(agent.id); const hasTurns = meta.turnCount > 0; return ` Session: ${esc(meta.sessionId.slice(0, 20))}… · ${meta.turnCount || 0} turns ${meta.lastActive ? ' · ' + new Date(meta.lastActive).toLocaleString() : ''} ${hasTurns ? `` : ''} ${hasTurns ? `` : ''} `; })()}
💬
Send a message to ${esc(agent.name || 'this agent')}
Messages are processed by ${esc(agent.provider || 'the configured LLM')}
`; detail.querySelector('#mcCloseDetail')?.addEventListener('click', () => { detail.style.display = 'none'; mcState.selectedAgent = null; document.querySelectorAll('.mc-agent-card').forEach(c => c.classList.remove('selected')); if (mcChatPollTimer) { clearInterval(mcChatPollTimer); mcChatPollTimer = null; } }); detail.querySelector('.mc-detail-tabs')?.addEventListener('click', e => { const tab = e.target.closest('.dt-tab'); if (!tab) return; detail.querySelectorAll('.dt-tab').forEach(t => t.classList.toggle('active', t === tab)); ['Chat', 'Pipeline', 'Decisions', 'Skills', 'Config'].forEach(name => { const el = detail.querySelector('#mcDetail' + name); if (el) el.style.display = tab.dataset.dt === name.toLowerCase() ? '' : 'none'; }); }); mcInitChatPanel(agent); } // ─── Agent Chat Panel (Persistent Isolated Sessions) ──────────────── // // Each agent gets a deterministic session ID: mc:: // Sessions persist in Aurora via the chat-archive API (/api/v10/chat-archive). // Chat messages route through the gateway WebSocket (agent/chat methods). // Sessions survive agent pause/stop -- history loads from Aurora on select. let mcChatPollTimer = null; let mcChatStreamDiv = null; let mcChatReqId = null; const MC_DEVICE_SUFFIX = (sessionId || 'anon').replace(/^sess-/, '').slice(0, 12); const mcSessionCache = (() => { try { return JSON.parse(localStorage.getItem('mc_agent_sessions') || '{}'); } catch { return {}; } })(); function mcSaveSessionCache() { try { localStorage.setItem('mc_agent_sessions', JSON.stringify(mcSessionCache)); } catch {} } function mcAgentSessionId(agentId) { return 'mc:' + String(agentId).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 40) + ':' + MC_DEVICE_SUFFIX; } function mcGetSessionMeta(agentId) { const sid = mcAgentSessionId(agentId); if (!mcSessionCache[sid]) { mcSessionCache[sid] = { agentId, sessionId: sid, turnCount: 0, lastActive: null, conversationId: null, title: null, }; } return mcSessionCache[sid]; } function mcInitChatPanel(agent) { const input = document.getElementById('mcChatInput'); const sendBtn = document.getElementById('mcChatSendBtn'); if (!input || !sendBtn) return; mcLoadChatFromArchive(agent); const cloned = sendBtn.cloneNode(true); sendBtn.parentNode.replaceChild(cloned, sendBtn); cloned.addEventListener('click', () => mcSendChat(agent)); const clonedInput = input.cloneNode(true); input.parentNode.replaceChild(clonedInput, input); clonedInput.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); mcSendChat(agent); } }); clonedInput.addEventListener('input', () => { clonedInput.style.height = 'auto'; clonedInput.style.height = Math.min(clonedInput.scrollHeight, 100) + 'px'; }); clonedInput.focus(); if (mcChatPollTimer) clearInterval(mcChatPollTimer); document.getElementById('mcBtnClearSession')?.addEventListener('click', () => { if (!confirm('Clear all chat history for ' + (agent.name || agent.id) + '? This removes the local session cache. Aurora archive is preserved.')) return; mcClearAgentSession(agent.id); const c = document.getElementById('mcChatMessages'); if (c) mcShowEmptyChat(c, agent); mcUpdateSessionBar(agent); }); document.getElementById('mcBtnExportSession')?.addEventListener('click', () => { const msgs = document.querySelectorAll('#mcChatMessages .mc-chat-msg'); let text = 'Session: ' + mcAgentSessionId(agent.id) + '\nAgent: ' + (agent.name || agent.id) + '\nExported: ' + new Date().toISOString() + '\n\n'; msgs.forEach(m => { const role = m.classList.contains('user') ? 'USER' : m.classList.contains('assistant') ? 'AGENT' : 'SYSTEM'; const content = m.querySelector('.mc-chat-content')?.textContent || m.textContent || ''; text += `[${role}] ${content.trim()}\n\n`; }); const blob = new Blob([text], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'session-' + agent.id + '-' + Date.now() + '.txt'; a.click(); URL.revokeObjectURL(url); }); } async function mcLoadChatFromArchive(agent) { const container = document.getElementById('mcChatMessages'); if (!container) return; const meta = mcGetSessionMeta(agent.id); const sid = meta.sessionId; container.innerHTML = '
Loading session...
'; if (meta.conversationId) { try { const resp = await fetch(ARCHIVE_API_BASE + '/api/v10/chat-archive/conversations/' + encodeURIComponent(meta.conversationId)); if (resp.ok) { const data = await resp.json(); container.innerHTML = ''; const turns = data.turns || []; if (turns.length === 0) { mcShowEmptyChat(container, agent); return; } turns.forEach(t => { if (t.user_content) mcAppendChatBubble({ role: 'user', content: t.user_content, timestamp: t.created_at }); if (t.agent_content) mcAppendChatBubble({ role: 'assistant', content: t.agent_content, timestamp: t.created_at }); }); meta.turnCount = turns.length; mcSaveSessionCache(); container.scrollTop = container.scrollHeight; return; } } catch {} } try { const resp = await fetch(ARCHIVE_API_BASE + '/api/v10/chat-archive/conversations?device_id=' + encodeURIComponent('mc-' + sid)); if (resp.ok) { const data = await resp.json(); const convos = data.conversations || []; if (convos.length > 0) { const convo = convos[0]; meta.conversationId = convo.id; meta.title = convo.title; meta.turnCount = convo.turn_count || 0; meta.lastActive = convo.updated_at; mcSaveSessionCache(); return mcLoadChatFromArchive(agent); } } } catch {} mcShowEmptyChat(container, agent); } function mcShowEmptyChat(container, agent) { const meta = mcGetSessionMeta(agent.id); const statusLabel = (agent.status || 'unknown').toLowerCase(); const statusColor = statusLabel === 'healthy' || statusLabel === 'active' ? '#22c55e' : statusLabel === 'paused' ? '#f59e0b' : statusLabel === 'available' ? '#64748b' : '#475569'; container.innerHTML = `
💬
Chat with ${esc(agent.name || 'this agent')}
Status: ${statusLabel} · Session: ${esc(meta.sessionId.slice(0, 24))}...
${meta.turnCount > 0 ? meta.turnCount + ' turns archived' : 'New session'} · Persisted in Aurora · Compression: Symbolic DSL + TOON
`; } async function mcSendChat(agent) { const input = document.getElementById('mcChatInput'); const sendBtn = document.getElementById('mcChatSendBtn'); if (!input) return; const text = input.value.trim(); if (!text) return; input.value = ''; input.style.height = 'auto'; if (sendBtn) sendBtn.disabled = true; const meta = mcGetSessionMeta(agent.id); const sid = meta.sessionId; mcAppendChatBubble({ role: 'user', content: text, timestamp: new Date().toISOString() }); const container = document.getElementById('mcChatMessages'); mcChatStreamDiv = document.createElement('div'); mcChatStreamDiv.className = 'mc-chat-msg assistant streaming'; mcChatStreamDiv.innerHTML = '
'; container?.appendChild(mcChatStreamDiv); container.scrollTop = container.scrollHeight; if (isSocketOpen()) { const id = 'mc-agent-' + Date.now(); mcChatReqId = id; const useHumphrey = (agent.agent_type || '').toLowerCase().includes('humphrey') || (agent.agent_type || '').toLowerCase().includes('voice'); const useTrading = (agent.agent_type || '').toLowerCase().includes('trad') || (agent.source || '') === 'trader'; const useCoding = (agent.agent_type || '').toLowerCase().includes('coding') || (agent.agent_type || '').toLowerCase().includes('brain'); let method = 'agent'; let params = { message: text, stream: true, chat_id: sid }; if (useHumphrey) { method = 'humphrey.query'; params = { query: text }; } else if (useTrading) { method = 'trading.agent'; params = { message: text, session_id: sid }; } else if (useCoding) { method = 'coding_brain.reason'; params = { query: text, mode: 'advanced' }; } if (selectedProvider) params.provider = selectedProvider; if (selectedModel) params.model = selectedModel; const origOnMessage = ws.onmessage; const mcMsgHandler = (e) => { try { const msg = JSON.parse(e.data); if (msg.type === 'event' && (msg.event === 'stream_chunk' || msg.event === 'chunk') && mcChatReqId === id) { const chunk = msg.payload?.chunk ?? msg.chunk ?? msg.payload ?? ''; if (chunk && mcChatStreamDiv) { const typingEl = mcChatStreamDiv.querySelector('.mc-chat-typing'); if (typingEl) typingEl.remove(); const existing = mcChatStreamDiv.querySelector('.mc-chat-content') || (() => { const d = document.createElement('div'); d.className = 'mc-chat-content'; mcChatStreamDiv.appendChild(d); return d; })(); existing.textContent += chunk; container.scrollTop = container.scrollHeight; } return; } if (msg.type === 'res' && msg.id === id) { ws.onmessage = origOnMessage; mcChatReqId = null; const typing = mcChatStreamDiv?.querySelector('.mc-chat-typing'); if (typing) typing.remove(); if (msg.ok === false) { const errContent = mcChatStreamDiv?.querySelector('.mc-chat-content') || (() => { const d = document.createElement('div'); d.className = 'mc-chat-content'; mcChatStreamDiv?.appendChild(d); return d; })(); if (errContent) { errContent.style.color = '#f87171'; errContent.textContent = msg.error || 'Request failed'; } } else { const payload = msg.payload || {}; const agentText = payload.response || payload.response_text || payload.output || ''; const contentEl = mcChatStreamDiv?.querySelector('.mc-chat-content'); if (contentEl && agentText && !contentEl.textContent.trim()) { contentEl.textContent = agentText; } meta.turnCount = (meta.turnCount || 0) + 1; meta.lastActive = new Date().toISOString(); mcSaveSessionCache(); mcArchiveTurn(sid, agent, text, contentEl?.textContent || agentText); } if (mcChatStreamDiv) { mcChatStreamDiv.classList.remove('streaming'); const timeDiv = document.createElement('div'); timeDiv.className = 'msg-time'; timeDiv.textContent = new Date().toLocaleTimeString(); mcChatStreamDiv.appendChild(timeDiv); } mcChatStreamDiv = null; if (sendBtn) sendBtn.disabled = false; input.focus(); container.scrollTop = container.scrollHeight; return; } if (origOnMessage) origOnMessage.call(ws, e); } catch { if (origOnMessage) origOnMessage.call(ws, e); } }; ws.onmessage = mcMsgHandler; ws.send(JSON.stringify({ type: 'req', id, method, params })); } else { try { const resp = await fetch(MC_API + '/agents/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agent_id: agent.id, message: text, session_id: sid }), }); const data = await resp.json(); const typing = mcChatStreamDiv?.querySelector('.mc-chat-typing'); if (typing) typing.remove(); if (!resp.ok) { mcAppendChatBubble({ role: 'system', content: data.error || 'Failed', timestamp: new Date().toISOString() }); } else { const reply = data.response || data.message || JSON.stringify(data); mcAppendChatBubble({ role: 'assistant', content: reply, timestamp: new Date().toISOString() }); mcArchiveTurn(sid, agent, text, reply); meta.turnCount = (meta.turnCount || 0) + 1; meta.lastActive = new Date().toISOString(); mcSaveSessionCache(); } } catch (e) { mcAppendChatBubble({ role: 'system', content: 'Network error: ' + e.message, timestamp: new Date().toISOString() }); } if (mcChatStreamDiv) mcChatStreamDiv.remove(); mcChatStreamDiv = null; if (sendBtn) sendBtn.disabled = false; input.focus(); } } function mcArchiveTurn(sid, agent, userContent, agentContent) { if (!userContent && !agentContent) return; const meta = mcGetSessionMeta(agent.id); try { fetch(ARCHIVE_API_BASE + '/api/v10/chat-archive/turn', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sid, device_id: 'mc-' + sid, title: meta.title || ('Chat with ' + (agent.name || agent.id)), user_content: userContent, agent_content: agentContent, method: 'mc-agent-chat', turn_index: meta.turnCount || 0, attach_latest_jepa_regime: true, }), }).then(resp => { if (resp.ok) return resp.json(); }).then(data => { if (data?.conversation_id && !meta.conversationId) { meta.conversationId = data.conversation_id; mcSaveSessionCache(); } }).catch(() => {}); } catch {} } function mcClearAgentSession(agentId) { const meta = mcGetSessionMeta(agentId); meta.turnCount = 0; meta.lastActive = null; meta.conversationId = null; meta.title = null; mcSaveSessionCache(); } function mcUpdateSessionBar(agent) { const bar = document.getElementById('mcSessionBar'); if (!bar) return; const meta = mcGetSessionMeta(agent.id); const hasTurns = meta.turnCount > 0; bar.innerHTML = ` Session: ${esc(meta.sessionId.slice(0, 20))}… · ${meta.turnCount || 0} turns ${meta.lastActive ? ' · ' + new Date(meta.lastActive).toLocaleString() : ''} ${hasTurns ? `` : ''} ${hasTurns ? `` : ''} `; } function mcAppendChatBubble(msg) { const container = document.getElementById('mcChatMessages'); if (!container) return; const empty = container.querySelector('.mc-chat-empty'); if (empty) empty.remove(); const role = (msg.role || 'system').toLowerCase(); const div = document.createElement('div'); div.className = 'mc-chat-msg ' + (role === 'assistant' ? 'assistant' : role); const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ''; const content = msg.content || ''; const rendered = (role === 'assistant' && typeof marked !== 'undefined') ? marked.parse(content) : esc(content); div.innerHTML = `
${rendered}
${time ? `
${time}
` : ''}`; container.appendChild(div); container.scrollTop = container.scrollHeight; } function mcRenderAgentPipeline(agent, decisions) { if (!decisions.length) return '
No decision pipeline recorded for this agent yet.
'; let html = '
'; decisions.forEach((d, i) => { const steps = d.reasoning_trace || []; const iconClass = d.decision_type?.includes('Trade') ? 'decision' : d.decision_type?.includes('Research') ? 'research' : d.decision_type?.includes('Alert') ? 'alert' : 'thinking'; const iconSymbol = iconClass === 'decision' ? '●' : iconClass === 'research' ? '⊕' : iconClass === 'alert' ? '⚠' : '⚙'; html += `
${iconSymbol}
${esc(d.decision_type || 'Decision')}
${esc(d.output || d.inputs || '—')}
${d.confidence != null ? `
${Math.round(d.confidence * 100)}%
` : ''} ${steps.length ? '
' + steps.map(s => `
Step ${s.step || ''}: ${esc(s.description || '')} ${s.logic_type || ''}
` ).join('') + '
' : ''}
`; if (i < decisions.length - 1) html += '
'; }); html += '
'; // Decision tree from reasoning trace if (decisions[0]?.reasoning_trace?.length > 1) { html += '
Decision Tree
'; html += mcBuildDecisionTree(decisions[0]); } return html; } function mcBuildDecisionTree(decision) { const steps = decision.reasoning_trace || []; if (!steps.length) return ''; let html = '
'; steps.forEach(s => { const conf = s.confidence_delta != null ? ` (${s.confidence_delta > 0 ? '+' : ''}${(s.confidence_delta * 100).toFixed(0)}%)` : ''; html += `
${s.logic_type === 'Bayesian' ? '𝒫' : s.logic_type === 'Deductive' ? '⊢' : '◆'} ${esc(s.description || 'Step ' + (s.step || ''))} ${esc(s.logic_type || '')}${conf}
`; }); html += '
'; return html; } function mcRenderAgentDecisions(decisions) { if (!decisions.length) return '
No decisions recorded.
'; return decisions.map(d => `
${esc(d.decision_type || 'Decision')} ${d.timestamp ? new Date(d.timestamp).toLocaleString() : ''}
${esc(typeof d.output === 'string' ? d.output : JSON.stringify(d.output || ''))}
${d.confidence != null ? `
Confidence: ${Math.round(d.confidence * 100)}% | Outcome: ${esc(d.outcome || 'pending')}
` : ''} ${d.record_hash ? `
Hash: ${esc(d.record_hash.slice(0, 16))}...
` : ''}
`).join(''); } function mcRenderAgentSkills(agent) { const skills = (agent.skills && agent.skills.length) ? agent.skills : ['General Reasoning', 'Tool Execution', 'Memory Recall', 'Context Compression']; let html = `
Capabilities
`; html += '
' + skills.map(s => `${esc(s)}`).join('') + '
'; html += `
Mind Map
`; html += `
`; html += `
${esc(agent.name || type)}
`; html += `
`; const categories = [ { name: 'Perception', items: ['Market Data', 'JEPA Embeddings', 'Regime Context'] }, { name: 'Reasoning', items: ['TM3 (1090+ Models)', 'Bayesian Updates', 'Decision Trees'] }, { name: 'Action', items: ['Tool Execution', 'API Dispatch', 'Alert Emission'] }, { name: 'Memory', items: ['Working (Session)', 'Episodic (Aurora)', 'Semantic (Vectors)'] }, ]; categories.forEach(cat => { html += `
${cat.name}
${cat.items.map(item => `
${item}
`).join('')}
`; }); html += `
`; html += `
Skill Preservation
`; html += `
Episodic memory: Long-term decisions stored in Aurora (hash-chained)
Semantic memory: Tool usage patterns indexed by vector fingerprint
Working memory: Session context compressed via Symbolic DSL
Reinforcement signals: Failure events adjust decision confidence
JEPA inference: Regime predictions from temporal embeddings
Code Gateway: 36-lang dict + symbolic + TOON compression, per-language syntax validation
`; return html; } function mcRenderPipelines() { const pipeline = document.getElementById('mcRecentPipeline'); if (!pipeline) return; const decisions = mcState.data.decisions.slice(0, 15); if (!decisions.length) { pipeline.innerHTML = '
No recent decisions.
'; return; } pipeline.innerHTML = decisions.map((d, i) => { const iconClass = d.decision_type?.includes('Trade') ? 'decision' : d.decision_type?.includes('Insight') ? 'research' : d.decision_type?.includes('Alert') ? 'alert' : 'tool'; const iconSymbol = iconClass === 'decision' ? '●' : iconClass === 'research' ? '⊕' : iconClass === 'alert' ? '⚠' : '⚙'; return `
${iconSymbol}
${esc(d.agent_name || d.agent_id || '—')} — ${esc(d.decision_type || '')}
${esc(typeof d.output === 'string' ? d.output.slice(0, 120) : JSON.stringify(d.output || '').slice(0, 120))}
${d.confidence != null ? `
${Math.round(d.confidence * 100)}%
` : ''}
` + (i < decisions.length - 1 ? '
' : ''); }).join(''); } function mcRenderOrchestrators() { const grid = document.getElementById('mcOrchGrid'); if (!grid) return; const orch = mcState.data.orchestrators || []; grid.innerHTML = orch.map(o => `
${esc(o.backend || o.name || '—')}
Active tasks${o.active_tasks ?? 0}
Completed${o.total_completed ?? 0}
Avg latency${o.avg_latency_ms ? o.avg_latency_ms + 'ms' : '—'}
`).join(''); } async function mcFetchDecisions() { try { const resp = await fetch(MC_API + '/decisions'); if (!resp.ok) return; const data = await resp.json(); const list = document.getElementById('mcDecisionList'); const chain = document.getElementById('mcChainStatus'); const count = document.getElementById('mcDecisionCount'); if (chain) chain.textContent = data.chain_valid ? 'Valid (' + data.chain_length + ' records)' : 'BROKEN'; if (chain) chain.style.color = data.chain_valid ? '#4ade80' : '#f87171'; if (count) count.textContent = (data.decisions || []).length + ' records'; if (list) list.innerHTML = mcRenderAgentDecisions(data.decisions || []); } catch (e) { /* network error */ } } async function mcFetchAlerts() { try { const resp = await fetch(MC_API + '/alerts'); if (!resp.ok) return; const data = await resp.json(); const timeline = document.getElementById('mcAlertTimeline'); if (!timeline) return; const alerts = data.alerts || []; if (!alerts.length) { timeline.innerHTML = '
No alerts.
'; return; } timeline.innerHTML = alerts.map(a => { const sev = (a.severity || 'info').toLowerCase(); return `
${esc(a.source || '—')} ${sev.toUpperCase()}
${esc(a.title || '')}
${esc(a.detail || '')}
${a.timestamp ? new Date(a.timestamp).toLocaleString() : ''}
`; }).join(''); } catch (e) { /* network error */ } } async function mcFetchTrust() { try { const resp = await fetch(MC_API + '/trust'); if (!resp.ok) return; const data = await resp.json(); const list = document.getElementById('mcTrustList'); const summary = document.getElementById('mcTrustSummary'); if (!list) return; const entries = data.trust_entries || []; if (!entries.length) { list.innerHTML = '
No trust entries.
'; if(summary) summary.innerHTML=''; return; } const avgScore = entries.reduce((s,t)=>s+(t.trust_score??0),0)/entries.length; const highCount = entries.filter(t=>(t.trust_score??0)>=0.7).length; const lowCount = entries.filter(t=>(t.trust_score??0)<0.4).length; const totalVerif = entries.reduce((s,t)=>s+(t.successful_verifications??0)+(t.failed_verifications??0),0); if(summary) summary.innerHTML = [ {label:'Avg Trust',val:(avgScore*100).toFixed(0)+'%',color:avgScore>=0.7?'#4ade80':avgScore>=0.4?'#fbbf24':'#f87171'}, {label:'Trusted Nodes',val:highCount+'/'+entries.length,color:'#38bdf8'}, {label:'At-Risk',val:lowCount+'',color:lowCount>0?'#f87171':'#4ade80'}, {label:'Total Verifications',val:totalVerif+'',color:'#a78bfa'}, ].map(s=>`
${s.label}
${s.val}
`).join(''); list.innerHTML = entries.map(t => { const score = t.trust_score ?? 0; const pct = (score*100).toFixed(0); const cls = score >= 0.7 ? 'high' : score >= 0.4 ? 'med' : 'low'; const ok = t.successful_verifications ?? 0; const fail = t.failed_verifications ?? 0; const total = ok + fail; const successRate = total > 0 ? ((ok/total)*100).toFixed(0) : '0'; return `
${esc(t.node_id || t.public_key_hex?.slice(0, 16) || '—')} ${pct}%
Verified: ${ok} Failed: ${fail} Success Rate: ${successRate}% ${t.public_key_hex ? `Key: ${esc(t.public_key_hex.slice(0,12))}…` : ''}
`; }).join(''); } catch (e) { /* network error */ } } async function mcFetchPersonas() { try { const resp = await fetch(MC_API + '/personas'); if (!resp.ok) return; const data = await resp.json(); const grid = document.getElementById('mcPersonaGrid'); const filter = document.getElementById('mcDivisionFilter'); const count = document.getElementById('mcPersonaCount'); const all = data.personas || []; if (count) count.textContent = all.length + ' personas'; mcState.data.personas = all; const divisions = [...new Set(all.map(p => p.division))].sort(); if (filter) filter.innerHTML = `` + divisions.map(d => ``).join(''); filter?.addEventListener('click', e => { const btn = e.target.closest('[data-div]'); if (!btn) return; filter.querySelectorAll('.dt-tab').forEach(b => { b.classList.toggle('active', b === btn); b.style.background = b === btn ? '#111827' : 'transparent'; b.style.color = b === btn ? '#94a3b8' : '#64748b'; }); mcRenderPersonaGrid(btn.dataset.div === 'all' ? all : all.filter(p => p.division === btn.dataset.div)); }); mcRenderPersonaGrid(all); } catch (e) { /* network error */ } } function mcRenderPersonaGrid(personas) { const grid = document.getElementById('mcPersonaGrid'); if (!grid) return; grid.innerHTML = personas.map((p, i) => `
${esc(p.name || '—')}
${esc(p.division || '')} · ${esc(p.agent_type || '')} · ${esc(p.nexus_phase || '')}
`).join(''); grid.addEventListener('click', e => { const chip = e.target.closest('.mc-persona-chip'); if (!chip) return; const idx = Number(chip.dataset.personaIdx); const persona = personas[idx]; if (!persona) return; const detail = document.getElementById('mcPersonaDetail'); if (!detail) return; detail.style.display = ''; detail.innerHTML = `
${esc(persona.name || '—')}
${esc(persona.division || '')} · ${esc(persona.agent_type || '')} · ${esc(persona.preferred_provider || '')}
${esc(persona.description || '')}
${persona.tools?.length ? '
' + persona.tools.map(t => `${esc(t)}`).join('') + '
' : ''}
NEXUS Phase: ${esc(persona.nexus_phase || '—')}
Source: ${esc(persona.source_path || '—')}
${persona.system_prompt ? `
System Prompt
${esc(persona.system_prompt)}
` : ''}
`; }); } async function mcFetchChaos() { try { const resp = await fetch(MC_API + '/chaos'); if (!resp.ok) return; const data = await resp.json(); mcState.data.chaos = data; const badge = document.getElementById('mcChaosEnabled'); if (badge) { badge.textContent = data.enabled ? 'ENABLED' : 'DISABLED'; badge.style.background = data.enabled ? 'rgba(239,68,68,0.15)' : 'rgba(107,114,128,0.15)'; badge.style.color = data.enabled ? '#f87171' : '#9ca3af'; } const expEl = document.getElementById('mcChaosExperiments'); const faultEl = document.getElementById('mcActiveFaults'); const exps = data.experiments || []; if (expEl) expEl.innerHTML = !exps.length ? '
No chaos experiments defined.
' : exps.map(e => `
${esc(e.name || e.id || '—')}
${esc(e.description || '')}
Status: ${esc(e.status || 'idle')}
`).join(''); const faults = data.active_faults || []; if (faultEl) faultEl.innerHTML = !faults.length ? '
No active fault injections.
' : faults.map(f => `
${esc(f.fault_type || '')} on ${esc(f.target_system || '')} ${f.probability ? ` — P=${f.probability}` : ''}
`).join(''); } catch (e) { /* network error */ } } function mcRenderFeed() { const container = document.getElementById('mcFeedItems'); if (!container) return; const items = []; (mcState.data.alerts || []).slice(0, 8).forEach(a => { items.push({ type: 'alert', severity: (a.severity || 'info').toLowerCase(), source: a.source, title: a.title, detail: a.detail, time: a.timestamp }); }); (mcState.data.decisions || []).slice(0, 8).forEach(d => { items.push({ type: 'decision', source: d.agent_name || d.agent_id, title: d.decision_type, detail: typeof d.output === 'string' ? d.output.slice(0, 80) : '', time: d.timestamp }); }); items.sort((a, b) => new Date(b.time || 0) - new Date(a.time || 0)); container.innerHTML = items.slice(0, 20).map(item => { if (item.type === 'alert') { return `
${esc(item.source || '')}${item.severity.toUpperCase()}
${esc(item.title || '')}
${esc(item.detail || '')}
${item.time ? new Date(item.time).toLocaleTimeString() : ''}
`; } return `
${esc(item.source || '')}DECISION
${esc(item.title || '')}
${esc(item.detail || '')}
${item.time ? new Date(item.time).toLocaleTimeString() : ''}
`; }).join(''); } // ─── Lifecycle API calls ──── async function mcLifecycleAction(action, agentId) { if (action === 'terminate' && !confirm('Terminate this agent? This cannot be undone.')) return; if (action === 'retire' && !confirm('Retire this agent permanently?')) return; try { const resp = await fetch(MC_API + '/agents/' + action, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ agent_id: agentId }), }); const data = await resp.json(); if (data.ok) { mcAddFeedItem('info', 'Mission Control', `Agent ${action}: ${agentId.slice(0,8)}`, data.message || 'Success'); mcPoll(); } else { mcAddFeedItem('warn', 'Mission Control', `${action} failed`, data.error || 'Unknown error'); } } catch (e) { mcAddFeedItem('warn', 'Mission Control', `${action} failed`, e.message); } } window.mcLifecycleAction = mcLifecycleAction; function mcAddFeedItem(severity, source, title, detail) { const container = document.getElementById('mcFeedItems'); if (!container) return; const item = document.createElement('div'); item.className = 'mc-feed-item'; item.innerHTML = `
${esc(source)}${severity.toUpperCase()}
${esc(title)}
${esc(detail)}
${new Date().toLocaleTimeString()}
`; container.prepend(item); while (container.children.length > 30) container.lastChild.remove(); } function mcShowCreateDialog() { const dialog = document.getElementById('mcCreateDialog'); if (!dialog) return; dialog.style.display = ''; const personas = mcState.data.agents.filter(a => a.source === 'persona').map(a => a.name); dialog.innerHTML = `
Create New Agent
Create from Persona (${personas.length} available)
Name
Type
Provider
Model (optional)
`; dialog.querySelector('#mcCloseCreate')?.addEventListener('click', () => { dialog.style.display = 'none'; }); dialog.querySelector('#mcCreatePersona')?.addEventListener('change', e => { const isPersona = !!e.target.value; ['mcCreateType','mcCreateProvider'].forEach(id => { const el = dialog.querySelector('#' + id); if (el) el.disabled = isPersona; }); }); dialog.querySelector('#mcCreateSubmit')?.addEventListener('click', async () => { const persona = dialog.querySelector('#mcCreatePersona')?.value; const payload = { persona_name: persona || undefined, name: dialog.querySelector('#mcCreateName')?.value || undefined, agent_type: !persona ? dialog.querySelector('#mcCreateType')?.value : undefined, provider: !persona ? dialog.querySelector('#mcCreateProvider')?.value : undefined, model: dialog.querySelector('#mcCreateModel')?.value || undefined, }; try { const resp = await fetch(MC_API + '/agents/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await resp.json(); if (data.ok) { dialog.style.display = 'none'; mcAddFeedItem('info', 'Mission Control', 'Agent created', data.message); mcPoll(); } else { mcAddFeedItem('warn', 'Mission Control', 'Create failed', data.error); } } catch (e) { mcAddFeedItem('warn', 'Mission Control', 'Create failed', e.message); } }); } // ═══════════════════════════════════════════════════════════════════════ // EMPIRICAL THESIS PLATFORM — create, backtest, monitor, regenerate // ═══════════════════════════════════════════════════════════════════════ const thesisState = { theses: [], selected: null, backtestPollers: {}, filter: 'all' }; function mcInitThesis() { document.getElementById('mcThesisFilter')?.addEventListener('click', e => { const chip = e.target.closest('.mc-filter-chip'); if (!chip) return; thesisState.filter = chip.dataset.stage; document.querySelectorAll('#mcThesisFilter .mc-filter-chip').forEach(c => c.classList.toggle('active', c === chip)); mcRenderTheses(); }); document.getElementById('mcCreateThesis')?.addEventListener('click', mcShowThesisForm); mcFetchTheses(); } async function mcFetchTheses() { try { const resp = await wsSend('trading.thesis', { action: 'list' }); thesisState.theses = resp?.theses || []; } catch (e) { thesisState.theses = mcDemoTheses(); } mcRenderTheses(); } function mcDemoTheses() { return [ { id: 'th-001', name: 'VWAP Mean Reversion Fade', stage: 'shadow', hypothesis: 'SPX tends to revert to VWAP after >1.5 std deviation excursions during the 10:30-11:30 window when GEX is positive.', symbol: 'SPX', direction: 'neutral', strategy_type: 'mean_reversion', timeframe: 'intraday', entry_conditions: 'Price > 1.5σ from VWAP, GEX > 0, IOC constraint regime = normal', exit_conditions: 'Price returns to VWAP or 30-min time stop or 0.5% stop loss', created_at: '2026-02-28T14:22:00Z', last_backtest: '2026-03-03T09:15:00Z', backtest_results: { sharpe: 1.82, sortino: 2.41, max_drawdown: -3.2, win_rate: 0.64, profit_factor: 1.95, total_trades: 187, total_return: 14.3, pbo: 0.18, status: 'complete' }, shadow_signals: 42, markout_5m_avg: 0.12, }, { id: 'th-002', name: 'DealerView Momentum Breakout', stage: 'candidate', hypothesis: 'When DealerView shows large delta imbalance (>2σ) aligned with ADPA event projection, momentum breakouts in ES have a 65%+ win rate.', symbol: 'ES', direction: 'long', strategy_type: 'dealerview_momentum', timeframe: 'scalp', entry_conditions: 'DealerView delta > 2σ, ADPA event confidence > 0.7, DOM pressure aligned', exit_conditions: 'Trail stop 4 ticks, time stop 5 min, max loss 8 ticks', created_at: '2026-03-01T10:00:00Z', last_backtest: null, backtest_results: null, shadow_signals: 0, markout_5m_avg: null, }, { id: 'th-003', name: 'Vol Surface Skew Arbitrage', stage: 'pilot', hypothesis: 'When 25-delta put skew exceeds 2σ from its 20-day mean while VIX term structure is in contango, selling the skew via bear put spreads produces consistent positive theta.', symbol: 'SPY', direction: 'short', strategy_type: 'vol_surface', timeframe: 'swing', entry_conditions: '25d put skew > 2σ(20d), VIX contango > 5%, IV rank > 40', exit_conditions: 'Skew normalizes to <1σ, or 50% profit, or 14-day time stop', created_at: '2026-02-15T08:30:00Z', last_backtest: '2026-03-02T16:00:00Z', backtest_results: { sharpe: 2.14, sortino: 3.02, max_drawdown: -2.8, win_rate: 0.71, profit_factor: 2.34, total_trades: 94, total_return: 22.7, pbo: 0.11, status: 'complete' }, shadow_signals: 156, markout_5m_avg: 0.18, }, { id: 'th-004', name: 'JEPA Temporal Fingerprint Regime', stage: 'candidate', hypothesis: 'The 256-dim JEPA temporal embedding clusters predict regime shifts 15-30 minutes before they appear in price, enabling early positioning.', symbol: 'ES', direction: 'neutral', strategy_type: 'jepa_fingerprint', timeframe: 'intraday', entry_conditions: 'JEPA regime shift probability > 0.8, cluster transition distance > 3σ', exit_conditions: 'Regime stabilizes (shift probability < 0.3) or 60-min time stop', created_at: '2026-03-03T11:00:00Z', last_backtest: null, backtest_results: null, shadow_signals: 0, markout_5m_avg: null, }, ]; } function mcRenderTheses() { const list = document.getElementById('mcThesisList'); if (!list) return; const filtered = thesisState.filter === 'all' ? thesisState.theses : thesisState.theses.filter(t => t.stage === thesisState.filter); if (!filtered.length) { list.innerHTML = `
No theses${thesisState.filter !== 'all' ? ' in ' + thesisState.filter + ' stage' : ''}. Create one to start testing.
`; return; } list.innerHTML = '
' + filtered.map((t, i) => { const bt = t.backtest_results; const hasResults = bt && bt.status === 'complete'; return `
${esc(t.name)} ${t.stage}
${esc(t.hypothesis || '').substring(0, 120)}${(t.hypothesis || '').length > 120 ? '...' : ''}
${esc(t.symbol)} ${esc(t.direction)} ${esc(t.strategy_type || '').replace(/_/g, ' ')} ${esc(t.timeframe)} ${t.shadow_signals ? `${t.shadow_signals} shadow signals` : ''}
${hasResults ? `
${bt.sharpe.toFixed(2)}
Sharpe
${(bt.win_rate * 100).toFixed(0)}%
Win Rate
${bt.max_drawdown.toFixed(1)}%
Max DD
${bt.total_return > 0 ? '+' : ''}${bt.total_return.toFixed(1)}%
Return
${bt.profit_factor.toFixed(2)}
Profit Factor
${bt.total_trades}
Trades
` : `
No backtest results yet
`}
`; }).join('') + '
'; } async function mcSelectThesis(thesisId) { const thesis = thesisState.theses.find(t => t.id === thesisId); if (!thesis) return; thesisState.selected = thesis; mcRenderTheses(); const detail = document.getElementById('mcThesisDetail'); if (!detail) return; detail.style.display = ''; const bt = thesis.backtest_results; const hasResults = bt && bt.status === 'complete'; const isRunning = bt && bt.status === 'running'; const nextStage = { candidate: 'shadow', shadow: 'pilot', pilot: 'provisional', provisional: 'proven' }[thesis.stage]; detail.innerHTML = `
${esc(thesis.name)} ${thesis.stage}
Hypothesis
${esc(thesis.hypothesis)}
Entry Conditions
${esc(thesis.entry_conditions || 'Not specified')}
Exit Conditions
${esc(thesis.exit_conditions || 'Not specified')}
${esc(thesis.symbol)} Direction: ${esc(thesis.direction)} Strategy: ${esc((thesis.strategy_type || '').replace(/_/g, ' '))} Timeframe: ${esc(thesis.timeframe)} ${thesis.shadow_signals ? `Shadow signals: ${thesis.shadow_signals}` : ''} ${thesis.markout_5m_avg != null ? `5m markout: ${(thesis.markout_5m_avg * 100).toFixed(2)}%` : ''}
${nextStage ? `` : ''}
${hasResults ? mcRenderBacktestResults(thesis) : ''}
`; detail.querySelector('#mcThesisClose')?.addEventListener('click', () => { detail.style.display = 'none'; thesisState.selected = null; mcRenderTheses(); }); } function mcRenderBacktestResults(thesis) { const bt = thesis.backtest_results; if (!bt || bt.status !== 'complete') return ''; const pbo = bt.pbo != null ? bt.pbo : null; return `
Latest Backtest Results Complete
${bt.sharpe.toFixed(2)}
Sharpe Ratio
${bt.sortino.toFixed(2)}
Sortino Ratio
${bt.max_drawdown.toFixed(1)}%
Max Drawdown
${(bt.win_rate * 100).toFixed(1)}%
Win Rate
${bt.profit_factor.toFixed(2)}
Profit Factor
${bt.total_trades}
Total Trades
${bt.total_return > 0 ? '+' : ''}${bt.total_return.toFixed(1)}%
Total Return
${pbo !== null ? `
${(pbo * 100).toFixed(0)}%
PBO (Overfit)
` : ''}
`; if (bt.equity_curve && bt.equity_curve.length > 1) { requestAnimationFrame(() => mcDrawEquityCurve('mcThesisEquityCurve', bt.equity_curve)); } } function mcDrawEquityCurve(containerId, curve) { const el = document.getElementById(containerId); if (!el || !curve || curve.length < 2) return; el.innerHTML = ''; const canvas = document.createElement('canvas'); canvas.width = el.clientWidth || 600; canvas.height = 160; canvas.style.width = '100%'; canvas.style.height = '160px'; el.appendChild(canvas); const ctx = canvas.getContext('2d'); if (!ctx) return; const W = canvas.width, H = canvas.height, pad = 8; const vals = curve.map(p => typeof p === 'number' ? p : (p.equity || p.value || 0)); const mn = Math.min(...vals), mx = Math.max(...vals); const range = mx - mn || 1; const scaleX = (W - 2 * pad) / (vals.length - 1); const scaleY = (H - 2 * pad) / range; ctx.fillStyle = '#0f172a'; ctx.fillRect(0, 0, W, H); const baseline = vals[0]; ctx.beginPath(); ctx.moveTo(pad, H - pad - (vals[0] - mn) * scaleY); for (let i = 1; i < vals.length; i++) { ctx.lineTo(pad + i * scaleX, H - pad - (vals[i] - mn) * scaleY); } ctx.strokeStyle = vals[vals.length - 1] >= baseline ? '#22c55e' : '#ef4444'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.lineTo(pad + (vals.length - 1) * scaleX, H - pad); ctx.lineTo(pad, H - pad); ctx.closePath(); ctx.fillStyle = vals[vals.length - 1] >= baseline ? 'rgba(34,197,94,0.08)' : 'rgba(239,68,68,0.08)'; ctx.fill(); ctx.fillStyle = '#94a3b8'; ctx.font = '10px monospace'; ctx.fillText(`${mx.toFixed(0)}`, pad + 2, pad + 10); ctx.fillText(`${mn.toFixed(0)}`, pad + 2, H - pad - 2); } async function mcThesisBacktest(thesisId) { const thesis = thesisState.theses.find(t => t.id === thesisId); if (!thesis) return; mcAddFeedItem('info', 'Thesis', `Backtest started: ${thesis.name}`, `Running ${thesis.strategy_type} on ${thesis.symbol}`); const liveEl = document.getElementById('mcThesisBacktestLive'); if (liveEl) { liveEl.innerHTML = `
Backtest in Progress Running
⏳ Replaying historical data for ${esc(thesis.symbol)}...
0 bars processed
`; } try { const resp = await wsSend('trading.thesis', { action: 'test', thesis_id: thesisId, symbol: thesis.symbol, strategy: thesis.strategy_type, start_date: thesis.backtest_start || '2025-01-01', end_date: thesis.backtest_end || '2026-03-01', position_sizing: 'half_kelly', initial_capital: 100000, paper_mode: true, }); if (resp?.backtest?.backtest_id) { mcPollBacktestStatus(thesisId, resp.backtest.backtest_id); } else { thesis.backtest_results = resp?.backtest || { status: 'complete', sharpe: 0, sortino: 0, max_drawdown: 0, win_rate: 0, profit_factor: 0, total_trades: 0, total_return: 0 }; mcAddFeedItem('info', 'Thesis', `Backtest complete: ${thesis.name}`, `Sharpe: ${thesis.backtest_results.sharpe?.toFixed(2) || 'N/A'}`); mcSelectThesis(thesisId); } } catch (e) { mcAddFeedItem('warn', 'Thesis', `Backtest error: ${thesis.name}`, e.message || 'Connection failed'); if (liveEl) liveEl.innerHTML = `
Backtest failed: ${esc(e.message || 'Unknown error')}. Check backend connectivity.
`; } } async function mcPollBacktestStatus(thesisId, backtestId) { const maxPolls = 60; let polls = 0; const interval = setInterval(async () => { polls++; try { const resp = await wsSend('trading.backtest', { action: 'status', backtest_id: backtestId }); const progress = document.getElementById('mcBacktestProgress'); if (progress && resp?.backtest?.bars_processed) { progress.textContent = `${resp.backtest.bars_processed} bars processed`; } if (resp?.backtest?.status === 'complete' || resp?.backtest?.status === 'failed') { clearInterval(interval); delete thesisState.backtestPollers[thesisId]; const thesis = thesisState.theses.find(t => t.id === thesisId); if (thesis && resp.backtest.status === 'complete') { thesis.backtest_results = resp.backtest; thesis.last_backtest = new Date().toISOString(); mcAddFeedItem('info', 'Thesis', `Backtest complete: ${thesis.name}`, `Sharpe: ${resp.backtest.sharpe?.toFixed(2)}, Return: ${resp.backtest.total_return?.toFixed(1)}%`); } else if (thesis) { mcAddFeedItem('warn', 'Thesis', `Backtest failed: ${thesis.name}`, resp.backtest.error || 'Unknown'); } mcSelectThesis(thesisId); } } catch { /* keep polling */ } if (polls >= maxPolls) { clearInterval(interval); delete thesisState.backtestPollers[thesisId]; } }, 3000); thesisState.backtestPollers[thesisId] = interval; } async function mcThesisRegenerate(thesisId) { const thesis = thesisState.theses.find(t => t.id === thesisId); if (!thesis) return; const feedback = prompt('Optional feedback for the AI to refine this strategy (or leave blank):'); const optimizeFor = prompt('Optimize for? (sharpe / sortino / win_rate / profit_factor / max_drawdown)', 'sharpe'); mcAddFeedItem('info', 'Thesis', `Regenerating: ${thesis.name}`, `Optimize for ${optimizeFor || 'sharpe'}`); try { const resp = await wsSend('trading.thesis', { action: 'regenerate', thesis_id: thesisId, feedback: feedback || undefined, optimize_for: optimizeFor || 'sharpe', }); if (resp?.thesis) { Object.assign(thesis, resp.thesis); mcAddFeedItem('info', 'Thesis', `Regenerated: ${thesis.name}`, 'New parameters applied. Run backtest to validate.'); mcSelectThesis(thesisId); mcRenderTheses(); } } catch (e) { mcAddFeedItem('warn', 'Thesis', `Regenerate failed: ${thesis.name}`, e.message || 'Unknown error'); } } async function mcThesisPromote(thesisId, targetStage) { const thesis = thesisState.theses.find(t => t.id === thesisId); if (!thesis) return; if (!confirm(`Promote "${thesis.name}" from ${thesis.stage} → ${targetStage}?`)) return; mcAddFeedItem('info', 'Thesis', `Promoting: ${thesis.name}`, `${thesis.stage} → ${targetStage}`); try { const resp = await wsSend('trading.thesis', { action: 'promote', thesis_id: thesisId, target_stage: targetStage, }); if (resp?.thesis) { thesis.stage = targetStage; mcAddFeedItem('info', 'Thesis', `Promoted: ${thesis.name}`, `Now in ${targetStage} stage`); mcSelectThesis(thesisId); mcRenderTheses(); } } catch (e) { mcAddFeedItem('warn', 'Thesis', `Promote failed`, e.message || 'Gates not met'); } } async function mcThesisArchive(thesisId) { const thesis = thesisState.theses.find(t => t.id === thesisId); if (!thesis) return; if (!confirm(`Archive thesis "${thesis.name}"? This will retire it from active testing.`)) return; try { await wsSend('trading.thesis', { action: 'archive', thesis_id: thesisId }); thesis.stage = 'retired'; mcAddFeedItem('info', 'Thesis', `Archived: ${thesis.name}`, 'Moved to retired'); document.getElementById('mcThesisDetail').style.display = 'none'; thesisState.selected = null; mcRenderTheses(); } catch (e) { mcAddFeedItem('warn', 'Thesis', 'Archive failed', e.message); } } function mcShowThesisForm() { const create = document.getElementById('mcThesisCreate'); if (!create) return; create.style.display = ''; document.getElementById('mcThesisDetail').style.display = 'none'; thesisState.selected = null; mcRenderTheses(); create.innerHTML = `
New Empirical Thesis
`; create.querySelector('#mcThesisFormClose')?.addEventListener('click', () => { create.style.display = 'none'; }); create.querySelector('#mcThesisFormCancel')?.addEventListener('click', () => { create.style.display = 'none'; }); const submitHandler = async (runBacktest) => { const payload = { action: 'create', name: create.querySelector('#tfName')?.value, hypothesis: create.querySelector('#tfHypothesis')?.value, symbol: create.querySelector('#tfSymbol')?.value || 'SPX', direction: create.querySelector('#tfDirection')?.value || 'neutral', timeframe: create.querySelector('#tfTimeframe')?.value || 'intraday', strategy_type: create.querySelector('#tfStrategy')?.value || 'mean_reversion', position_sizing: create.querySelector('#tfSizing')?.value || 'half_kelly', entry_conditions: create.querySelector('#tfEntry')?.value || undefined, exit_conditions: create.querySelector('#tfExit')?.value || undefined, backtest_start: create.querySelector('#tfStart')?.value, backtest_end: create.querySelector('#tfEnd')?.value, }; if (!payload.name || !payload.hypothesis) { alert('Name and hypothesis are required.'); return; } try { const resp = await wsSend('trading.thesis', payload); const newThesis = resp?.thesis || { id: 'th-' + Date.now().toString(36), ...payload, stage: 'candidate', created_at: new Date().toISOString(), backtest_results: null, shadow_signals: 0, }; thesisState.theses.push(newThesis); create.style.display = 'none'; mcAddFeedItem('info', 'Thesis', `Created: ${payload.name}`, 'New thesis in candidate stage'); mcRenderTheses(); if (runBacktest) { mcThesisBacktest(newThesis.id); } } catch (e) { mcAddFeedItem('warn', 'Thesis', 'Create failed', e.message || 'Unknown error'); } }; create.querySelector('#mcThesisFormSave')?.addEventListener('click', () => submitHandler(true)); create.querySelector('#mcThesisFormSaveOnly')?.addEventListener('click', () => submitHandler(false)); } // ═══════════════════════════════════════════════════════════════════════ // FEEDBACK — thumbs up/down per agent message // ═══════════════════════════════════════════════════════════════════════ let feedbackMsgCounter = 0; function appendFeedbackBar(msgEl) { const msgId = 'fb-' + (++feedbackMsgCounter) + '-' + Date.now(); msgEl.dataset.feedbackId = msgId; const bar = document.createElement('div'); bar.className = 'msg-feedback'; const btnUp = document.createElement('button'); btnUp.type = 'button'; btnUp.textContent = '👍'; btnUp.title = 'Helpful'; btnUp.setAttribute('aria-label', 'Mark as helpful'); const btnDown = document.createElement('button'); btnDown.type = 'button'; btnDown.textContent = '👎'; btnDown.title = 'Not helpful'; btnDown.setAttribute('aria-label', 'Mark as not helpful'); const label = document.createElement('span'); label.className = 'fb-label'; function submitFeedback(rating) { const bodyEl = msgEl.querySelector('.body'); const content = bodyEl ? bodyEl.textContent.substring(0, 200) : ''; fetch('/api/v10/chat/feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message_id: msgId, chat_id: sessionId, rating: rating, content_preview: content }), }).catch(() => {}); } btnUp.addEventListener('click', () => { const wasActive = btnUp.classList.contains('active-up'); btnUp.classList.toggle('active-up'); btnDown.classList.remove('active-down'); label.textContent = wasActive ? '' : 'Thanks!'; if (!wasActive) submitFeedback('thumbs_up'); }); btnDown.addEventListener('click', () => { const wasActive = btnDown.classList.contains('active-down'); btnDown.classList.toggle('active-down'); btnUp.classList.remove('active-up'); label.textContent = wasActive ? '' : 'Noted'; if (!wasActive) submitFeedback('thumbs_down'); }); bar.appendChild(btnUp); bar.appendChild(btnDown); bar.appendChild(label); msgEl.appendChild(bar); } // ═══════════════════════════════════════════════════════════════════════ // TOOL CALL INTERCEPTOR — detect §CALL / §MULTI / JSON tool patterns // in agent text and dispatch via WS tool.execute // ═══════════════════════════════════════════════════════════════════════ function extractJsonBlocks(text, startPattern) { const results = []; let idx = 0; while (idx < text.length) { const pos = text.indexOf(startPattern, idx); if (pos === -1) break; let braces = 0, start = pos, inStr = false, esc = false; for (let i = pos; i < text.length; i++) { const ch = text[i]; if (esc) { esc = false; continue; } if (ch === '\\') { esc = true; continue; } if (ch === '"') { inStr = !inStr; continue; } if (inStr) continue; if (ch === '{' || ch === '[') braces++; if (ch === '}' || ch === ']') { braces--; if (braces === 0) { try { results.push(JSON.parse(text.slice(start, i + 1))); } catch {} idx = i + 1; break; } } } if (braces !== 0) break; } return results; } function interceptAndExecuteTools(msgEl, agentText) { if (!isSocketOpen() || !agentText) return; const seen = new Set(); const calls = []; // {"tool":"name","input":{...}} — handles arbitrary nesting extractJsonBlocks(agentText, '{"tool"').forEach(obj => { if (obj.tool && typeof obj.tool === 'string') { const key = obj.tool + ':' + JSON.stringify(obj.input || {}); if (!seen.has(key)) { seen.add(key); calls.push({ tool: obj.tool, input: obj.input || {} }); } } }); // §MULTI [{...},{...}] const multiIdx = agentText.indexOf('§MULTI'); if (multiIdx !== -1) { const bracketStart = agentText.indexOf('[', multiIdx); if (bracketStart !== -1) { extractJsonBlocks(agentText.slice(multiIdx), '[').forEach(arr => { if (Array.isArray(arr)) arr.forEach(c => { if (c.tool) calls.push({ tool: c.tool, input: c.input || {} }); }); }); } } if (!calls.length) return; const container = document.createElement('div'); container.className = 'tool-calls-container'; container.style.marginTop = '8px'; msgEl.appendChild(container); calls.forEach((call, idx) => { const card = document.createElement('details'); card.className = 'tool-call-card'; card.innerHTML = `${getToolIcon(call.tool)} ${esc(call.tool)} executing…
Input
${esc(JSON.stringify(call.input, null, 2))}
`; container.appendChild(card); const id = 'tc-' + Date.now() + '-' + idx; internalReq[id] = 'tool.intercept'; const onResult = (msg) => { const badge = card.querySelector('.tc-badge'); const detail = card.querySelector('.tc-detail'); if (msg.ok === false) { if (badge) { badge.className = 'tc-badge error'; badge.textContent = 'failed'; } if (detail) detail.innerHTML += `
Error
${esc(msg.error || 'Unknown error')}
`; } else { if (badge) { badge.className = 'tc-badge success'; badge.textContent = 'done'; } const out = msg.payload?.output ?? msg.payload?.result ?? msg.payload ?? {}; if (detail) detail.innerHTML += `
Result
${esc(typeof out === 'string' ? out : JSON.stringify(out, null, 2))}
`; } scrollChatToBottom(); }; window._toolInterceptCb = window._toolInterceptCb || {}; window._toolInterceptCb[id] = onResult; ws.send(JSON.stringify({ type: 'req', id, method: 'tool.execute', params: { tool: call.tool, parameters: call.input } })); }); } // ═══════════════════════════════════════════════════════════════════════ // FOLLOW-UP PROMPT SUGGESTIONS // ═══════════════════════════════════════════════════════════════════════ function appendFollowUpChips(msgEl, agentText) { const bodyEl = msgEl.querySelector('.body'); const content = bodyEl ? bodyEl.textContent.substring(0, 500) : agentText.substring(0, 500); const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 5000); fetch(ARCHIVE_API_BASE + '/api/v10/chat/follow-up', { method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: ctrl.signal, body: JSON.stringify({ message_id: msgEl.dataset.feedbackId || '', chat_id: sessionId, last_message: content }), }) .then(r => { clearTimeout(timer); return r.ok ? r.json() : null; }) .then(data => { if (!data || !Array.isArray(data.suggestions) || !data.suggestions.length) return; const wrap = document.createElement('div'); wrap.className = 'followup-chips'; data.suggestions.slice(0, 4).forEach(s => { const chip = document.createElement('button'); chip.type = 'button'; chip.className = 'followup-chip'; chip.textContent = s; chip.addEventListener('click', () => { const input = document.getElementById('input'); input.value = s; sendMessage(); wrap.remove(); }); wrap.appendChild(chip); }); msgEl.appendChild(wrap); scrollChatToBottom(); }) .catch(() => { clearTimeout(timer); }); } // ═══════════════════════════════════════════════════════════════════════ // MODERATION CHECK — run input through moderation before send // ═══════════════════════════════════════════════════════════════════════ const MODERATION_ENABLED = window.__CONDUIT_CONFIG?.moderation_enabled ?? false; async function checkModeration(text) { if (!MODERATION_ENABLED) return { allowed: true }; try { const resp = await fetch(ARCHIVE_API_BASE + '/api/v10/moderation/check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text }), }); if (!resp.ok) return { allowed: true }; const data = await resp.json(); return { allowed: !data.flagged, categories: data.categories || [], action: data.action || 'pass' }; } catch { return { allowed: true }; } } function appendModerationBadge(msgEl, categories) { const meta = msgEl.querySelector('.meta'); if (!meta) return; const badge = document.createElement('span'); badge.className = 'moderation-badge flagged'; badge.textContent = '⚠ ' + (categories.length ? categories[0] : 'flagged'); meta.appendChild(badge); } // ═══════════════════════════════════════════════════════════════════════ // SSO LOGIN OVERLAY // ═══════════════════════════════════════════════════════════════════════ const ssoOverlayEl = document.getElementById('ssoOverlay'); const ssoLoginLink = document.getElementById('ssoLoginLink'); if (ssoLoginLink) { ssoLoginLink.addEventListener('click', (e) => { e.preventDefault(); if (ssoOverlayEl) ssoOverlayEl.classList.remove('hidden'); }); } function ssoClose() { if (ssoOverlayEl) ssoOverlayEl.classList.add('hidden'); } function ssoStart(provider) { window.open('/api/v10/auth/sso/' + provider + '/authorize?redirect_uri=' + encodeURIComponent(window.location.origin + '/api/v10/auth/sso/' + provider + '/callback'), '_blank', 'width=500,height=600'); ssoClose(); } /* ═══════════════════════════════════════════════════════════════════════ CURSOR IMPORTS panel — backed by /api/v10/cursor-imports/* ═══════════════════════════════════════════════════════════════════════ */ (function () { let initialized = false; let lastConvList = []; let lastPlanList = []; let projectsKnown = new Set(); function api(path, init) { return fetch(path, Object.assign({ headers: { 'Accept': 'application/json' } }, init || {})); } function fmtBytes(n) { if (typeof n !== 'number' || !isFinite(n)) return '—'; const KB = 1024, MB = 1024 * 1024, GB = 1024 * 1024 * 1024; if (n >= GB) return (n / GB).toFixed(2) + ' GB'; if (n >= MB) return (n / MB).toFixed(2) + ' MB'; if (n >= KB) return (n / KB).toFixed(1) + ' KB'; return n + ' B'; } function fmtTime(s) { if (!s) return ''; try { const d = new Date(s); return d.toLocaleString(); } catch (_) { return s; } } function escapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function logImports(line) { const el = document.getElementById('importsLog'); if (!el) return; const stamp = new Date().toLocaleTimeString(); el.textContent = `[${stamp}] ${line}\n` + el.textContent; } async function refreshStats() { try { const r = await api('/api/v10/cursor-imports/stats'); if (!r.ok) throw new Error('HTTP ' + r.status); const j = await r.json(); document.getElementById('importsStatChats').textContent = (j.chats?.total ?? 0).toLocaleString(); document.getElementById('importsStatChatsSub').textContent = (j.chats?.messages ?? 0).toLocaleString() + ' messages'; document.getElementById('importsStatPlans').textContent = (j.plans?.total ?? 0).toLocaleString(); document.getElementById('importsStatPlansSub').textContent = (j.plans?.total_todos ?? 0).toLocaleString() + ' todos'; document.getElementById('importsStatActive').textContent = (j.plans?.in_progress_todos ?? 0).toLocaleString(); document.getElementById('importsStatSavings').textContent = j.totals?.savings_pct || '—'; document.getElementById('importsStatBytes').textContent = fmtBytes(j.totals?.raw_bytes || 0) + ' → ' + fmtBytes(j.totals?.compressed_bytes || 0); } catch (e) { logImports('stats fetch failed: ' + (e && e.message || e)); } } function rebuildProjectFilter() { const sel = document.getElementById('importsConvProject'); if (!sel) return; const current = sel.value; const opts = [''] .concat(Array.from(projectsKnown).sort().map(p => ``)); sel.innerHTML = opts.join(''); sel.value = projectsKnown.has(current) ? current : ''; } async function refreshConversations() { const filter = document.getElementById('importsConvFilter')?.value?.trim() || ''; const project = document.getElementById('importsConvProject')?.value?.trim() || ''; const params = new URLSearchParams({ limit: '100' }); if (filter) params.set('q', filter); if (project) params.set('project', project); try { const r = await api('/api/v10/cursor-imports/conversations?' + params.toString()); if (!r.ok) throw new Error('HTTP ' + r.status); const j = await r.json(); lastConvList = j.conversations || []; for (const c of lastConvList) if (c.cursor_project) projectsKnown.add(c.cursor_project); rebuildProjectFilter(); renderConvList(); document.getElementById('importsConvCount').textContent = `${lastConvList.length.toLocaleString()} conversations`; } catch (e) { logImports('conversations fetch failed: ' + (e && e.message || e)); } } function renderConvList() { const host = document.getElementById('importsConvList'); if (!host) return; if (!lastConvList.length) { host.innerHTML = '
No conversations yet. Click Sync now to import.
'; return; } host.innerHTML = lastConvList.map(c => { const tags = (c.tags || []).slice(0, 8).map(t => `${escapeHtml(t)}` ).join(''); return `
${escapeHtml(c.title || 'Untitled')}
${(c.message_count ?? 0).toLocaleString()} msgs ${(c.user_message_count ?? 0)}/${(c.assistant_message_count ?? 0)} u/a ${escapeHtml(c.cursor_project || '')} ${fmtTime(c.last_message_at || c.transcript_mtime)} ${fmtBytes(c.raw_bytes || 0)} raw → ${fmtBytes(c.compressed_bytes || 0)} sym
${tags ? `
${tags}
` : ''}
`; }).join(''); host.querySelectorAll('.imports-row').forEach(row => { row.addEventListener('click', () => loadConvDetail(row.dataset.tx, row)); }); } async function loadConvDetail(transcriptUuid, rowEl) { try { const r = await api('/api/v10/cursor-imports/conversations/' + encodeURIComponent(transcriptUuid)); if (!r.ok) throw new Error('HTTP ' + r.status); const c = await r.json(); const detail = document.getElementById('importsConvDetail'); if (!detail) return; document.querySelectorAll('#importsConvList .imports-row').forEach(r => r.classList.remove('active')); if (rowEl) rowEl.classList.add('active'); const messages = Array.isArray(c.messages) ? c.messages : []; const messagesHtml = messages.slice(0, 200).map(m => { const role = (m.role || 'unknown').toLowerCase(); const cls = role === 'user' ? 'role-user' : role === 'assistant' ? 'role-assistant' : 'role-tool'; const text = m.text || ''; return `
${escapeHtml(m.role || 'unknown')}
${escapeHtml(text).slice(0, 4000)}${text.length > 4000 ? ' (truncated)' : ''}
`; }).join(''); detail.innerHTML = `

${escapeHtml(c.title || 'Untitled')}

${escapeHtml(c.cursor_project || '')} transcript: ${escapeHtml(c.transcript_uuid)} ${(c.message_count ?? 0).toLocaleString()} messages ${fmtBytes(c.raw_bytes || 0)} raw → ${fmtBytes(c.compressed_bytes || 0)} symbolic imported ${fmtTime(c.imported_at)}
${messagesHtml || '
No messages decoded.
'} ${messages.length > 200 ? `
Showing first 200 of ${messages.length}
` : ''} `; detail.hidden = false; detail.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) { logImports('detail fetch failed: ' + (e && e.message || e)); } } async function refreshPlans() { const filter = document.getElementById('importsPlanFilter')?.value?.trim() || ''; const activeOnly = document.getElementById('importsPlanActiveOnly')?.checked; const params = new URLSearchParams({ limit: '200' }); if (filter) params.set('q', filter); if (activeOnly) params.set('active_only', 'true'); try { const r = await api('/api/v10/cursor-imports/plans?' + params.toString()); if (!r.ok) throw new Error('HTTP ' + r.status); const j = await r.json(); lastPlanList = j.plans || []; renderPlanList(); document.getElementById('importsPlanCount').textContent = `${lastPlanList.length.toLocaleString()} plans`; } catch (e) { logImports('plans fetch failed: ' + (e && e.message || e)); } } function renderPlanList() { const host = document.getElementById('importsPlanList'); if (!host) return; if (!lastPlanList.length) { host.innerHTML = '
No plans yet. Click Sync now to import.
'; return; } host.innerHTML = lastPlanList.map(p => { const inProg = p.todo_in_progress ?? 0; const pending = p.todo_pending ?? 0; const done = p.todo_completed ?? 0; return `
${escapeHtml(p.name || p.plan_slug)}
${done}/${(p.todo_count ?? 0)} done ${inProg ? `${inProg} in progress` : ''} ${pending ? `${pending} pending` : ''} ${escapeHtml(p.cursor_project || '')} ${fmtTime(p.file_mtime)} ${fmtBytes(p.raw_bytes || 0)} raw → ${fmtBytes(p.compressed_bytes || 0)} sym
${p.overview ? `
${escapeHtml(p.overview).slice(0, 200)}${p.overview.length > 200 ? '…' : ''}
` : ''}
`; }).join(''); host.querySelectorAll('.imports-row').forEach(row => { row.addEventListener('click', () => loadPlanDetail(row.dataset.slug, row)); }); } async function loadPlanDetail(slug, rowEl) { try { const r = await api('/api/v10/cursor-imports/plans/' + encodeURIComponent(slug)); if (!r.ok) throw new Error('HTTP ' + r.status); const p = await r.json(); const detail = document.getElementById('importsPlanDetail'); if (!detail) return; document.querySelectorAll('#importsPlanList .imports-row').forEach(r => r.classList.remove('active')); if (rowEl) rowEl.classList.add('active'); const todos = Array.isArray(p.todos) ? p.todos : []; const todosHtml = todos.map(t => `
${escapeHtml(t.status || 'pending')}
${escapeHtml(t.id || '')} — ${escapeHtml(t.content || '')}
`).join(''); let bodyHtml = 'No body.'; if (p.body_md) { try { if (window.marked && window.DOMPurify) { bodyHtml = window.DOMPurify.sanitize(window.marked.parse(p.body_md)); } else { bodyHtml = '
' + escapeHtml(p.body_md) + '
'; } } catch (_) { bodyHtml = '
' + escapeHtml(p.body_md) + '
'; } } detail.innerHTML = `

${escapeHtml(p.name || p.plan_slug)}

${escapeHtml(p.cursor_project || '')} slug: ${escapeHtml(p.plan_slug)} ${(p.todo_count ?? 0).toLocaleString()} todos ${fmtBytes(p.raw_bytes || 0)} raw → ${fmtBytes(p.compressed_bytes || 0)} symbolic file mtime ${fmtTime(p.file_mtime)}
${p.overview ? `
${escapeHtml(p.overview)}
` : ''} ${todosHtml ? `

Todos

${todosHtml}
` : ''}
${bodyHtml}
`; detail.hidden = false; detail.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch (e) { logImports('plan detail fetch failed: ' + (e && e.message || e)); } } async function runSearch() { const q = document.getElementById('importsSearchInput')?.value?.trim() || ''; const kind = document.getElementById('importsSearchKind')?.value || 'all'; const host = document.getElementById('importsSearchResults'); if (!host) return; if (!q) { host.innerHTML = '
Enter a query…
'; return; } host.innerHTML = '
Searching…
'; try { const r = await api('/api/v10/cursor-imports/search?q=' + encodeURIComponent(q) + '&kind=' + encodeURIComponent(kind)); if (!r.ok) throw new Error('HTTP ' + r.status); const j = await r.json(); const chatRows = (j.chats || []).map(c => `
[chat] ${escapeHtml(c.title || 'Untitled')}
${escapeHtml(c.cursor_project || '')}${fmtTime(c.last_message_at)}
`).join(''); const planRows = (j.plans || []).map(p => `
[plan] ${escapeHtml(p.name || p.plan_slug)}
${escapeHtml(p.cursor_project || '')}${fmtTime(p.file_mtime)}${p.todo_in_progress ? `${p.todo_in_progress} in progress` : ''}
${p.overview ? `
${escapeHtml(p.overview).slice(0, 220)}
` : ''}
`).join(''); host.innerHTML = chatRows + planRows || `
No matches for "${escapeHtml(q)}"
`; host.querySelectorAll('.imports-row[data-kind="chat"]').forEach(row => { row.addEventListener('click', () => loadConvDetail(row.dataset.tx, null)); }); host.querySelectorAll('.imports-row[data-kind="plan"]').forEach(row => { row.addEventListener('click', () => loadPlanDetail(row.dataset.slug, null)); }); } catch (e) { host.innerHTML = '
Search failed: ' + escapeHtml(String(e && e.message || e)) + '
'; } } async function triggerSync(forceFull) { logImports('sync started' + (forceFull ? ' (force full)' : '')); try { const r = await api('/api/v10/cursor-imports/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force_full: !!forceFull }), }); const j = await r.json(); if (!r.ok) throw new Error(j.error || 'HTTP ' + r.status); const rep = j.report || {}; logImports(`sync done in ${rep.duration_ms}ms: chats +${rep.chats_inserted}/${rep.chats_updated} (skip ${rep.chats_skipped_unchanged}), plans +${rep.plans_inserted}/${rep.plans_updated} (skip ${rep.plans_skipped_unchanged}); raw ${fmtBytes(rep.raw_bytes_total)} → sym ${fmtBytes(rep.compressed_bytes_total)}`); await Promise.all([refreshStats(), refreshConversations(), refreshPlans()]); } catch (e) { logImports('sync FAILED: ' + (e && e.message || e)); } } function bindImportsHandlersOnce() { if (initialized) return; initialized = true; document.getElementById('btnImportsRefresh')?.addEventListener('click', () => { void refreshStats(); void refreshConversations(); void refreshPlans(); }); document.getElementById('btnImportsSync')?.addEventListener('click', () => triggerSync(false)); document.getElementById('btnImportsSyncForce')?.addEventListener('click', () => triggerSync(true)); document.getElementById('importsConvFilter')?.addEventListener('input', debounce(refreshConversations, 250)); document.getElementById('importsConvProject')?.addEventListener('change', refreshConversations); document.getElementById('importsPlanFilter')?.addEventListener('input', debounce(refreshPlans, 250)); document.getElementById('importsPlanActiveOnly')?.addEventListener('change', refreshPlans); document.getElementById('btnImportsSearch')?.addEventListener('click', runSearch); document.getElementById('importsSearchInput')?.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); runSearch(); } }); document.querySelectorAll('#importsModeView .panel-tabs .tab').forEach(tab => { tab.addEventListener('click', () => { const target = tab.dataset.importsTab; document.querySelectorAll('#importsModeView .panel-tabs .tab').forEach(t => t.classList.toggle('active', t === tab)); document.getElementById('importsConversationsPanel').hidden = (target !== 'conversations'); document.getElementById('importsPlansPanel').hidden = (target !== 'plans'); document.getElementById('importsSearchPanel').hidden = (target !== 'search'); }); }); } function debounce(fn, ms) { let t; return function () { clearTimeout(t); const a = arguments, c = this; t = setTimeout(() => fn.apply(c, a), ms); }; } async function initImportsPanel() { bindImportsHandlersOnce(); await Promise.all([refreshStats(), refreshConversations(), refreshPlans()]); } /** * Activate one of the imports sub-tabs (`conversations` | `plans` | `search`). * Used by the chat-window cursor quick-tabs so a click jumps straight to the * right sub-panel instead of dropping the user on the default Conversations tab. */ function activateImportsTab(target) { const valid = new Set(['conversations', 'plans', 'search']); const normalized = valid.has(target) ? target : 'conversations'; document.querySelectorAll('[data-imports-tab]').forEach(btn => { btn.classList.toggle('active', btn.dataset.importsTab === normalized); }); const map = { conversations: 'importsConversationsPanel', plans: 'importsPlansPanel', search: 'importsSearchPanel', }; Object.entries(map).forEach(([k, id]) => { const el = document.getElementById(id); if (!el) return; if (k === normalized) el.removeAttribute('hidden'); else el.setAttribute('hidden', ''); }); } // Expose for switchMode() + cursor quick-tabs. window.initImportsPanel = initImportsPanel; window.activateImportsTab = activateImportsTab; window.refreshImportsStatsForQuickTabs = refreshStats; })();