(function () {
  const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  const SEV_COLS = [
    { key: 'critical', label: 'Crítico', cls: 'ao-kanban-col--critical' },
    { key: 'high', label: 'Alto', cls: 'ao-kanban-col--high' },
    { key: 'warn', label: 'Aviso', cls: 'ao-kanban-col--warn' },
    { key: 'info', label: 'Info / OK', cls: 'ao-kanban-col--info' },
  ];
  const AGENT_ICONS = {
    A0: '🎯', A1: '💓', A2: '🛤', A3: '✉', A4: '🛡', A5: '👁',
    A6: '🤖', A7: '🔧', sentinel: '📡', curator: '📚',
  };
  const AGENT_COLORS = {
    A0: '#6366f1', A1: '#22c55e', A2: '#3b82f6', A3: '#06b6d4', A4: '#8b5cf6',
    A5: '#ec4899', A6: '#a855f7', A7: '#ef4444', sentinel: '#f59e0b', curator: '#64748b',
  };
  const SUB_VIEWS = [
    { id: 'command', label: 'Squad Command', icon: '🎯' },
    { id: 'agents', label: 'Agent Management', icon: '👥' },
    { id: 'tasks', label: 'Task Board', icon: '📋' },
    { id: 'memory', label: 'Knowledge Graph', icon: '🧠' },
    { id: 'recurring', label: 'Recurring Tasks', icon: '🔄' },
    { id: 'activity', label: 'Activity Feed', icon: '📡' },
  ];

  let state = {
    selectedAgent: 'A6',
    selectedIncidentId: null,
    threadId: null,
    mobileTab: 'board',
    mainTab: 'missions',
    subView: 'command',
    chatModalOpen: false,
    kbQuery: '',
    lastActions: [],
    pollTimer: null,
  };
  let cache = null;
  let extraCache = { scenarios: null, activity: null, kbSources: null, kbHits: null };
  let shellMounted = false;
  let eventsBound = false;
  let renderInFlight = false;
  let pendingRender = null;
  let lastThreadHtml = '';
  let chatInFlight = false;

  async function agentsApi(path, opts = {}) {
    const h = authHeaders({ ...(opts.headers || {}) });
    if (!(opts.body instanceof FormData) && !h['Content-Type']) h['Content-Type'] = 'application/json';
    const isSlow = /\/chat$/.test(path) || path.includes('/chat');
    const timeoutMs = isSlow ? 180000 : 60000;
    const r = await fetchWithTimeout(`/api/v1/agents${path}`, { ...opts, headers: h }, timeoutMs);
    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 escHtml(s) {
    return esc(s);
  }

  function renderMarkdown(text) {
    if (!text) return '';
    const lines = String(text).split('\n');
    const out = [];
    let inList = false;
    let inTable = false;
    const flushList = () => {
      if (inList) { out.push('</ul>'); inList = false; }
    };
    const flushTable = () => {
      if (inTable) { out.push('</tbody></table>'); inTable = false; }
    };
    for (let i = 0; i < lines.length; i++) {
      const raw = lines[i];
      const line = raw.trimEnd();
      if (/^\|.+\|$/.test(line.trim())) {
        flushList();
        const cells = line.trim().slice(1, -1).split('|').map((c) => c.trim());
        if (/^[-:\s|]+$/.test(line.replace(/\|/g, ''))) continue;
        if (!inTable) {
          out.push('<table class="ao-md-table"><thead><tr>');
          cells.forEach((c) => out.push(`<th>${inlineMd(c)}</th>`));
          out.push('</tr></thead><tbody>');
          inTable = true;
        } else {
          out.push('<tr>');
          cells.forEach((c) => out.push(`<td>${inlineMd(c)}</td>`));
          out.push('</tr>');
        }
        continue;
      }
      flushTable();
      if (/^---+$/.test(line.trim())) {
        flushList();
        out.push('<hr class="ao-md-hr">');
      } else if (/^### (.+)/.test(line)) {
        flushList();
        out.push(`<h4 class="ao-md-h">${inlineMd(line.replace(/^### /, ''))}</h4>`);
      } else if (/^## (.+)/.test(line)) {
        flushList();
        out.push(`<h3 class="ao-md-h">${inlineMd(line.replace(/^## /, ''))}</h3>`);
      } else if (/^[-*] (.+)/.test(line)) {
        if (!inList) { out.push('<ul class="ao-md-ul">'); inList = true; }
        out.push(`<li>${inlineMd(line.replace(/^[-*] /, ''))}</li>`);
      } else if (/^\d+\. (.+)/.test(line)) {
        if (!inList) { out.push('<ol class="ao-md-ul">'); inList = true; }
        out.push(`<li>${inlineMd(line.replace(/^\d+\. /, ''))}</li>`);
      } else if (!line.trim()) {
        flushList();
        out.push('');
      } else {
        flushList();
        out.push(`<p class="ao-md-p">${inlineMd(line)}</p>`);
      }
    }
    flushList();
    flushTable();
    return out.join('');
  }

  function inlineMd(s) {
    return escHtml(s)
      .replace(/`([^`]+)`/g, '<code class="ao-md-code">$1</code>')
      .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
  }

  function fmtMsgTime(iso) {
    if (!iso) return 'agora';
    try {
      const d = new Date(iso);
      const diff = Date.now() - d.getTime();
      if (diff < 60000) return 'agora';
      return d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
    } catch {
      return iso;
    }
  }

  function chatErrorMessage(err) {
    const msg = String(err?.message || err || '');
    if (msg.includes('429') || msg.toLowerCase().includes('quota') || msg.toLowerCase().includes('balance')) {
      return 'Conta KIMI sem saldo — recarregue em platform.moonshot.ai';
    }
    if (err?.name === 'AbortError' || msg.includes('aborted')) {
      return 'Tempo esgotado — tente novamente ou verifique Groq/Ollama.';
    }
    return msg || 'Erro ao enviar mensagem';
  }

  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 liveDotClass(open) {
    if ((open.critical || 0) > 0 || (open.high || 0) > 0) return 'ao-live-dot--alert';
    if ((open.warn || 0) > 0) return 'ao-live-dot--warn';
    return '';
  }

  function chatTargetLabel() {
    if (state.selectedAgent === 'ALL') return 'A6 Copiloto';
    const agents = cache?.roster?.agents || [];
    const a = agents.find((x) => x.id === state.selectedAgent);
    const icon = AGENT_ICONS[state.selectedAgent] || '🤖';
    return a ? `${a.name} (${state.selectedAgent}) ${icon}` : `${state.selectedAgent} ${icon}`;
  }

  function chatAgentId() {
    return state.selectedAgent === 'ALL' ? 'A6' : state.selectedAgent;
  }

  function msgBoxEl(root) {
    if (!root) return null;
    return root.querySelector(state.chatModalOpen ? '#ao-modal-thread-messages' : '#ao-thread-messages');
  }

  function chatInputEl(root) {
    if (!root) return null;
    return root.querySelector(state.chatModalOpen ? '#ao-modal-chat-input' : '#ao-chat-input');
  }

  function sendBtnEl(root) {
    if (!root) return null;
    return root.querySelector(state.chatModalOpen ? '#ao-modal-btn-send' : '#ao-btn-send');
  }

  function groupThreads(threads) {
    const waiting = [];
    const open = [];
    threads.forEach((t) => {
      if (t.status !== 'open') return;
      if (t.pending_human > 0) waiting.push(t);
      else open.push(t);
    });
    return { waiting, open };
  }

  function taskCardHtml(t) {
    const active = state.threadId === t.id ? ' ao-task-card--active' : '';
    const icon = AGENT_ICONS[t.primary_agent] || '🤖';
    const badge = t.pending_human > 0 ? `<span class="ao-task-badge">aguarda você</span>` : '';
    return `<article class="ao-task-card${active}" data-thread-id="${t.id}" data-task-agent="${esc(t.primary_agent)}">
      <div class="ao-task-card-head">
        <span class="ao-task-card-icon">${icon}</span>
        <div class="ao-task-card-title">${esc(t.subject)}</div>
      </div>
      <div class="ao-task-card-meta">${esc(t.agent_name || t.primary_agent)} · ${fmtAge(t.updated_at)} · #${t.id}</div>
      ${badge}
    </article>`;
  }

  function agentPickHtml(agents) {
    const picks = agents.filter((a) => !['curator', 'sentinel'].includes(a.id)).slice(0, 8);
    return picks.map((a) => {
      const active = state.selectedAgent === a.id ? ' ao-agent-pick--active' : '';
      const icon = AGENT_ICONS[a.id] || '🤖';
      return `<button type="button" class="ao-agent-pick${active}" data-chat-agent="${esc(a.id)}">${icon} ${esc(a.name)}</button>`;
    }).join('');
  }

  function taskBoardHtml(data) {
    const threads = data.threads?.threads || [];
    const { waiting, open } = groupThreads(threads);
    const agents = (data.roster?.agents || []).filter((a) => a.id !== 'curator');
    const waitCards = waiting.map(taskCardHtml).join('')
      || '<p class="ticket-meta" style="font-size:0.7rem;opacity:0.5;text-align:center">—</p>';
    const openCards = open.slice(0, 12).map(taskCardHtml).join('')
      || '<p class="ticket-meta" style="font-size:0.7rem;opacity:0.5;text-align:center">Nenhuma conversa activa</p>';
    return `
<div class="ao-task-board ao-task-board--full">
  <div class="ao-task-col ao-task-col--wait"><h4>Aguardando você</h4>${waitCards}</div>
  <div class="ao-task-col ao-task-col--open"><h4>Conversas abertas</h4>${openCards}</div>
  <div class="ao-task-col ao-task-col--new">
    <h4>Nova conversa</h4>
    <div class="ao-new-chat-box">
      <p><strong>Como conversar:</strong> escolha um agente, clique <strong>Abrir chat</strong> ou duplo-clique no Squad. O chat abre em modal grande.</p>
      <div class="ao-agent-picks" id="ao-agent-picks">${agentPickHtml(agents)}</div>
      <button type="button" class="ao-btn-mission" id="ao-btn-new-chat" style="width:100%">💬 Abrir chat com ${esc(chatAgentId())}</button>
    </div>
  </div>
</div>`;
  }

  function deriveView(data) {
    const list = data.incidents.incidents || [];
    const openAgents = new Set(list.map((i) => i.primary_agent));
    const counts = {};
    list.forEach((i) => { counts[i.primary_agent] = (counts[i.primary_agent] || 0) + 1; });
    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) || null;
    return { list, openAgents, counts, filtered, bySev, sel };
  }

  function incidentsFingerprint(data) {
    return (data.incidents?.incidents || [])
      .map((i) => `${i.id}:${i.status}:${i.severity}:${i.last_seen_at || ''}`)
      .join('|');
  }

  function timelineFingerprint(timeline) {
    return JSON.stringify(timeline?.ticks || []);
  }

  function agentStatus(id, openAgents) {
    if (openAgents.has(id)) return { cls: 'ao-squad-status--alert', label: 'alerta' };
    if (state.selectedAgent === id) return { cls: 'ao-squad-status--active', label: 'activo' };
    return { cls: 'ao-squad-status--standby', label: 'standby' };
  }

  function squadCardHtml(a, openAgents, counts) {
    const selected = state.selectedAgent === a.id ? ' ao-squad-card--selected' : '';
    const alert = openAgents.has(a.id) ? ' ao-squad-card--alert' : '';
    const st = agentStatus(a.id, openAgents);
    const count = counts[a.id] || 0;
    const badge = count > 0 ? `<span class="ao-squad-badge">${count}</span>` : '';
    const color = AGENT_COLORS[a.id] || '#64748b';
    const icon = AGENT_ICONS[a.id] || '🤖';
    return `<div class="ao-squad-card${selected}${alert}" data-agent-id="${esc(a.id)}" role="button" tabindex="0">
      ${badge}
      <div class="ao-avatar" style="background:linear-gradient(135deg,${color}33,${color}11);border-color:${color}55">
        <span class="ao-avatar-ring"></span>${icon}
      </div>
      <div class="ao-squad-name">${esc(a.name)}</div>
      <div class="ao-squad-id">${esc(a.id)} · ${esc(a.codename)}</div>
      <span class="ao-squad-status ${st.cls}">${st.label}</span>
      <button type="button" class="ao-squad-chat-btn" data-open-chat="${esc(a.id)}" title="Abrir chat">💬</button>
    </div>`;
  }

  function squadHtml(data, view) {
    const squadAgents = (data.roster.agents || []).filter((a) => a.id !== 'curator');
    const allCard = `<div class="ao-squad-card${state.selectedAgent === 'ALL' ? ' ao-squad-card--selected' : ''}" data-agent-id="ALL" role="button">
      <div class="ao-avatar" style="background:linear-gradient(135deg,#6366f133,#8b5cf611);border-color:#6366f155">🌐</div>
      <div class="ao-squad-name">Todos</div>
      <div class="ao-squad-id">visão global</div>
      <span class="ao-squad-status ao-squad-status--standby">${view.list.length} abertos</span>
    </div>`;
    return allCard + squadAgents.map((a) => squadCardHtml(a, view.openAgents, view.counts)).join('');
  }

  function missionCardHtml(inc) {
    const active = state.selectedIncidentId === inc.id ? ' ao-mission-card--active' : '';
    const sevCls = ` ao-mission-card--${inc.severity || 'warn'}`;
    const icon = AGENT_ICONS[inc.primary_agent] || '🤖';
    return `<article class="ao-mission-card${active}${sevCls}" data-incident-id="${inc.id}" data-thread-id="${inc.thread_id || ''}">
      <div class="ao-mission-card-title">${icon} ${esc(inc.title)}</div>
      <div class="ao-mission-card-meta">${esc(inc.agent_name)} · ${esc(inc.scenario_id)} · ${fmtAge(inc.last_seen_at)} · ${inc.occurrence_count || 1}×</div>
      <div class="ao-mission-card-action">${esc((inc.suggested_human_action || 'Investigar manualmente.').slice(0, 160))}</div>
      <div class="ao-mission-card-btns">
        <button type="button" data-open-incident="${inc.id}">Abrir</button>
        <button type="button" data-ack-incident="${inc.id}">Ack</button>
      </div>
    </article>`;
  }

  function kanbanHtml(view) {
    if (!view.filtered.length) {
      return '<div class="ao-empty-state"><div class="ao-empty-icon">✓</div><p>Ambiente saudável — nenhum incidente aberto.</p></div>';
    }
    return SEV_COLS.map((col) => {
      const cards = (view.bySev[col.key] || []).map(missionCardHtml).join('')
        || '<p class="ticket-meta" style="font-size:0.7rem;opacity:0.5;text-align:center">—</p>';
      return `<div class="ao-kanban-col ${col.cls}"><h4>${col.label}</h4>${cards}</div>`;
    }).join('');
  }

  function chipsHtml(overview, open) {
    const tier = overview.tier === 't1' ? 'T1 LLM' : 'T0 Checks';
    const provider = overview.provider || 'ollama';
    const model = overview.model || '—';
    let llmChip;
    if (provider === 'groq') {
      llmChip = `<span class="ao-chip ao-chip--ok">Groq · ${esc(model)}</span>`;
    } else if (provider === 'kimi') {
      llmChip = `<span class="ao-chip ao-chip--ok">KIMI · ${esc(model)}</span>`;
    } else if (overview.ollama) {
      llmChip = `<span class="ao-chip ao-chip--ok">Ollama · ${esc(model)}</span>`;
    } else {
      llmChip = '<span class="ao-chip ao-chip--warn">LLM offline</span>';
    }
    const warnChip = (open.high || open.critical) ? ' ao-chip--warn' : '';
    return `
      <span class="ao-chip ao-chip--tier">${esc(tier)}</span>
      ${llmChip}
      <span class="ao-chip">Tick ${fmtAge(overview.last_tick_at)}</span>
      <span class="ao-chip ao-chip--ok">${overview.scenarios_ok || 0}/${overview.scenarios_total || 9} cenários OK</span>
      <span class="ao-chip${warnChip}">${open.critical || 0} crít · ${open.high || 0} alto · ${open.warn || 0} aviso</span>`;
  }

  function timelineHtml(timeline) {
    const ticks = timeline.ticks || [];
    if (!ticks.length) return '<p class="ticket-meta">Sem ticks recentes.</p>';
    return ticks.map((t) =>
      `<div class="ao-timeline-row"><span>${esc(fmtAge(t.at))}</span><span>${t.scenarios || '—'} cenários · ${t.findings || 0} findings</span></div>`
    ).join('');
  }

  function commsContextHtml(sel) {
    if (!sel) return 'Seleccione um incidente no board ou fale com um agente.';
    return `<strong>${esc(sel.title)}</strong><br>${esc(sel.scenario_id)} · ${esc(sel.agent_name)}`;
  }

  function msgContext(m) {
    if (m.context) return m.context;
    if (m.context_json) {
      try { return JSON.parse(m.context_json); } catch { return {}; }
    }
    return {};
  }

  function actionsHtml(actions) {
    if (!actions?.length) return '';
    const btns = actions.map((a) => {
      if (a.type === 'run_scenario') {
        const sev = a.severity || 'info';
        return `<button type="button" class="ao-action-btn ao-action-btn--run ao-action-btn--${esc(sev)}" data-run-scenario="${esc(a.scenario_id)}">${esc(a.label)}</button>`;
      }
      if (a.type === 'ack_incident') {
        return `<button type="button" class="ao-action-btn ao-action-btn--ack" data-ack-incident-id="${a.incident_id}">${esc(a.label)}</button>`;
      }
      return '';
    }).join('');
    return `<div class="ao-action-bar"><span class="ao-action-bar-label">Acções rápidas</span>${btns}</div>`;
  }

  function threadBubble(m) {
    const cls = m.from_type === 'human' ? 'ao-bubble-human' : 'ao-bubble-agent';
    const ctx = msgContext(m);
    const body = m.from_type === 'human'
      ? esc(m.body).replace(/\n/g, '<br>')
      : renderMarkdown(m.body);
    const model = ctx.model ? ` · ${esc(ctx.model)}` : '';
    const actions = m.from_type === 'agent' ? actionsHtml(ctx.actions) : '';
    return `<div class="ao-bubble ${cls}">
      <div class="ao-bubble-meta">${esc(m.from_label || m.from_id)} · ${esc(fmtMsgTime(m.created_at))}${model}</div>
      <div class="ao-bubble-body">${body}</div>
      ${actions}
    </div>`;
  }

  function typingBubbleHtml(agentLabel) {
    return `<div class="ao-bubble ao-bubble-agent ao-bubble--typing" id="ao-typing-bubble">
      <div class="ao-bubble-meta">${esc(agentLabel)} · a escrever<span class="ao-typing-dots"><span>.</span><span>.</span><span>.</span></span></div>
      <div class="ao-bubble-body ao-stream-body"></div>
    </div>`;
  }

  function subnavHtml() {
    return SUB_VIEWS.map((v) =>
      `<button type="button" class="ao-subnav-btn${state.subView === v.id ? ' active' : ''}" data-sub-view="${v.id}">${v.icon} ${esc(v.label)}</button>`
    ).join('');
  }

  function agentManagementHtml(data) {
    const agents = (data.roster?.agents || []).filter((a) => a.id !== 'curator');
    const cards = agents.map((a) => {
      const icon = AGENT_ICONS[a.id] || '🤖';
      const color = AGENT_COLORS[a.id] || '#64748b';
      const reads = (a.reads || []).slice(0, 3).map((r) => `<li>${esc(r)}</li>`).join('');
      const actions = (a.actions || []).slice(0, 2).map((r) => `<li>${esc(r)}</li>`).join('');
      const scenarios = (a.scenarios || []).length
        ? a.scenarios.map((s) => `<span class="ao-chip">${esc(s)}</span>`).join(' ')
        : '<span class="ticket-meta">—</span>';
      return `<article class="ao-agent-mgmt-card" data-agent-id="${esc(a.id)}">
        <div class="ao-agent-mgmt-head">
          <div class="ao-avatar ao-avatar--lg" style="background:linear-gradient(135deg,${color}33,${color}11);border-color:${color}55">${icon}</div>
          <div>
            <h3>${esc(a.name)} <span class="ao-squad-id">${esc(a.id)}</span></h3>
            <p class="ao-agent-mgmt-role">${esc(a.role)}</p>
            <p class="ticket-meta">Aprovação: ${esc(a.approval)}</p>
          </div>
        </div>
        <div class="ao-agent-mgmt-grid">
          <div><strong>Lê</strong><ul class="ao-agent-mgmt-list">${reads}</ul></div>
          <div><strong>Actua</strong><ul class="ao-agent-mgmt-list">${actions}</ul></div>
        </div>
        <div class="ao-agent-mgmt-scenarios"><strong>Cenários:</strong> ${scenarios}</div>
        <button type="button" class="ao-btn-mission ao-btn-chat-agent" data-open-chat="${esc(a.id)}">💬 Conversar com ${esc(a.name)}</button>
      </article>`;
    }).join('');
    return `<div class="ao-screen-head"><h3>Agent Management</h3><p class="ticket-meta">Roster A0–A7 + Vigia · clique Conversar para abrir chat em modal</p></div><div class="ao-agent-mgmt-grid-wrap">${cards}</div>`;
  }

  function knowledgeGraphHtml() {
    const sources = extraCache.kbSources?.sources || [];
    const total = extraCache.kbSources?.total_chunks || 0;
    const hits = extraCache.kbHits?.hits || [];
    const sourceNodes = sources.map((s) => {
      const short = String(s.source_path || '').split('/').slice(-2).join('/');
      return `<div class="ao-kg-node" data-kb-source="${esc(s.source_path)}">
        <div class="ao-kg-node-title">📄 ${esc(short)}</div>
        <div class="ao-kg-node-meta">${s.chunks} chunks · ${fmtAge(s.indexed_at)}</div>
      </div>`;
    }).join('') || '<p class="ticket-meta">Nenhum documento indexado — aguardar tick do Curador.</p>';
    const hitCards = hits.map((h) =>
      `<article class="ao-kg-hit"><div class="ao-kg-hit-src">${esc(h.source)}</div><div class="ao-kg-hit-snippet">${esc(h.snippet)}</div></article>`
    ).join('') || '<p class="ticket-meta">Pesquise specs, runbooks ou infra…</p>';
    return `
<div class="ao-screen-head"><h3>Memory · Knowledge Graph</h3><p class="ticket-meta">${total} chunks indexados · RAG alimenta Copiloto e agentes</p></div>
<div class="ao-kg-search">
  <input type="search" id="ao-kb-search" placeholder="Pesquisar base de conhecimento…" value="${esc(state.kbQuery)}"/>
  <button type="button" class="ao-btn-mission" id="ao-kb-search-btn">Pesquisar</button>
</div>
<div class="ao-kg-layout">
  <div class="ao-kg-sources"><p class="ao-section-label">Fontes indexadas</p><div class="ao-kg-graph">${sourceNodes}</div></div>
  <div class="ao-kg-results"><p class="ao-section-label">Resultados</p>${hitCards}</div>
</div>`;
  }

  function recurringTasksHtml() {
    const scenarios = extraCache.scenarios?.scenarios || [];
    if (!scenarios.length) {
      return '<div class="ao-screen-head"><h3>Natural Language Recurring Tasks</h3><p class="ticket-meta">A carregar cenários…</p></div>';
    }
    const rows = scenarios.map((s) => {
      const statusCls = s.last_run_status === 'ok' ? 'ao-chip--ok' : (s.last_run_status === 'fail' ? 'ao-chip--warn' : '');
      const enabled = s.enabled ? 'Activo' : 'Pausado';
      return `<article class="ao-recurring-card">
        <div class="ao-recurring-head">
          <h4>${esc(s.title || s.id)}</h4>
          <span class="ao-chip ${statusCls}">${esc(s.last_run_status || '—')}</span>
        </div>
        <div class="ao-recurring-meta">
          <span class="ao-chip ao-chip--tier">${esc(s.id)}</span>
          <span class="ao-chip">${esc(s.schedule || 'cron')}</span>
          <span class="ao-chip">${enabled}</span>
          <span class="ao-chip">Severidade ${esc(s.severity_default || 'warn')}</span>
        </div>
        <p class="ticket-meta">Última execução: ${fmtAge(s.last_run_at)} · ${s.last_run_message ? esc(String(s.last_run_message).slice(0, 120)) : '—'}</p>
      </article>`;
    }).join('');
    return `<div class="ao-screen-head"><h3>Natural Language Recurring Tasks</h3><p class="ticket-meta">Cenários T0 vigilância 24/7 — linguagem natural descreve o check, worker executa no schedule</p></div><div class="ao-recurring-list">${rows}</div>`;
  }

  function activityFeedHtml() {
    const events = extraCache.activity?.events || [];
    if (!events.length) {
      return '<div class="ao-screen-head"><h3>Activity Feed</h3><p class="ticket-meta">A carregar eventos…</p></div>';
    }
    const rows = events.map((e) => {
      const payload = e.payload_json ? (() => { try { return JSON.parse(e.payload_json); } catch { return {}; } })() : {};
      const agent = e.agent_id ? `<span class="ao-chip">${esc(e.agent_id)}</span>` : '';
      return `<article class="ao-activity-row">
        <div class="ao-activity-time">${esc(fmtAge(e.ts))}</div>
        <div class="ao-activity-body">
          <div class="ao-activity-type">${esc(e.event_type)} ${agent}</div>
          <div class="ao-activity-msg">${esc(e.message || '')}</div>
          ${payload.model ? `<div class="ticket-meta">model: ${esc(payload.model)}</div>` : ''}
        </div>
      </article>`;
    }).join('');
    return `<div class="ao-screen-head"><h3>Activity Feed</h3><p class="ticket-meta">Últimos eventos agentic — ticks, chat, acks, runs</p></div><div class="ao-activity-feed">${rows}</div>`;
  }

  function patchHtml(node, html) {
    if (!node || node.innerHTML === html) return false;
    node.innerHTML = html;
    return true;
  }

  function chatModalHtml() {
    return `
<div class="ao-modal" id="ao-chat-modal" aria-hidden="true">
  <div class="ao-modal-backdrop" data-close-modal></div>
  <div class="ao-modal-dialog" role="dialog" aria-labelledby="ao-modal-title">
    <header class="ao-modal-header">
      <div class="ao-modal-agent" id="ao-modal-title">
        <span class="ao-modal-icon" id="ao-modal-icon">🤖</span>
        <div>
          <div class="ao-modal-name" id="ao-modal-name">Copiloto</div>
          <div class="ao-modal-sub" id="ao-modal-sub">Thread: nenhuma</div>
        </div>
      </div>
      <button type="button" class="ao-modal-close" id="ao-modal-close" aria-label="Fechar">×</button>
    </header>
    <div class="ao-modal-context" id="ao-modal-context"></div>
    <div class="ao-modal-feed" id="ao-modal-thread-messages"><p class="ticket-meta">Nova conversa — escreva abaixo.</p></div>
    <div class="ao-modal-composer">
      <label for="ao-modal-chat-input" id="ao-modal-chat-label">Mensagem</label>
      <textarea id="ao-modal-chat-input" rows="4" placeholder="Pergunte ao agente…"></textarea>
      <div class="ao-modal-actions">
        <button type="button" class="ao-btn-send" id="ao-modal-btn-send">Enviar mensagem</button>
      </div>
    </div>
  </div>
</div>`;
  }

  function mountShell(el) {
    el.innerHTML = `
<div class="ao-console">
  <header class="ao-mission-header">
    <div>
      <div class="ao-mission-title">
        <span class="ao-live-dot" id="ao-live-dot"></span>
        <h2>Agent Squad</h2>
      </div>
      <div class="ao-stat-chips" id="ao-stat-chips"></div>
    </div>
    <div class="ao-header-actions">
      <button type="button" class="ao-btn-mission ao-btn-chat-fab" id="ao-btn-open-chat">💬 Chat · ${esc(chatAgentId())}</button>
      <button type="button" class="ao-btn-mission" id="ao-btn-refresh">↻ Actualizar (R)</button>
    </div>
  </header>
  <nav class="ao-subnav" id="ao-subnav">${subnavHtml()}</nav>

  <div class="ao-subview ao-subview--active" data-subview="command">
    <div class="ao-mobile-tabs" id="ao-mobile-tabs">
      <button type="button" data-ao-tab="squad">Squad</button>
      <button type="button" data-ao-tab="board">Board</button>
      <button type="button" data-ao-tab="tasks">Tasks</button>
    </div>
    <section class="ao-pane" data-ao-pane="squad">
      <p class="ao-section-label">Agent Squad — clique filtra · duplo-clique ou 💬 abre chat modal</p>
      <div class="ao-squad-grid" id="ao-squad-grid"></div>
    </section>
    <div class="ao-main ao-main--full">
      <section class="ao-pane ao-pane--active" data-ao-pane="board">
        <div class="ao-content-tabs" id="ao-content-tabs">
          <button type="button" data-main-tab="missions">Mission Board</button>
          <button type="button" data-main-tab="tasks">Task Board</button>
        </div>
        <div class="ao-content-pane ao-content-pane--active" data-main-pane="missions">
          <p class="ao-section-label" id="ao-board-label">Mission Board</p>
          <div class="ao-kanban" id="ao-kanban"></div>
          <div class="ao-timeline-strip">
            <p class="ao-section-label" style="margin-bottom:0.4rem">Timeline · últimas 24h</p>
            <div id="ao-timeline"></div>
          </div>
        </div>
        <div class="ao-content-pane" data-main-pane="tasks">
          <p class="ao-section-label">Task Board — conversas com agentes</p>
          <div id="ao-task-board"></div>
        </div>
      </section>
    </div>
    <aside class="ao-comms-compact" id="ao-comms-compact">
      <p><strong>Comms</strong> — chat em modal grande. Duplo-clique num agente ou use o botão 💬 Chat.</p>
      <button type="button" class="ao-btn-mission" id="ao-btn-open-chat-inline">Abrir chat com ${esc(chatAgentId())}</button>
    </aside>
  </div>

  <div class="ao-subview" data-subview="agents" id="ao-pane-agents"></div>
  <div class="ao-subview" data-subview="tasks" id="ao-pane-tasks"></div>
  <div class="ao-subview" data-subview="memory" id="ao-pane-memory"></div>
  <div class="ao-subview" data-subview="recurring" id="ao-pane-recurring"></div>
  <div class="ao-subview" data-subview="activity" id="ao-pane-activity"></div>

  ${chatModalHtml()}
</div>`;
    shellMounted = true;
  }

  function updateSubViews(el) {
    el.querySelectorAll('.ao-subnav-btn').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.subView === state.subView);
    });
    el.querySelectorAll('.ao-subview').forEach((pane) => {
      pane.classList.toggle('ao-subview--active', pane.dataset.subview === state.subView);
    });
  }

  function updateMainTabs(el) {
    el.querySelectorAll('[data-main-tab]').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.mainTab === state.mainTab);
    });
    el.querySelectorAll('[data-main-pane]').forEach((pane) => {
      pane.classList.toggle('ao-content-pane--active', pane.dataset.mainPane === state.mainTab);
    });
  }

  function updateModalChrome(el, sel) {
    const modal = el.querySelector('#ao-chat-modal');
    if (!modal) return;
    const agentId = chatAgentId();
    const icon = AGENT_ICONS[agentId] || '🤖';
    const iconEl = modal.querySelector('#ao-modal-icon');
    const nameEl = modal.querySelector('#ao-modal-name');
    const subEl = modal.querySelector('#ao-modal-sub');
    const ctxEl = modal.querySelector('#ao-modal-context');
    const labelEl = modal.querySelector('#ao-modal-chat-label');
    if (iconEl) iconEl.textContent = icon;
    if (nameEl) nameEl.textContent = chatTargetLabel();
    if (subEl) subEl.textContent = state.threadId ? `Thread #${state.threadId}` : 'Nova conversa';
    if (ctxEl) ctxEl.innerHTML = commsContextHtml(sel);
    if (labelEl) labelEl.textContent = `Mensagem para ${chatTargetLabel()}`;
    modal.classList.toggle('ao-modal--open', state.chatModalOpen);
    modal.setAttribute('aria-hidden', state.chatModalOpen ? 'false' : 'true');
    document.body.classList.toggle('ao-modal-open', state.chatModalOpen);
  }

  function updateCommsChrome(el, sel) {
    const fab = el.querySelector('#ao-btn-open-chat');
    const inline = el.querySelector('#ao-btn-open-chat-inline');
    const label = `💬 Chat · ${chatAgentId()}`;
    if (fab) fab.textContent = label;
    if (inline) inline.textContent = `Abrir chat com ${chatAgentId()}`;
    updateModalChrome(el, sel);
  }

  function updateMobileTabs(el) {
    if (state.subView !== 'command') return;
    el.querySelectorAll('.ao-mobile-tabs button').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.aoTab === state.mobileTab);
    });
    el.querySelectorAll('[data-subview="command"] .ao-pane').forEach((pane) => {
      const paneName = pane.dataset.aoPane;
      const active = paneName === state.mobileTab
        || (state.mobileTab === 'tasks' && paneName === 'board');
      pane.classList.toggle('ao-pane--active', active);
    });
  }

  function patchSubViewPanes(el, data) {
    patchHtml(el.querySelector('#ao-pane-agents'), agentManagementHtml(data));
    patchHtml(el.querySelector('#ao-pane-tasks'), taskBoardHtml(data));
    patchHtml(el.querySelector('#ao-pane-memory'), knowledgeGraphHtml());
    patchHtml(el.querySelector('#ao-pane-recurring'), recurringTasksHtml());
    patchHtml(el.querySelector('#ao-pane-activity'), activityFeedHtml());
  }

  function patchDashboard(el, data, opts = {}) {
    const { soft = false } = opts;
    const view = deriveView(data);
    const open = data.overview.incidents_open || {};
    const dot = el.querySelector('#ao-live-dot');
    if (dot) dot.className = `ao-live-dot ${liveDotClass(open)}`.trim();

    patchHtml(el.querySelector('#ao-stat-chips'), chipsHtml(data.overview, open));

    const incFp = incidentsFingerprint(data);
    const tickFp = timelineFingerprint(data.timeline);
    const boardChanged = !soft || incFp !== state._aoIncidentsFp;
    const timelineChanged = !soft || tickFp !== state._aoTimelineFp;

    if (boardChanged) {
      patchHtml(el.querySelector('#ao-subnav'), subnavHtml());
      patchHtml(el.querySelector('#ao-squad-grid'), squadHtml(data, view));
      patchHtml(el.querySelector('#ao-kanban'), kanbanHtml(view));
      state._aoIncidentsFp = incFp;
    }

    if (timelineChanged) {
      patchHtml(el.querySelector('#ao-timeline'), timelineHtml(data.timeline));
      state._aoTimelineFp = tickFp;
    }

    if (!soft) {
      patchHtml(el.querySelector('#ao-task-board'), taskBoardHtml(data));
      patchSubViewPanes(el, data);
    }

    const boardLabel = el.querySelector('#ao-board-label');
    if (boardLabel) {
      boardLabel.textContent = state.selectedAgent !== 'ALL'
        ? `Mission Board · filtro ${state.selectedAgent}`
        : 'Mission Board';
    }

    if (!soft) {
      updateSubViews(el);
    }
    updateMainTabs(el);
    updateCommsChrome(el, view.sel);
    updateMobileTabs(el);
    return view;
  }

  function openChatModal(root, agentId) {
    if (agentId && agentId !== 'ALL') state.selectedAgent = agentId;
    state.chatModalOpen = true;
    const view = cache ? deriveView(cache) : { sel: null };
    updateModalChrome(root, view.sel);
    if (!state.threadId) {
      const box = root.querySelector('#ao-modal-thread-messages');
      if (box && !box.querySelector('.ao-bubble')) {
        box.innerHTML = '<p class="ticket-meta">Nova conversa — escreva a primeira mensagem abaixo.</p>';
      }
    }
    root.querySelector('#ao-modal-chat-input')?.focus();
  }

  function closeChatModal(root) {
    state.chatModalOpen = false;
    updateModalChrome(root, cache ? deriveView(cache).sel : null);
  }

  async function sendChatStream(root, path, body) {
    const h = authHeaders({ 'Content-Type': 'application/json', Accept: 'text/event-stream' });
    const r = await fetchWithTimeout(`/api/v1/agents${path}`, {
      method: 'POST',
      headers: h,
      body: JSON.stringify(body),
    }, 180000);
    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)}`);
    if (!r.body) throw new Error('stream indisponível');
    return r.body.getReader();
  }

  async function sendChatMessage(root) {
    const input = chatInputEl(root);
    const btn = sendBtnEl(root);
    const box = msgBoxEl(root);
    const q = (input?.value || '').trim();
    if (!q || chatInFlight) return;
    const target = chatAgentId();
    chatInFlight = true;
    if (btn) btn.disabled = true;
    let streamText = '';
    let typingEl = null;

    if (box) {
      box.querySelector('#ao-typing-bubble')?.remove();
      box.querySelector('.ticket-meta')?.remove();
      box.insertAdjacentHTML('beforeend', `<div class="ao-bubble ao-bubble-human"><div class="ao-bubble-meta">Você · agora</div><div class="ao-bubble-body">${esc(q)}</div></div>`);
      box.insertAdjacentHTML('beforeend', typingBubbleHtml(chatTargetLabel()));
      typingEl = box.querySelector('#ao-typing-bubble');
      box.scrollTop = box.scrollHeight;
    }

    try {
      const path = state.threadId
        ? `/threads/${state.threadId}/chat/stream`
        : '/chat/stream';
      const reader = await sendChatStream(root, path, {
        question: q,
        include_findings: true,
        target_agent: target,
      });
      const decoder = new TextDecoder();
      let buf = '';
      let doneEvt = null;

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buf += decoder.decode(value, { stream: true });
        const parts = buf.split('\n\n');
        buf = parts.pop() || '';
        for (const part of parts) {
          const line = part.trim();
          if (!line.startsWith('data: ')) continue;
          let evt;
          try { evt = JSON.parse(line.slice(6)); } catch { continue; }
          if (evt.type === 'start' && evt.thread_id) {
            state.threadId = evt.thread_id;
            updateCommsChrome(root, cache ? deriveView(cache).sel : null);
          }
          if (evt.type === 'token' && evt.text) {
            streamText += evt.text;
            const streamBody = typingEl?.querySelector('.ao-stream-body');
            if (streamBody) {
              streamBody.innerHTML = renderMarkdown(streamText);
              box.scrollTop = box.scrollHeight;
            }
          }
          if (evt.type === 'done') doneEvt = evt;
          if (evt.type === 'error') throw new Error(evt.message || 'Erro no stream');
        }
      }

      input.value = '';
      lastThreadHtml = '';
      if (doneEvt?.thread_id) state.threadId = doneEvt.thread_id;
      if (doneEvt?.actions?.length) {
        state.lastActions = doneEvt.actions;
        if (typingEl) typingEl.insertAdjacentHTML('beforeend', actionsHtml(doneEvt.actions));
      }

      if (state.threadId) {
        await loadThread(root, state.threadId);
      } else if (typingEl && streamText) {
        typingEl.id = '';
        typingEl.classList.remove('ao-bubble--typing');
        const meta = typingEl.querySelector('.ao-bubble-meta');
        if (meta) meta.textContent = `${chatTargetLabel()} · agora${doneEvt?.model ? ` · ${doneEvt.model}` : ''}`;
      }

      try {
        const threads = await agentsApi('/threads?limit=40');
        if (cache) cache.threads = threads;
        patchHtml(root.querySelector('#ao-pane-tasks'), cache ? taskBoardHtml(cache) : '');
        patchHtml(root.querySelector('#ao-task-board'), cache ? taskBoardHtml(cache) : '');
      } catch { /* ignore */ }
    } catch (err) {
      typingEl?.remove();
      if (box) {
        box.insertAdjacentHTML('beforeend', `<p class="error">${esc(chatErrorMessage(err))}</p>`);
        box.scrollTop = box.scrollHeight;
      }
    } finally {
      chatInFlight = false;
      if (btn) btn.disabled = false;
      input?.focus();
    }
  }

  async function runScenarioAction(root, scenarioId, btn) {
    if (btn) { btn.disabled = true; btn.dataset.prev = btn.textContent; btn.textContent = '⏳ A executar…'; }
    const box = msgBoxEl(root);
    try {
      const res = await agentsApi(`/runs/${encodeURIComponent(scenarioId)}`, { method: 'POST' });
      const ok = res.ok && res.status === 'ok';
      const msg = ok
        ? `✅ **Check concluído** — \`${scenarioId}\` saudável (sem findings).`
        : `⚠️ **Check concluído** — \`${scenarioId}\`: status \`${res.status}\`, ${res.findings_count || 0} finding(s). Run #${res.run_id || '?'}`;
      if (box) {
        box.insertAdjacentHTML('beforeend', `<div class="ao-bubble ao-bubble-system"><div class="ao-bubble-meta">Sistema · agora</div><div class="ao-bubble-body">${renderMarkdown(msg)}</div></div>`);
        box.scrollTop = box.scrollHeight;
      }
      await renderAgenticOps({ soft: true, force: true });
    } catch (err) {
      if (box) {
        box.insertAdjacentHTML('beforeend', `<p class="error">${esc(chatErrorMessage(err))}</p>`);
      }
    } finally {
      if (btn) {
        btn.disabled = false;
        btn.textContent = btn.dataset.prev || btn.textContent;
      }
    }
  }

  async function ackIncidentAction(root, incidentId, btn) {
    if (btn) { btn.disabled = true; btn.textContent = '⏳…'; }
    try {
      await agentsApi(`/incidents/${incidentId}/ack`, { method: 'POST' });
      const box = msgBoxEl(root);
      if (box) {
        box.insertAdjacentHTML('beforeend', `<div class="ao-bubble ao-bubble-system"><div class="ao-bubble-meta">Sistema · agora</div><div class="ao-bubble-body">✓ Incidente #${incidentId} reconhecido (ack).</div></div>`);
        box.scrollTop = box.scrollHeight;
      }
      await renderAgenticOps({ soft: true, force: true });
    } catch (err) {
      const box = msgBoxEl(root);
      if (box) box.insertAdjacentHTML('beforeend', `<p class="error">${esc(chatErrorMessage(err))}</p>`);
    } finally {
      if (btn) btn.disabled = false;
    }
  }

  function focusChat(root, agentId) {
    if (agentId && agentId !== 'ALL') state.selectedAgent = agentId;
    openChatModal(root, agentId);
  }

  async function openTaskThread(root, threadId, agentId) {
    state.threadId = threadId;
    if (agentId) state.selectedAgent = agentId;
    lastThreadHtml = '';
    openChatModal(root, agentId);
    if (cache) patchDashboard(root, cache);
    await loadThread(root, threadId);
    updateCommsChrome(root, cache ? deriveView(cache).sel : null);
    root.querySelector('#ao-modal-chat-input')?.focus();
  }

  function preserveInputs(el) {
    return {
      chat: chatInputEl(el)?.value || '',
      modalChat: el.querySelector('#ao-modal-chat-input')?.value || '',
      kb: el.querySelector('#ao-kb-search')?.value || state.kbQuery,
    };
  }

  function restoreInputs(el, saved) {
    const chat = chatInputEl(el);
    if (chat && saved.chat) chat.value = saved.chat;
    const modal = el.querySelector('#ao-modal-chat-input');
    if (modal && saved.modalChat) modal.value = saved.modalChat;
    const kb = el.querySelector('#ao-kb-search');
    if (kb && saved.kb) kb.value = saved.kb;
  }

  async function loadThread(el, threadId, options = {}) {
    const box = msgBoxEl(el);
    if (!box || !threadId) return;
    const silent = options.silent === true;
    const prevScroll = box.scrollTop;
    const atBottom = box.scrollHeight - box.scrollTop - box.clientHeight < 40;

    if (!silent && state.threadId !== threadId) {
      box.innerHTML = '<p class="ticket-meta" style="opacity:0.6">A carregar thread…</p>';
    }

    state.threadId = threadId;
    const data = await agentsApi(`/threads/${threadId}/messages`);
    const html = data.messages.map(threadBubble).join('') || '<p class="ticket-meta">Sem mensagens.</p>';
    if (html === lastThreadHtml && state.threadId === threadId) {
      if (atBottom) box.scrollTop = box.scrollHeight;
      else box.scrollTop = prevScroll;
      return;
    }
    lastThreadHtml = html;
    box.innerHTML = html;
    box.scrollTop = atBottom ? box.scrollHeight : prevScroll;
    updateModalChrome(el, cache ? deriveView(cache).sel : null);
  }

  async function fetchDashboard() {
    const [overview, incidents, roster, timeline, threads] = await Promise.all([
      agentsApi('/overview'),
      agentsApi('/incidents?status=open&limit=50'),
      agentsApi('/roster'),
      agentsApi('/timeline?limit=12'),
      agentsApi('/threads?limit=40'),
    ]);
    return { overview, incidents, roster, timeline, threads };
  }

  async function fetchExtraData() {
    const [scenarios, activity, kbSources] = await Promise.all([
      agentsApi('/scenarios'),
      agentsApi('/action-log?limit=80'),
      agentsApi('/kb/sources'),
    ]);
    extraCache.scenarios = scenarios;
    extraCache.activity = activity;
    extraCache.kbSources = kbSources;
    if (state.kbQuery.trim()) {
      extraCache.kbHits = await agentsApi(`/kb/search?q=${encodeURIComponent(state.kbQuery.trim())}`);
    }
  }

  async function runKbSearch(root) {
    const input = root.querySelector('#ao-kb-search');
    state.kbQuery = (input?.value || '').trim();
    if (!state.kbQuery) return;
    extraCache.kbHits = await agentsApi(`/kb/search?q=${encodeURIComponent(state.kbQuery)}`);
    if (cache) patchHtml(root.querySelector('#ao-pane-memory'), knowledgeGraphHtml());
  }

  function bindEventsOnce(el) {
    if (eventsBound) return;
    eventsBound = true;

    el.addEventListener('dblclick', (ev) => {
      const root = document.getElementById('agentic-ops-content');
      const agentNode = ev.target.closest('[data-agent-id]');
      if (!root || !agentNode || agentNode.dataset.agentId === 'ALL') return;
      if (ev.target.closest('.ao-squad-chat-btn')) return;
      ev.preventDefault();
      state.threadId = null;
      lastThreadHtml = '';
      focusChat(root, agentNode.dataset.agentId);
    });

    el.addEventListener('keydown', (ev) => {
      const root = document.getElementById('agentic-ops-content');
      if (!root || !root.contains(ev.target)) return;
      if (ev.key === 'Enter' && !ev.shiftKey && (ev.target.id === 'ao-chat-input' || ev.target.id === 'ao-modal-chat-input')) {
        ev.preventDefault();
        sendChatMessage(root);
      }
      if (ev.key === 'Escape' && state.chatModalOpen) {
        ev.preventDefault();
        closeChatModal(root);
      }
    });

    el.addEventListener('click', async (ev) => {
      const root = document.getElementById('agentic-ops-content');
      if (!root || !root.contains(ev.target)) return;

      if (ev.target.closest('[data-close-modal]') || ev.target.closest('#ao-modal-close')) {
        closeChatModal(root);
        return;
      }

      const runBtn = ev.target.closest('[data-run-scenario]');
      if (runBtn) {
        ev.preventDefault();
        await runScenarioAction(root, runBtn.dataset.runScenario, runBtn);
        return;
      }

      const ackActionBtn = ev.target.closest('[data-ack-incident-id]');
      if (ackActionBtn) {
        ev.preventDefault();
        await ackIncidentAction(root, parseInt(ackActionBtn.dataset.ackIncidentId, 10), ackActionBtn);
        return;
      }

      const subBtn = ev.target.closest('[data-sub-view]');
      if (subBtn && subBtn.classList.contains('ao-subnav-btn')) {
        state.subView = subBtn.dataset.subView;
        updateSubViews(root);
        if (cache) patchSubViewPanes(root, cache);
        return;
      }

      const openChatBtn = ev.target.closest('[data-open-chat], #ao-btn-open-chat, #ao-btn-open-chat-inline');
      if (openChatBtn) {
        ev.stopPropagation();
        const agent = openChatBtn.dataset.openChat || chatAgentId();
        state.threadId = openChatBtn.dataset.openChat ? null : state.threadId;
        if (openChatBtn.dataset.openChat) lastThreadHtml = '';
        focusChat(root, agent);
        return;
      }

      const refreshBtn = ev.target.closest('#ao-btn-refresh');
      if (refreshBtn) {
        ev.preventDefault();
        await renderAgenticOps({ force: true });
        return;
      }

      const kbBtn = ev.target.closest('#ao-kb-search-btn');
      if (kbBtn) {
        await runKbSearch(root);
        return;
      }

      const mainTabBtn = ev.target.closest('[data-main-tab]');
      if (mainTabBtn) {
        state.mainTab = mainTabBtn.dataset.mainTab;
        updateMainTabs(root);
        return;
      }

      const chatAgentBtn = ev.target.closest('[data-chat-agent]');
      if (chatAgentBtn) {
        state.selectedAgent = chatAgentBtn.dataset.chatAgent;
        state.threadId = null;
        lastThreadHtml = '';
        if (cache) patchDashboard(root, cache);
        focusChat(root, chatAgentBtn.dataset.chatAgent);
        return;
      }

      const newChatBtn = ev.target.closest('#ao-btn-new-chat');
      if (newChatBtn) {
        state.threadId = null;
        lastThreadHtml = '';
        focusChat(root, chatAgentId());
        return;
      }

      const taskCard = ev.target.closest('[data-thread-id]');
      if (taskCard && taskCard.classList.contains('ao-task-card')) {
        const tid = parseInt(taskCard.dataset.threadId, 10);
        await openTaskThread(root, tid, taskCard.dataset.taskAgent);
        return;
      }

      const agentNode = ev.target.closest('[data-agent-id]');
      if (agentNode && !ev.target.closest('[data-ack-incident]') && !ev.target.closest('[data-open-chat]')) {
        state.selectedAgent = agentNode.dataset.agentId;
        if (cache) patchDashboard(root, cache);
        return;
      }

      const ackBtn = ev.target.closest('[data-ack-incident]');
      if (ackBtn) {
        ev.stopPropagation();
        const id = parseInt(ackBtn.dataset.ackIncident, 10);
        await agentsApi(`/incidents/${id}/ack`, { method: 'POST' });
        if (state.selectedIncidentId === id) {
          state.selectedIncidentId = null;
          state.threadId = null;
          lastThreadHtml = '';
        }
        await renderAgenticOps({ soft: true, force: true });
        return;
      }

      const incNode = ev.target.closest('[data-incident-id], [data-open-incident]');
      if (incNode && !ev.target.closest('[data-ack-incident]')) {
        const id = parseInt(incNode.dataset.incidentId || incNode.dataset.openIncident, 10);
        state.selectedIncidentId = id;
        if (cache) {
          const view = patchDashboard(root, cache);
          const tid = parseInt(root.querySelector(`[data-incident-id="${id}"]`)?.dataset.threadId, 10);
          if (view.sel && tid) await openTaskThread(root, tid, view.sel.primary_agent);
          else if (view.sel?.thread_id) await openTaskThread(root, view.sel.thread_id, view.sel.primary_agent);
        }
        return;
      }

      const tabBtn = ev.target.closest('.ao-mobile-tabs button');
      if (tabBtn) {
        state.mobileTab = tabBtn.dataset.aoTab;
        if (tabBtn.dataset.aoTab === 'tasks') state.mainTab = 'tasks';
        updateMobileTabs(root);
        updateMainTabs(root);
        return;
      }

      if (ev.target.closest('#ao-btn-send') || ev.target.closest('#ao-modal-btn-send')) {
        await sendChatMessage(root);
      }
    });
  }

  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 (renderInFlight) {
      if (options.poll) return;
      pendingRender = { ...options, force: true };
      return;
    }

    const soft = shellMounted && (options.poll || options.soft) && !options.force;
    renderInFlight = true;

    if (shellMounted && !el.querySelector('#ao-subnav')) {
      shellMounted = false;
      eventsBound = false;
    }

    if (!soft && !shellMounted) {
      el.innerHTML = '<div class="ao-console ao-loading"><p>Carregando Agent Squad…</p></div>';
    }

    if (!getToken()) {
      shellMounted = false;
      eventsBound = false;
      el.innerHTML = '<p class="error">Sessão não encontrada. <a href="/login.html">Fazer login</a></p>';
      renderInFlight = false;
      return;
    }

    if (!soft && typeof ensureValidSession === 'function') {
      const ok = await ensureValidSession();
      if (!ok) {
        shellMounted = false;
        eventsBound = false;
        el.innerHTML = '<p class="error">Sessão expirada. <a href="/login.html">Fazer login</a></p>';
        renderInFlight = false;
        return;
      }
    }

    try {
      const saved = soft ? preserveInputs(el) : null;
      const data = await fetchDashboard();
      cache = data;
      try {
        await fetchExtraData();
      } catch {
        /* extra panes degrade gracefully */
      }

      if (!shellMounted) {
        mountShell(el);
        bindEventsOnce(el);
      }

      const view = patchDashboard(el, data, { soft });
      if (saved) restoreInputs(el, saved);

      if (state.threadId && state.chatModalOpen) {
        await loadThread(el, state.threadId, { silent: true });
      } else if (state.threadId && !state.chatModalOpen && view.sel?.thread_id) {
        await loadThread(el, view.sel.thread_id);
      }

      if (!state.pollTimer) schedulePoll();
    } catch (err) {
      if (!soft) el.innerHTML = `<p class="error">Erro: ${esc(err.message)}</p>`;
    } finally {
      renderInFlight = false;
      if (pendingRender) {
        const next = pendingRender;
        pendingRender = null;
        await renderAgenticOps(next);
      }
    }
  }

  document.addEventListener('keydown', (ev) => {
    const view = document.getElementById('view-agentic-ops');
    if (!view || view.hidden) return;
    if (ev.key === 'r' && !ev.ctrlKey && !ev.metaKey) {
      const t = ev.target;
      if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA')) return;
      ev.preventDefault();
      renderAgenticOps({ force: true });
    }
    if (ev.key === 'Escape' && !state.chatModalOpen) {
      state.selectedIncidentId = null;
      state.threadId = null;
      lastThreadHtml = '';
      const el = document.getElementById('agentic-ops-content');
      if (el && cache) patchDashboard(el, cache);
    }
  });

  window.renderAgenticOps = renderAgenticOps;
})();
