Skip to editor Skip to agent chat

Conduit Silver

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

Ask anything

Agent, Plan, Ask, or Debug. Tools, model, and files stay in the composer — the stream is for the conversation.

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:
Agents Window
Agent Sessions
Loading agents…
Explorer
No results
1 of 1
Reviewing changes 0 hunks
⌘
Open a file from the explorer or press Ctrl+P
Conduit Silver IDE — Full Compression Stack
OS jail: … network: … mode: allowlist classifier: deterministic health: …
$
No problems have been detected in the workspace.
Debug console — agent process events, WebSocket traffic, and compression stats appear here.
Loading port map…
—
Loading Sentrux quality…
Agent Chat Keys: /configure-llm — not in chat text
History (local)
Planning next moves
⑃ main ↑0 ↓0 0 errors
Ln 1, Col 1 Spaces: 2 UTF-8 LF blame off Plain Text Model: Auto tok — keys: … 🔔 health: … 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

          
circadian search
mothership: handshaking corpus: loading… ocr: probing…

circadian

Ask the attached research sandbox first (Analog of Perplexity Computer — not Equivalent). Native HFT / distillery / sonar-deep-research overlay when :7070 is up. Waves are stamped: Jurisdica ownership, Legal OS RR, Moneyball letter vs Full, Kaswari parcel. Do not sum SAM across waves. Pin answers as research/YYYY-MM-DD-slug.md.

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

Portfolio command summary + outcome discovery + settled ledger via . Band = liquidity bucket, not edge guarantee. Auth: Authorization: Bearer … or X-Api-Key.

Not loaded yet.

Load discovery…

Enter portfolio ID and refresh.

Enter portfolio ID and refresh.


            
SSE: /api/v9/trader/pm/dashboard-stream

          

Trading-reasoning playground

Same production tree as run_decision_cycle (IOC → dealer GEX → ADPA Moneyball → microstress → TRE fuse → CFTM → P18 → TRADE vs skip → OCC). OpenRouter catalog is inventory, not live votes. LLM stance never OrderSends. ADPA-Q01 remains rank SSOT. Millibit/FEATURE is tick-history substrate, not a fuse voter. Empty Max is honest — ticks are never invented. enable_outcome_routing stays false. Rely bands are advisory chrome only.

Not loaded yet.

Production DAG

    Scenarios

    Rely-band scorecard

    Load protocol or score stored egress.

    
            

    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
    
            
    BahamaBoard Owned canvas · Flip · Surfaces · voice · nodes · no tldraw iframe → /bahama-board/app TV mode Vite :18889
    ' + 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(); if (typeof bindRunModeSelect === 'function') bindRunModeSelect(); document.getElementById('agentSelect')?.addEventListener('focus', () => { refreshAgentSelector(); }, { once: true }); connect(); async function mcInit() { try { await pairedPromise; } catch (_) {} 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(); } } 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(); 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(); }