(function () { const esc = (s) => String(s ?? '').replace(/&/g, '&').replace(//g, '>'); const SEV_COLS = [ { key: 'critical', label: 'Crítico', cls: 'ao-board-col--critical' }, { key: 'high', label: 'Alto', cls: 'ao-board-col--high' }, { key: 'warn', label: 'Aviso', cls: 'ao-board-col--warn' }, { key: 'info', label: 'Info / OK', cls: 'ao-board-col--ok' }, ]; const AGENT_ACCENTS = { A0: '#6366f1', A1: '#22c55e', A2: '#3b82f6', A3: '#06b6d4', A4: '#8b5cf6', A5: '#ec4899', A6: '#a855f7', A7: '#ef4444', sentinel: '#f59e0b', curator: '#64748b', }; let state = { selectedAgent: 'A6', selectedIncidentId: null, threadId: null, mobileTab: 'board', pollTimer: null, }; async function agentsApi(path, opts = {}) { const deskApi = typeof globalThis.api === 'function' ? globalThis.api : null; if (deskApi) return deskApi(`/v1/agents${path}`, opts); const h = authHeaders({ ...(opts.headers || {}) }); if (!(opts.body instanceof FormData) && !h['Content-Type']) h['Content-Type'] = 'application/json'; const r = await fetchWithTimeout(`/api/v1/agents${path}`, { ...opts, headers: h }, 60000); if (r.status === 401) { logout(); throw new Error('sessão expirada — faça login novamente'); } if (!r.ok) throw new Error(`${r.status} ${(await r.text()).slice(0, 200)}`); return r.json(); } function fmtAge(iso) { if (!iso) return '—'; try { const ms = Date.now() - new Date(iso).getTime(); const m = Math.floor(ms / 60000); if (m < 1) return 'agora'; if (m < 60) return `há ${m} min`; const h = Math.floor(m / 60); if (h < 24) return `há ${h}h`; return `há ${Math.floor(h / 24)}d`; } catch { return iso; } } function incidentCard(inc) { const active = state.selectedIncidentId === inc.id ? ' ao-incident-card--active' : ''; return `
${esc(inc.title)}
${esc(inc.agent_name)} · ${esc(inc.scenario_id)} · ${fmtAge(inc.last_seen_at)} · ${inc.occurrence_count || 1}×
${esc((inc.suggested_human_action || 'Investigar manualmente.').slice(0, 160))}
`; } function fleetItem(a, openAgents) { const active = state.selectedAgent === a.id ? ' ao-fleet-item--active' : ''; const pulse = openAgents.has(a.id) ? ' ao-fleet-item--pulse' : ''; const dotCls = openAgents.has(a.id) ? ' ao-fleet-dot--active' : ''; const accent = AGENT_ACCENTS[a.id] || '#64748b'; return `
${esc(a.name)} ${esc(a.id)}
`; } function threadBubble(m) { const cls = m.from_type === 'human' ? 'ao-bubble-human' : 'ao-bubble-agent'; return `
${esc(m.from_label || m.from_id)} · ${esc(m.created_at)}
${esc(m.body).replace(/\n/g, '
')}
`; } async function loadThread(el, threadId) { state.threadId = threadId; const box = el.querySelector('#ao-thread-messages'); if (!box || !threadId) return; box.innerHTML = '

Carregando…

'; const data = await agentsApi(`/threads/${threadId}/messages`); box.innerHTML = data.messages.map(threadBubble).join('') || '

Sem mensagens.

'; box.scrollTop = box.scrollHeight; } function bindShell(el) { el.querySelector('#ao-btn-refresh')?.addEventListener('click', () => renderAgenticOps()); el.querySelectorAll('[data-agent-id]').forEach((node) => { node.addEventListener('click', () => { state.selectedAgent = node.dataset.agentId; renderAgenticOps(); }); }); el.querySelectorAll('[data-incident-id], [data-open-incident]').forEach((node) => { node.addEventListener('click', async (ev) => { if (ev.target.closest('[data-ack-incident]')) return; const id = parseInt(node.dataset.incidentId || node.dataset.openIncident, 10); state.selectedIncidentId = id; const card = el.querySelector(`[data-incident-id="${id}"]`); const tid = parseInt(card?.dataset.threadId, 10); if (tid) await loadThread(el, tid); renderAgenticOps(); }); }); el.querySelectorAll('[data-ack-incident]').forEach((btn) => { btn.addEventListener('click', async (ev) => { ev.stopPropagation(); await agentsApi(`/incidents/${btn.dataset.ackIncident}/ack`, { method: 'POST' }); if (state.selectedIncidentId === parseInt(btn.dataset.ackIncident, 10)) { state.selectedIncidentId = null; state.threadId = null; } await renderAgenticOps(); }); }); el.querySelector('#ao-btn-reply')?.addEventListener('click', async () => { const input = el.querySelector('#ao-reply-input'); const tid = state.threadId; const body = (input?.value || '').trim(); if (!tid || !body) return; await agentsApi(`/threads/${tid}/reply`, { method: 'POST', body: JSON.stringify({ body, target_agent: state.selectedAgent }), }); input.value = ''; await loadThread(el, tid); }); el.querySelector('#ao-btn-chat')?.addEventListener('click', async () => { const input = el.querySelector('#ao-chat-input'); const out = el.querySelector('#ao-chat-answer'); const q = (input?.value || '').trim(); if (!q) return; out.hidden = false; out.innerHTML = '

A pensar…

'; try { const res = await agentsApi('/chat', { method: 'POST', body: JSON.stringify({ question: q, include_findings: true, target_agent: state.selectedAgent }), }); out.innerHTML = `

${esc(state.selectedAgent)} (${esc(res.model)})

${esc(res.answer)}

`; if (res.thread_id) { state.threadId = res.thread_id; await loadThread(el, res.thread_id); } } catch (err) { out.innerHTML = `

${esc(err.message)}

`; } }); el.querySelectorAll('.ao-mobile-tabs button').forEach((btn) => { btn.addEventListener('click', () => { state.mobileTab = btn.dataset.aoTab; el.querySelectorAll('.ao-mobile-tabs button').forEach((b) => b.classList.toggle('active', b === btn)); el.querySelectorAll('.ao-pane').forEach((p) => { p.classList.toggle('ao-pane--active', p.dataset.aoPane === state.mobileTab); }); }); }); } function schedulePoll() { if (state.pollTimer) clearInterval(state.pollTimer); state.pollTimer = setInterval(() => { if (document.hidden) return; const view = document.getElementById('view-agentic-ops'); if (view && !view.hidden) renderAgenticOps({ poll: true }); }, 30000); } async function renderAgenticOps(options = {}) { const el = document.getElementById('agentic-ops-content'); if (!el) return; if (!options.poll) { el.innerHTML = '

Carregando Mission Board…

' + '
'; } if (!getToken()) { el.innerHTML = '

Sessão não encontrada. Fazer login

'; return; } if (!options.poll && typeof ensureValidSession === 'function') { const ok = await ensureValidSession(); if (!ok) { el.innerHTML = '

Sessão expirada. Fazer login

'; return; } } try { const [overview, incidents, roster, timeline] = await Promise.all([ agentsApi('/overview'), agentsApi('/incidents?status=open&limit=50'), agentsApi('/roster'), agentsApi('/timeline?limit=12'), ]); const list = incidents.incidents || []; const openAgents = new Set(list.map((i) => i.primary_agent)); const filtered = state.selectedAgent && state.selectedAgent !== 'ALL' ? list.filter((i) => i.primary_agent === state.selectedAgent) : list; const bySev = { critical: [], high: [], warn: [], info: [] }; filtered.forEach((inc) => { const k = bySev[inc.severity] ? inc.severity : 'warn'; (bySev[k] || bySev.warn).push(inc); }); const sel = list.find((i) => i.id === state.selectedIncidentId); const ollamaPill = overview.ollama ? `Ollama · ${esc(overview.model)}` : 'Ollama offline'; const tier = overview.tier === 't1' ? 'T1 LLM' : 'T0'; const open = overview.incidents_open || {}; const boardCols = SEV_COLS.map((col) => { const cards = (bySev[col.key] || []).map(incidentCard).join('') || '

'; return `

${col.label}

${cards}
`; }).join(''); const fleet = (roster.agents || []).map((a) => fleetItem(a, openAgents)).join(''); const ticks = (timeline.ticks || []).map((t) => `
${esc(fmtAge(t.at))}${t.scenarios || '—'} cenários · ${t.findings || 0} findings
` ).join('') || '

Sem ticks recentes.

'; el.innerHTML = `

Agentic Ops

${tier} ${ollamaPill} Último tick: ${fmtAge(overview.last_tick_at)} ${overview.scenarios_ok || 0}/${overview.scenarios_total || 9} cenários OK Abertos: ${open.high || 0} alto · ${open.warn || 0} aviso
${filtered.length ? boardCols : '

✓ Ambiente saudável — nenhum incidente aberto.

'}

Timeline ticks (24h)

${ticks}
`; bindShell(el); if (state.threadId) await loadThread(el, state.threadId); else if (sel?.thread_id) await loadThread(el, sel.thread_id); if (!state.pollTimer) schedulePoll(); } catch (err) { if (!options.poll) el.innerHTML = `

Erro: ${esc(err.message)}

`; } } document.addEventListener('keydown', (ev) => { if (ev.key === 'r' && !ev.ctrlKey && !ev.metaKey && document.getElementById('view-agentic-ops') && !document.getElementById('view-agentic-ops').hidden) { const t = ev.target; if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA')) return; ev.preventDefault(); renderAgenticOps(); } if (ev.key === 'Escape') { state.selectedIncidentId = null; state.threadId = null; renderAgenticOps(); } }); window.renderAgenticOps = renderAgenticOps; })();