let _currentPanel = 'chat';
let _skillsData = null; // cached skills list

async function switchPanel(name) {
  _currentPanel = name;
  // Update nav tabs
  document.querySelectorAll('.nav-tab').forEach(t => t.classList.toggle('active', t.dataset.panel === name));
  // Update panel views
  document.querySelectorAll('.panel-view').forEach(p => p.classList.remove('active'));
  const panelEl = $('panel' + name.charAt(0).toUpperCase() + name.slice(1));
  if (panelEl) panelEl.classList.add('active');
  // Lazy-load panel data
  if (name === 'tasks') await loadCrons();
  if (name === 'skills') await loadSkills();
  if (name === 'memory') await loadMemory();
  if (name === 'workspaces') await loadWorkspacesPanel();
  if (name === 'profiles') await loadProfilesPanel();
  if (name === 'todos') loadTodos();
  if (name === 'missioncontrol') await loadMissionControl();
  if (name === 'agents') await loadAgentsPanel();
}

// ── Cron panel ──
async function loadCrons() {
  const box = $('cronList');
  try {
    const data = await api('/api/crons');
    if (!data.jobs || !data.jobs.length) {
      box.innerHTML = `<div style="padding:16px;color:var(--muted);font-size:12px">${esc(t('cron_no_jobs'))}</div>`;
      return;
    }
    box.innerHTML = '';
    for (const job of data.jobs) {
      const item = document.createElement('div');
      item.className = 'cron-item';
      item.id = 'cron-' + job.id;
      const statusClass = job.enabled === false ? 'disabled' : job.state === 'paused' ? 'paused' : job.last_status === 'error' ? 'error' : 'active';
      const statusLabel = job.enabled === false ? t('cron_status_off') : job.state === 'paused' ? t('cron_status_paused') : job.last_status === 'error' ? t('cron_status_error') : t('cron_status_active');
      const nextRun = job.next_run_at ? new Date(job.next_run_at).toLocaleString() : t('not_available');
      const lastRun = job.last_run_at ? new Date(job.last_run_at).toLocaleString() : t('never');
      item.innerHTML = `
        <div class="cron-header" onclick="toggleCron('${job.id}')">
          <span class="cron-name" title="${esc(job.name)}">${esc(job.name)}</span>
          <span class="cron-schedule-inline">${esc(job.schedule_display || job.schedule?.expression || '')}</span>
          <span class="cron-last-inline">${job.last_run_at ? esc(new Date(job.last_run_at).toLocaleString()) : ''}</span>
          <span class="cron-status ${statusClass}">${statusLabel}</span>
        </div>
        <div class="cron-body" id="cron-body-${job.id}">
          <div class="cron-schedule">${li('clock',12)} ${esc(job.schedule_display || job.schedule?.expression || '')} &nbsp;|&nbsp; ${esc(t('cron_next'))}: ${esc(nextRun)} &nbsp;|&nbsp; ${esc(t('cron_last'))}: ${esc(lastRun)}</div>
          <div class="cron-prompt">${esc((job.prompt||'').slice(0,300))}${(job.prompt||'').length>300?'…':''}</div>
          <div class="cron-actions">
            <button class="cron-btn run" onclick="cronRun('${job.id}')">${li('play',12)} ${esc(t('cron_run_now'))}</button>
            ${job.state==='paused'
              ? `<button class="cron-btn" onclick="cronResume('${job.id}')">${li('play',12)} ${esc(t('cron_resume'))}</button>`
              : `<button class="cron-btn pause" onclick="cronPause('${job.id}')">${li('pause',12)} ${esc(t('cron_pause'))}</button>`}
            <button class="cron-btn" onclick="cronEditOpen('${job.id}',${JSON.stringify(job).replace(/"/g,'&quot;')})">${li('pencil',12)} ${esc(t('edit'))}</button>
            <button class="cron-btn" style="border-color:rgba(201,168,76,.3);color:var(--accent)" onclick="cronDelete('${job.id}')">${li('trash-2',12)} ${esc(t('delete_title'))}</button>
          </div>
          <!-- Inline edit form, hidden by default -->
          <div id="cron-edit-${job.id}" style="display:none;margin-top:8px;border-top:1px solid var(--border);padding-top:8px">
            <input id="cron-edit-name-${job.id}" placeholder="${esc(t('cron_job_name_placeholder'))}" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:5px;box-sizing:border-box">
            <input id="cron-edit-schedule-${job.id}" placeholder="${esc(t('cron_schedule_placeholder'))}" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;margin-bottom:5px;box-sizing:border-box">
            <textarea id="cron-edit-prompt-${job.id}" rows="3" placeholder="${esc(t('cron_prompt_placeholder'))}" style="width:100%;background:rgba(255,255,255,.05);border:1px solid var(--border2);border-radius:6px;color:var(--text);padding:5px 8px;font-size:12px;outline:none;resize:none;font-family:inherit;margin-bottom:5px;box-sizing:border-box"></textarea>
            <div id="cron-edit-err-${job.id}" style="font-size:11px;color:var(--accent);display:none;margin-bottom:5px"></div>
            <div style="display:flex;gap:6px">
              <button class="cron-btn run" style="flex:1" onclick="cronEditSave('${job.id}')">${esc(t('save'))}</button>
              <button class="cron-btn" style="flex:1" onclick="cronEditClose('${job.id}')">${esc(t('cancel'))}</button>
            </div>
          </div>
          <div id="cron-output-${job.id}">
            <div class="cron-last-header" style="display:flex;align-items:center;justify-content:space-between">
              <span>${esc(t('cron_last_output'))}</span>
              <button class="cron-btn" style="padding:1px 8px;font-size:10px" onclick="loadCronHistory('${job.id}',this)">${esc(t('cron_all_runs'))}</button>
            </div>
            <div class="cron-last" id="cron-out-text-${job.id}" style="color:var(--muted);font-size:11px">${esc(t('loading'))}</div>
            <div id="cron-history-${job.id}" style="display:none"></div>
          </div>
        </div>`;
      box.appendChild(item);
      // Eagerly load last output for visible items
      loadCronOutput(job.id);
    }
  } catch(e) { box.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">${esc(t('error_prefix'))}${esc(e.message)}</div>`; }
}

let _cronSelectedSkills=[];
let _cronSkillsCache=null;

function toggleCronForm(){
  const form=$('cronCreateForm');
  if(!form)return;
  const open=form.style.display!=='none';
  form.style.display=open?'none':'';
  if(!open){
    $('cronFormName').value='';
    $('cronFormSchedule').value='';
    $('cronFormPrompt').value='';
    $('cronFormDeliver').value='local';
    $('cronFormError').style.display='none';
    _cronSelectedSkills=[];
    _renderCronSkillTags();
    const search=$('cronFormSkillSearch');
    if(search)search.value='';
    // Always re-fetch skills to avoid stale cache
    _cronSkillsCache=null;
    api('/api/skills').then(d=>{_cronSkillsCache=d.skills||[];}).catch(()=>{});
    $('cronFormName').focus();
  }
}

function _renderCronSkillTags(){
  const wrap=$('cronFormSkillTags');
  if(!wrap)return;
  wrap.innerHTML='';
  for(const name of _cronSelectedSkills){
    const tag=document.createElement('span');
    tag.className='skill-tag';
    tag.dataset.skill=name;
    const rm=document.createElement('span');
    rm.className='remove-tag';rm.textContent='×';
    rm.onclick=()=>{_cronSelectedSkills=_cronSelectedSkills.filter(s=>s!==name);tag.remove();};
    tag.appendChild(document.createTextNode(name));
    tag.appendChild(rm);
    wrap.appendChild(tag);
  }
}

// Skill search input handler
(function(){
  const setup=()=>{
    const search=$('cronFormSkillSearch');
    const dropdown=$('cronFormSkillDropdown');
    if(!search||!dropdown)return;
    search.oninput=()=>{
      const q=search.value.trim().toLowerCase();
      if(!q||!_cronSkillsCache){dropdown.style.display='none';return;}
      const matches=_cronSkillsCache.filter(s=>
        !_cronSelectedSkills.includes(s.name)&&
        (s.name.toLowerCase().includes(q)||(s.category||'').toLowerCase().includes(q))
      ).slice(0,8);
      if(!matches.length){dropdown.style.display='none';return;}
      dropdown.innerHTML='';
      for(const s of matches){
        const opt=document.createElement('div');
        opt.className='skill-opt';
        opt.textContent=s.name+(s.category?' ('+s.category+')':'');
        opt.onclick=()=>{
          _cronSelectedSkills.push(s.name);
          _renderCronSkillTags();
          search.value='';
          dropdown.style.display='none';
        };
        dropdown.appendChild(opt);
      }
      dropdown.style.display='';
    };
    search.onblur=()=>setTimeout(()=>{dropdown.style.display='none';},150);
  };
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',setup);
  else setTimeout(setup,0);
})();

async function submitCronCreate(){
  const name=$('cronFormName').value.trim();
  const schedule=$('cronFormSchedule').value.trim();
  const prompt=$('cronFormPrompt').value.trim();
  const deliver=$('cronFormDeliver').value;
  const errEl=$('cronFormError');
  errEl.style.display='none';
  if(!schedule){errEl.textContent=t('cron_schedule_required_example');errEl.style.display='';return;}
  if(!prompt){errEl.textContent=t('cron_prompt_required');errEl.style.display='';return;}
  try{
    const body={schedule,prompt,deliver};
    if(name)body.name=name;
    if(_cronSelectedSkills.length)body.skills=_cronSelectedSkills;
    await api('/api/crons/create',{method:'POST',body:JSON.stringify(body)});
    toggleCronForm();
    showToast(t('cron_job_created'));
    await loadCrons();
  }catch(e){
    errEl.textContent=t('error_prefix')+e.message;errEl.style.display='';
  }
}

function _cronOutputSnippet(content) {
  // Extract the response body from a cron output .md file
  const lines = content.split('\n');
  const responseIdx = lines.findIndex(l => l.startsWith('## Response') || l.startsWith('# Response'));
  const body = (responseIdx >= 0 ? lines.slice(responseIdx + 1) : lines).join('\n').trim();
  return body.slice(0, 600) || '(empty)';
}

async function loadCronOutput(jobId) {
  try {
    const data = await api(`/api/crons/output?job_id=${encodeURIComponent(jobId)}&limit=1`);
    const el = $('cron-out-text-' + jobId);
    if (!el) return;
    if (!data.outputs || !data.outputs.length) { el.textContent = t('cron_no_runs_yet'); return; }
    const out = data.outputs[0];
    const ts = out.filename.replace('.md','').replace(/_/g,' ');
    el.textContent = ts + '\n\n' + _cronOutputSnippet(out.content);
  } catch(e) { /* ignore */ }
}

async function loadCronHistory(jobId, btn) {
  const histEl = $('cron-history-' + jobId);
  if (!histEl) return;
  // Toggle: if already open, close it
  if (histEl.style.display !== 'none') {
    histEl.style.display = 'none';
    if (btn) btn.textContent = t('cron_all_runs');
    return;
  }
  if (btn) btn.textContent = t('loading');
  try {
    const data = await api(`/api/crons/output?job_id=${encodeURIComponent(jobId)}&limit=20`);
    if (!data.outputs || !data.outputs.length) {
      histEl.innerHTML = `<div style="font-size:11px;color:var(--muted);padding:4px 0">${esc(t('cron_no_runs_yet'))}</div>`;
    } else {
      histEl.innerHTML = data.outputs.map((out, i) => {
        const ts = out.filename.replace('.md','').replace(/_/g,' ');
        const snippet = _cronOutputSnippet(out.content);
        const id = `cron-hist-run-${jobId}-${i}`;
        return `<div style="border-top:1px solid var(--border);padding:6px 0">
          <div style="display:flex;align-items:center;justify-content:space-between;cursor:pointer" onclick="document.getElementById('${id}').style.display=document.getElementById('${id}').style.display==='none'?'':'none'">
            <span style="font-size:11px;font-weight:600;color:var(--muted)">${esc(ts)}</span>
            <span style="font-size:10px;color:var(--muted);opacity:.6">▸</span>
          </div>
          <div id="${id}" style="display:none;font-size:11px;color:var(--muted);white-space:pre-wrap;line-height:1.5;margin-top:4px;max-height:200px;overflow-y:auto">${esc(snippet)}</div>
        </div>`;
      }).join('');
    }
    histEl.style.display = '';
    if (btn) btn.textContent = t('cron_hide_runs');
  } catch(e) {
    if (btn) btn.textContent = t('cron_all_runs');
  }
}

function toggleCron(id) {
  const body = $('cron-body-' + id);
  if (body) body.classList.toggle('open');
}

async function cronRun(id) {
  try {
    await api('/api/crons/run', {method:'POST', body: JSON.stringify({job_id: id})});
    showToast(t('cron_job_triggered'));
    setTimeout(() => loadCronOutput(id), 5000);
  } catch(e) { showToast(t('failed_colon') + e.message, 4000); }
}

async function cronPause(id) {
  try {
    await api('/api/crons/pause', {method:'POST', body: JSON.stringify({job_id: id})});
    showToast(t('cron_job_paused'));
    await loadCrons();
  } catch(e) { showToast(t('failed_colon') + e.message, 4000); }
}

async function cronResume(id) {
  try {
    await api('/api/crons/resume', {method:'POST', body: JSON.stringify({job_id: id})});
    showToast(t('cron_job_resumed'));
    await loadCrons();
  } catch(e) { showToast(t('failed_colon') + e.message, 4000); }
}

function cronEditOpen(id, job) {
  const form = $('cron-edit-' + id);
  if (!form) return;
  $('cron-edit-name-' + id).value = job.name || '';
  $('cron-edit-schedule-' + id).value = job.schedule_display || (job.schedule && job.schedule.expression) || job.schedule || '';
  $('cron-edit-prompt-' + id).value = job.prompt || '';
  const errEl = $('cron-edit-err-' + id);
  if (errEl) errEl.style.display = 'none';
  form.style.display = '';
}

function cronEditClose(id) {
  const form = $('cron-edit-' + id);
  if (form) form.style.display = 'none';
}

async function cronEditSave(id) {
  const name = $('cron-edit-name-' + id).value.trim();
  const schedule = $('cron-edit-schedule-' + id).value.trim();
  const prompt = $('cron-edit-prompt-' + id).value.trim();
  const errEl = $('cron-edit-err-' + id);
  if (!schedule) { errEl.textContent = t('cron_schedule_required'); errEl.style.display = ''; return; }
  if (!prompt) { errEl.textContent = t('cron_prompt_required'); errEl.style.display = ''; return; }
  try {
    const updates = {job_id: id, schedule, prompt};
    if (name) updates.name = name;
    await api('/api/crons/update', {method:'POST', body: JSON.stringify(updates)});
    showToast(t('cron_job_updated'));
    await loadCrons();
  } catch(e) { errEl.textContent = t('error_prefix') + e.message; errEl.style.display = ''; }
}

async function cronDelete(id) {
  const _delCron=await showConfirmDialog({title:t('cron_delete_confirm_title'),message:t('cron_delete_confirm_message'),confirmLabel:t('delete_title'),danger:true,focusCancel:true});
  if(!_delCron) return;
  try {
    await api('/api/crons/delete', {method:'POST', body: JSON.stringify({job_id: id})});
    showToast(t('cron_job_deleted'));
    await loadCrons();
  } catch(e) { showToast(t('delete_failed') + e.message, 4000); }
}

function loadTodos() {
  const panel = $('todoPanel');
  if (!panel) return;
  const sourceMessages = (S.session && Array.isArray(S.session.messages) && S.session.messages.length) ? S.session.messages : S.messages;
  // Parse the most recent todo state from message history
  let todos = [];
  for (let i = sourceMessages.length - 1; i >= 0; i--) {
    const m = sourceMessages[i];
    if (m && m.role === 'tool') {
      try {
        const d = JSON.parse(typeof m.content === 'string' ? m.content : JSON.stringify(m.content));
        if (d && Array.isArray(d.todos) && d.todos.length) {
          todos = d.todos;
          break;
        }
      } catch(e) {}
    }
  }
  if (!todos.length) {
    panel.innerHTML = `<div style="color:var(--muted);font-size:12px;padding:4px 0">${esc(t('todos_no_active'))}</div>
      <div style="margin-top:10px;padding:12px;border:1px dashed var(--border2);border-radius:8px;font-size:12px;color:var(--muted);line-height:1.6;background:rgba(124,185,255,.03)">
        <div style="font-weight:600;margin-bottom:6px;color:var(--text);opacity:.8">${li('lightbulb',12)} Tipp</div>
        <div style="opacity:.7">Frag im Chat nach einer Todo-Liste, z.B.:</div>
        <div style="margin-top:6px;padding:6px 8px;background:rgba(255,255,255,.04);border-radius:6px;font-style:italic;opacity:.6">"Plane mein Wochenende: Freitags einkaufen, samtags Sport, sonntags relaxen"</div>
      </div>`;
    return;
  }
  const statusIcon = {pending:li('square',14), in_progress:li('loader',14), completed:li('check',14), cancelled:li('x',14)};
  const statusColor = {pending:'var(--muted)', in_progress:'var(--blue)', completed:'rgba(100,200,100,.8)', cancelled:'rgba(200,100,100,.5)'};
  panel.innerHTML = todos.map(t => `
    <div style="display:flex;align-items:flex-start;gap:10px;padding:6px 0;border-bottom:1px solid var(--border);">
      <span style="font-size:14px;display:inline-flex;align-items:center;flex-shrink:0;margin-top:1px;color:${statusColor[t.status]||'var(--muted)'}">${statusIcon[t.status]||li('square',14)}</span>
      <div style="flex:1;min-width:0">
        <div style="font-size:13px;color:${t.status==='completed'?'var(--muted)':t.status==='in_progress'?'var(--text)':'var(--text)'};${t.status==='completed'?'text-decoration:line-through;opacity:.5':''};line-height:1.4">${esc(t.content)}</div>
        <div style="font-size:10px;color:var(--muted);margin-top:2px;opacity:.6">${esc(t.id)} · ${esc(t.status)}</div>
      </div>
    </div>`).join('');
}

async function clearConversation() {
  if(!S.session) return;
  const _clrMsg=await showConfirmDialog({title:t('clear_conversation_title'),message:t('clear_conversation_message'),confirmLabel:t('clear'),danger:true,focusCancel:true});
  if(!_clrMsg) return;
  try {
    const data = await api('/api/session/clear', {method:'POST',
      body: JSON.stringify({session_id: S.session.session_id})});
    S.session = data.session;
    S.messages = [];
    S.toolCalls = [];
    syncTopbar();
    renderMessages();
    showToast(t('conversation_cleared'));
  } catch(e) { setStatus(t('clear_failed') + e.message); }
}

// ── Skills panel ──
async function loadSkills() {
  if (_skillsData) { renderSkills(_skillsData); return; }
  const box = $('skillsList');
  try {
    const data = await api('/api/skills');
    _skillsData = data.skills || [];
    renderSkills(_skillsData);
  } catch(e) { box.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`; }
}

function renderSkills(skills) {
  const query = ($('skillsSearch').value || '').toLowerCase();
  const filtered = query ? skills.filter(s =>
    (s.name||'').toLowerCase().includes(query) ||
    (s.description||'').toLowerCase().includes(query) ||
    (s.category||'').toLowerCase().includes(query)
  ) : skills;
  // Group by category
  const cats = {};
  for (const s of filtered) {
    const cat = s.category || 'general';
    if (!cats[cat]) cats[cat] = [];
    cats[cat].push(s);
  }
  const box = $('skillsList');
  box.innerHTML = '';
  if (!filtered.length) { box.innerHTML = `<div style="padding:12px;color:var(--muted);font-size:12px">${esc(t('skills_no_match'))}</div>`; return; }
  for (const [cat, items] of Object.entries(cats).sort()) {
    const sec = document.createElement('div');
    sec.className = 'skills-category';
    const displayName = cat === 'general' ? 'General' : cat;
    sec.innerHTML = `<div class="skills-cat-header">${li('folder',12)} ${esc(displayName)} <span style="opacity:.5">(${items.length})</span></div>`;
    for (const skill of items.sort((a,b) => a.name.localeCompare(b.name))) {
      const el = document.createElement('div');
      el.className = 'skill-item';
      el.innerHTML = `<span class="skill-name">${esc(skill.name)}</span><span class="skill-desc">${esc(skill.description||'')}</span>`;
      el.onclick = () => openSkill(skill.name, el);
      sec.appendChild(el);
    }
    box.appendChild(sec);
  }
}

function filterSkills() {
  if (_skillsData) renderSkills(_skillsData);
}

async function openSkill(name, el) {
  // Highlight active skill
  document.querySelectorAll('.skill-item').forEach(e => e.classList.remove('active'));
  if (el) el.classList.add('active');
  try {
    const data = await api(`/api/skills/content?name=${encodeURIComponent(name)}`);
    // Show skill content in right panel preview
    $('previewPathText').textContent = name + '.md';
    $('previewBadge').textContent = 'skill';
    $('previewBadge').className = 'preview-badge md';
    showPreview('md');
    let html = renderMd(data.content || '(no content)');
    // Render linked files section if present
    const lf = data.linked_files || {};
    const categories = Object.entries(lf).filter(([,files]) => files && files.length > 0);
    if (categories.length) {
      html += `<div class="skill-linked-files"><div style="font-size:11px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">${esc(t('linked_files'))}</div>`;
      for (const [cat, files] of categories) {
        html += `<div class="skill-linked-section"><h4>${esc(cat)}</h4>`;
        for (const f of files) {
          html += `<a class="skill-linked-file" href="#" data-skill-name="${esc(name)}" data-skill-file="${esc(f)}">${esc(f)}</a>`;
        }
        html += '</div>';
      }
      html += '</div>';
    }
    $('previewMd').innerHTML = html;
    // Wire linked-file clicks via data attributes (avoids inline JS XSS with apostrophes)
    $('previewMd').querySelectorAll('.skill-linked-file').forEach(a=>{
      a.addEventListener('click',e=>{e.preventDefault();openSkillFile(a.dataset.skillName,a.dataset.skillFile);});
    });
    $('previewArea').classList.add('visible');
    $('fileTree').style.display = 'none';
    const wsSearchSkill=$('wsSearchWrap');if(wsSearchSkill)wsSearchSkill.style.display='none';
  } catch(e) { setStatus(t('skill_load_failed') + e.message); }
}

async function openSkillFile(skillName, filePath) {
  try {
    const data = await api(`/api/skills/content?name=${encodeURIComponent(skillName)}&file=${encodeURIComponent(filePath)}`);
    $('previewPathText').textContent = skillName + ' / ' + filePath;
    $('previewBadge').textContent = filePath.split('.').pop() || 'file';
    $('previewBadge').className = 'preview-badge code';
    const ext = filePath.split('.').pop() || '';
    if (['md','markdown'].includes(ext)) {
      showPreview('md');
      $('previewMd').innerHTML = renderMd(data.content || '');
    } else {
      showPreview('code');
      if (['yml','yaml'].includes(ext)) {
        $('previewCode').textContent = '';
        $('previewCode').className = 'preview-code hl-yaml';
        $('previewCode').innerHTML = highlightYAML(data.content || '');
      } else {
        $('previewCode').className = 'preview-code';
        $('previewCode').textContent = data.content || '';
      }
      requestAnimationFrame(() => highlightCode());
    }
  } catch(e) { setStatus(t('skill_file_load_failed') + e.message); }
}

// ── YAML syntax highlighter with line numbers ──
function highlightYAML(text) {
  const lines = text.split('\n');
  return lines.map(raw => {
    // Escape HTML first
    let line = esc(raw);
    // Highlight full-line comments
    if (/^\s*#/.test(line)) {
      return '<span class="code-line"><span class="hl-comment">' + line + '</span></span>';
    }
    // Highlight inline comments (after a value)
    line = line.replace(/(#.*)$/, '<span class="hl-comment">$1</span>');
    // Highlight strings (double or single quoted)
    line = line.replace(/(&quot;[^&]*?&quot;|&#39;[^&]*?&#39;)/g, '<span class="hl-string">$1</span>');
    // Highlight key: value pairs — key before the colon
    line = line.replace(/^(\s*)([\w._-]+)(:)/, '$1<span class="hl-key">$2</span><span class="hl-value">$3</span>');
    return '<span class="code-line">' + line + '</span>';
  }).join('\n');
}

// ── Skill create/edit form ──
let _editingSkillName = null;

function toggleSkillForm(prefillName, prefillCategory, prefillContent) {
  const form = $('skillCreateForm');
  if (!form) return;
  const open = form.style.display !== 'none';
  if (open) { form.style.display = 'none'; _editingSkillName = null; return; }
  $('skillFormName').value = prefillName || '';
  $('skillFormCategory').value = prefillCategory || '';
  $('skillFormContent').value = prefillContent || '';
  $('skillFormError').style.display = 'none';
  _editingSkillName = prefillName || null;
  form.style.display = '';
  $('skillFormName').focus();
}

async function submitSkillSave() {
  const name = ($('skillFormName').value||'').trim().toLowerCase().replace(/\s+/g, '-');
  const category = ($('skillFormCategory').value||'').trim();
  const content = $('skillFormContent').value;
  const errEl = $('skillFormError');
  errEl.style.display = 'none';
  if (!name) { errEl.textContent = t('skill_name_required'); errEl.style.display = ''; return; }
  if (!content.trim()) { errEl.textContent = t('content_required'); errEl.style.display = ''; return; }
  try {
    await api('/api/skills/save', {method:'POST', body: JSON.stringify({name, category: category||undefined, content})});
    showToast(_editingSkillName ? t('skill_updated') : t('skill_created'));
    _skillsData = null;
    _cronSkillsCache = null;
    toggleSkillForm();
    await loadSkills();
  } catch(e) { errEl.textContent = t('error_prefix') + e.message; errEl.style.display = ''; }
}

// ── Memory inline edit ──
let _memoryData = null;

function toggleMemoryEdit() {
  const form = $('memoryEditForm');
  if (!form) return;
  const open = form.style.display !== 'none';
  if (open) { form.style.display = 'none'; return; }
  $('memEditSection').textContent = t('memory_notes_label');
  $('memEditContent').value = _memoryData ? (_memoryData.memory || '') : '';
  $('memEditError').style.display = 'none';
  form.style.display = '';
}

function closeMemoryEdit() {
  const form = $('memoryEditForm');
  if (form) form.style.display = 'none';
}

async function submitMemorySave() {
  const content = $('memEditContent').value;
  const errEl = $('memEditError');
  errEl.style.display = 'none';
  try {
    await api('/api/memory/write', {method:'POST', body: JSON.stringify({section: 'memory', content})});
    showToast(t('memory_saved'));
    closeMemoryEdit();
    await loadMemory(true);
  } catch(e) { errEl.textContent = t('error_prefix') + e.message; errEl.style.display = ''; }
}

// ── Workspace management ──
let _workspaceList = [];  // cached from /api/workspaces

function getWorkspaceFriendlyName(path){
  // Look up the friendly name from the workspace list cache, fallback to last path segment
  if(_workspaceList && _workspaceList.length){
    const match=_workspaceList.find(w=>w.path===path);
    if(match && match.name) return match.name;
  }
  return path.split('/').filter(Boolean).pop()||path;
}

function syncWorkspaceDisplays(){
  const hasSession=!!(S.session&&S.session.workspace);
  const ws=hasSession?S.session.workspace:'';
  const label=hasSession?getWorkspaceFriendlyName(ws):t('no_workspace');

  const sidebarName=$('sidebarWsName');
  const sidebarPath=$('sidebarWsPath');
  if(sidebarName) sidebarName.textContent=label;
  if(sidebarPath) sidebarPath.textContent=ws;

  const composerChip=$('composerWorkspaceChip');
  const composerLabel=$('composerWorkspaceLabel');
  const composerDropdown=$('composerWsDropdown');
  if(!hasSession && composerDropdown) composerDropdown.classList.remove('open');
  if(composerLabel) composerLabel.textContent=label;
  if(composerChip){
    composerChip.disabled=!hasSession;
    composerChip.title=hasSession?ws:t('no_workspace');
    composerChip.classList.toggle('active',!!(composerDropdown&&composerDropdown.classList.contains('open')));
  }
}

async function loadWorkspaceList(){
  try{
    const data = await api('/api/workspaces');
    _workspaceList = data.workspaces || [];
    syncWorkspaceDisplays();
    return data;
  }catch(e){ return {workspaces:[], last:''}; }
}

function _renderWorkspaceAction(label, meta, iconSvg, onClick){
  const opt=document.createElement('div');
  opt.className='ws-opt ws-opt-action';
  opt.innerHTML=`<span class="ws-opt-icon">${iconSvg}</span><span><span class="ws-opt-name">${esc(label)}</span>${meta?`<span class="ws-opt-meta">${esc(meta)}</span>`:''}</span>`;
  opt.onclick=onClick;
  return opt;
}

function _positionComposerWsDropdown(){
  const dd=$('composerWsDropdown');
  const chip=$('composerWorkspaceChip');
  const footer=document.querySelector('.composer-footer');
  if(!dd||!chip||!footer)return;
  const chipRect=chip.getBoundingClientRect();
  const footerRect=footer.getBoundingClientRect();
  let left=chipRect.left-footerRect.left;
  const maxLeft=Math.max(0, footer.clientWidth-dd.offsetWidth);
  left=Math.max(0, Math.min(left, maxLeft));
  dd.style.left=`${left}px`;
}

function _positionProfileDropdown(){
  const dd=$('profileDropdown');
  const chip=$('profileChip');
  const footer=document.querySelector('.composer-footer');
  if(!dd||!chip||!footer)return;
  const chipRect=chip.getBoundingClientRect();
  const footerRect=footer.getBoundingClientRect();
  let left=chipRect.left-footerRect.left;
  const maxLeft=Math.max(0, footer.clientWidth-dd.offsetWidth);
  left=Math.max(0, Math.min(left, maxLeft));
  dd.style.left=`${left}px`;
}

function renderWorkspaceDropdownInto(dd, workspaces, currentWs){
  if(!dd)return;
  dd.innerHTML='';
  for(const w of workspaces){
    const opt=document.createElement('div');
    opt.className='ws-opt'+(w.path===currentWs?' active':'');
    opt.innerHTML=`<span class="ws-opt-name">${esc(w.name)}</span><span class="ws-opt-path">${esc(w.path)}</span>`;
    opt.onclick=()=>switchToWorkspace(w.path,w.name);
    dd.appendChild(opt);
  }
  dd.appendChild(document.createElement('div')).className='ws-divider';
  dd.appendChild(_renderWorkspaceAction(
    t('workspace_choose_path'),
    t('workspace_choose_path_meta'),
    li('folder',12),
    ()=>promptWorkspacePath()
  ));
  const div=document.createElement('div');div.className='ws-divider';dd.appendChild(div);
  dd.appendChild(_renderWorkspaceAction(
    t('workspace_manage'),
    t('workspace_manage_meta'),
    li('settings',12),
    ()=>{closeWsDropdown();mobileSwitchPanel('workspaces');}
  ));
}

function toggleWsDropdown(){
  const dd=$('wsDropdown');
  if(!dd)return;
  const open=dd.classList.contains('open');
  if(open){closeWsDropdown();}
  else{
    closeProfileDropdown(); // close profile dropdown if open
    loadWorkspaceList().then(data=>{
      renderWorkspaceDropdownInto(dd, data.workspaces, S.session?S.session.workspace:'');
      dd.classList.add('open');
    });
  }
}

function toggleComposerWsDropdown(){
  const dd=$('composerWsDropdown');
  const chip=$('composerWorkspaceChip');
  if(!dd||!chip||chip.disabled)return;
  const open=dd.classList.contains('open');
  if(open){closeWsDropdown();}
  else{
    closeProfileDropdown();
    if(typeof closeModelDropdown==='function') closeModelDropdown();
    loadWorkspaceList().then(data=>{
      renderWorkspaceDropdownInto(dd, data.workspaces, S.session?S.session.workspace:'');
      dd.classList.add('open');
      _positionComposerWsDropdown();
      chip.classList.add('active');
    });
  }
}

function closeWsDropdown(){
  const dd=$('wsDropdown');
  const composerDd=$('composerWsDropdown');
  const composerChip=$('composerWorkspaceChip');
  if(dd)dd.classList.remove('open');
  if(composerDd)composerDd.classList.remove('open');
  if(composerChip)composerChip.classList.remove('active');
}
document.addEventListener('click',e=>{
  if(
    !e.target.closest('#composerWorkspaceChip') &&
    !e.target.closest('#composerWsDropdown')
  ) closeWsDropdown();
});
window.addEventListener('resize',()=>{
  const dd=$('composerWsDropdown');
  if(dd&&dd.classList.contains('open')) _positionComposerWsDropdown();
});

async function loadWorkspacesPanel(){
  const panel=$('workspacesPanel');
  if(!panel)return;
  const data=await loadWorkspaceList();
  renderWorkspacesPanel(data.workspaces);
}

function renderWorkspacesPanel(workspaces){
  const panel=$('workspacesPanel');
  panel.innerHTML='';
  for(const w of workspaces){
    const row=document.createElement('div');row.className='ws-row';
    row.innerHTML=`
      <div class="ws-row-info">
        <div class="ws-row-name">${esc(w.name)}</div>
        <div class="ws-row-path">${esc(w.path)}</div>
      </div>
      <div class="ws-row-actions">
        <button class="ws-action-btn" title="${esc(t('workspace_use_title'))}" onclick="switchToWorkspace('${esc(w.path)}','${esc(w.name)}')">${li('arrow-right',12)} ${esc(t('workspace_use'))}</button>
        <button class="ws-action-btn danger" title="${esc(t('remove'))}" onclick="removeWorkspace('${esc(w.path)}')">${li('x',12)}</button>
      </div>`;
    panel.appendChild(row);
  }
  const addRow=document.createElement('div');addRow.className='ws-add-row';
  addRow.innerHTML=`
    <input id="wsAddInput" placeholder="${esc(t('workspace_add_path_placeholder'))}" style="flex:1;background:rgba(255,255,255,.06);border:1px solid var(--border2);border-radius:7px;color:var(--text);padding:7px 10px;font-size:12px;outline:none;">
    <button class="ws-action-btn" onclick="addWorkspace()">${li('plus',12)} ${esc(t('add'))}</button>`;
  panel.appendChild(addRow);
  const hint=document.createElement('div');
  hint.style.cssText='font-size:11px;color:var(--muted);padding:4px 0 8px';
  hint.textContent=t('workspace_paths_validated_hint');
  panel.appendChild(hint);
}

async function addWorkspace(){
  const input=$('wsAddInput');
  const path=(input?input.value:'').trim();
  if(!path)return;
  try{
    const data=await api('/api/workspaces/add',{method:'POST',body:JSON.stringify({path})});
    _workspaceList=data.workspaces;
    renderWorkspacesPanel(data.workspaces);
    if(input)input.value='';
    showToast(t('workspace_added'));
  }catch(e){setStatus(t('add_failed')+e.message);}
}

async function removeWorkspace(path){
  const _rmWs=await showConfirmDialog({title:t('workspace_remove_confirm_title'),message:t('workspace_remove_confirm_message',path),confirmLabel:t('remove'),danger:true,focusCancel:true});
  if(!_rmWs) return;
  try{
    const data=await api('/api/workspaces/remove',{method:'POST',body:JSON.stringify({path})});
    _workspaceList=data.workspaces;
    renderWorkspacesPanel(data.workspaces);
    showToast(t('workspace_removed'));
  }catch(e){setStatus(t('remove_failed')+e.message);}
}

async function promptWorkspacePath(){
  if(!S.session)return;
  const value=await showPromptDialog({
    title:t('workspace_switch_prompt_title'),
    message:t('workspace_switch_prompt_message'),
    confirmLabel:t('workspace_switch_prompt_confirm'),
    placeholder:t('workspace_switch_prompt_placeholder'),
    value:S.session.workspace||''
  });
  const path=(value||'').trim();
  if(!path)return;
  try{
    const data=await api('/api/workspaces/add',{method:'POST',body:JSON.stringify({path})});
    _workspaceList=data.workspaces||[];
    const target=_workspaceList[_workspaceList.length-1];
    if(!target) throw new Error(t('workspace_not_added'));
    await switchToWorkspace(target.path,target.name);
  }catch(e){
    if(String(e.message||'').includes('Workspace already in list')){
      showToast(t('workspace_already_saved'));
      return;
    }
    showToast(t('workspace_switch_failed')+e.message);
  }
}

async function switchToWorkspace(path,name){
  if(!S.session)return;
  if(S.busy){
    showToast(t('workspace_busy_switch'));
    return;
  }
  if(typeof _previewDirty!=='undefined'&&_previewDirty){
    const discard=await showConfirmDialog({
      title:t('discard_file_edits_title'),
      message:t('discard_file_edits_message'),
      confirmLabel:t('discard'),
      danger:true
    });
    if(!discard)return;
    if(typeof cancelEditMode==='function')cancelEditMode();
    if(typeof clearPreview==='function')clearPreview();
  }
  try{
    closeWsDropdown();
    await api('/api/session/update',{method:'POST',body:JSON.stringify({
      session_id:S.session.session_id, workspace:path, model:S.session.model
    })});
    S.session.workspace=path;
    syncTopbar();
    await loadDir('.');
    showToast(t('workspace_switched_to',name||getWorkspaceFriendlyName(path)));
  }catch(e){setStatus(t('switch_failed')+e.message);}
}

// ── Profile panel + dropdown ──
let _profilesCache = null;

async function loadProfilesPanel() {
  const panel = $('profilesPanel');
  if (!panel) return;
  try {
    const data = await api('/api/profiles');
    _profilesCache = data;
    panel.innerHTML = '';
    if (!data.profiles || !data.profiles.length) {
      panel.innerHTML = `<div style="padding:16px;color:var(--muted);font-size:12px">${esc(t('profiles_no_profiles'))}</div>`;
      return;
    }
    for (const p of data.profiles) {
      const card = document.createElement('div');
      card.className = 'profile-card';
      const meta = [];
      if (p.model) meta.push(p.model.split('/').pop());
      if (p.provider) meta.push(p.provider);
      if (p.skill_count) meta.push(t('profile_skill_count', p.skill_count));
      if (p.has_env) meta.push(t('profile_api_keys_configured'));
      const gwDot = p.gateway_running
        ? `<span class="profile-opt-badge running" title="${esc(t('profile_gateway_running'))}"></span>`
        : `<span class="profile-opt-badge stopped" title="${esc(t('profile_gateway_stopped'))}"></span>`;
      const isActive = p.name === data.active;
      const activeBadge = isActive ? `<span style="color:var(--link);font-size:10px;font-weight:600;margin-left:6px">${esc(t('profile_active'))}</span>` : '';
      card.innerHTML = `
        <div class="profile-card-header">
          <div style="min-width:0;flex:1">
            <div class="profile-card-name${isActive ? ' is-active' : ''}">${gwDot}${esc(p.name)}${p.is_default ? ' <span style="opacity:.5">(default)</span>' : ''}${activeBadge}</div>
            ${meta.length ? `<div class="profile-card-meta">${esc(meta.join(' \u00b7 '))}</div>` : `<div class="profile-card-meta">${esc(t('profile_no_configuration'))}</div>`}
          </div>
          <div class="profile-card-actions">
            ${!isActive ? `<button class="ws-action-btn" onclick="switchToProfile('${esc(p.name)}')" title="${esc(t('profile_switch_title'))}">${esc(t('profile_use'))}</button>` : ''}
            ${!p.is_default ? `<button class="ws-action-btn danger" onclick="deleteProfile('${esc(p.name)}')" title="${esc(t('profile_delete_title'))}">${li('x',12)}</button>` : ''}
          </div>
        </div>`;
      panel.appendChild(card);
    }
  } catch (e) {
    panel.innerHTML = `<div style="color:var(--accent);font-size:12px;padding:12px">Error: ${esc(e.message)}</div>`;
  }
}

function renderProfileDropdown(data) {
  const dd = $('profileDropdown');
  if (!dd) return;
  dd.innerHTML = '';
  const profiles = data.profiles || [];
  const active = data.active || 'default';
  for (const p of profiles) {
    const opt = document.createElement('div');
    opt.className = 'profile-opt' + (p.name === active ? ' active' : '');
    const meta = [];
    if (p.model) meta.push(p.model.split('/').pop());
    if (p.skill_count) meta.push(t('profile_skill_count', p.skill_count));
    const gwDot = `<span class="profile-opt-badge ${p.gateway_running ? 'running' : 'stopped'}"></span>`;
    const checkmark = p.name === active ? ' <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--link)" stroke-width="3" style="vertical-align:-1px"><polyline points="20 6 9 17 4 12"/></svg>' : '';
    opt.innerHTML = `<div class="profile-opt-name">${gwDot}${esc(p.name)}${p.is_default ? ' <span style="opacity:.5;font-weight:400">(default)</span>' : ''}${checkmark}</div>` +
      (meta.length ? `<div class="profile-opt-meta">${esc(meta.join(' \u00b7 '))}</div>` : '');
    opt.onclick = async () => {
      closeProfileDropdown();
      if (p.name === active) return;
      await switchToProfile(p.name);
    };
    dd.appendChild(opt);
  }
  // Divider + Manage link
  const div = document.createElement('div'); div.className = 'ws-divider'; dd.appendChild(div);
  const mgmt = document.createElement('div'); mgmt.className = 'profile-opt ws-manage';
  mgmt.innerHTML = `${li('settings',12)} ${esc(t('manage_profiles'))}`;
  mgmt.onclick = () => { closeProfileDropdown(); mobileSwitchPanel('profiles'); };
  dd.appendChild(mgmt);
}

function toggleProfileDropdown() {
  const dd = $('profileDropdown');
  if (!dd) return;
  if (dd.classList.contains('open')) { closeProfileDropdown(); return; }
  closeWsDropdown(); // close workspace dropdown if open
  if(typeof closeModelDropdown==='function') closeModelDropdown();
  api('/api/profiles').then(data => {
    renderProfileDropdown(data);
    dd.classList.add('open');
    _positionProfileDropdown();
    const chip=$('profileChip');
    if(chip) chip.classList.add('active');
  }).catch(e => { showToast(t('profiles_load_failed')); });
}

function closeProfileDropdown() {
  const dd = $('profileDropdown');
  if (dd) dd.classList.remove('open');
  const chip=$('profileChip');
  if(chip) chip.classList.remove('active');
}
document.addEventListener('click', e => {
  if (!e.target.closest('#profileChipWrap') && !e.target.closest('#profileDropdown')) closeProfileDropdown();
});
window.addEventListener('resize',()=>{
  const dd=$('profileDropdown');
  if(dd&&dd.classList.contains('open')) _positionProfileDropdown();
});

async function switchToProfile(name) {
  if (S.busy) { showToast(t('profiles_busy_switch')); return; }

  // Determine whether the current session has any messages.
  // A session with messages is "in progress" and belongs to the current profile —
  // we must not retag it.  We'll start a fresh session for the new profile instead.
  const sessionInProgress = S.session && S.messages && S.messages.length > 0;

  try {
    const data = await api('/api/profile/switch', { method: 'POST', body: JSON.stringify({ name }) });
    S.activeProfile = data.active || name;

    // ── Model ──────────────────────────────────────────────────────────────
    localStorage.removeItem('hermes-webui-model');
    _skillsData = null;
    await populateModelDropdown();
    if (data.default_model) {
      const sel = $('modelSelect');
      const resolved = _applyModelToDropdown(data.default_model, sel);
      const modelToUse = resolved || data.default_model;
      S._pendingProfileModel = modelToUse;
      // Only patch the in-memory session model if we're NOT about to replace the session
      if (S.session && !sessionInProgress) {
        S.session.model = modelToUse;
      }
    }

    // ── Workspace ──────────────────────────────────────────────────────────
    _workspaceList = null;
    await loadWorkspaceList();
    if (data.default_workspace) {
      // Always store the profile default for new sessions
      S._profileDefaultWorkspace = data.default_workspace;

      if (S.session && !sessionInProgress) {
        // Empty session (no messages yet) — safe to update it in place
        try {
          await api('/api/session/update', { method: 'POST', body: JSON.stringify({
            session_id: S.session.session_id,
            workspace: data.default_workspace,
            model: S.session.model,
          })});
          S.session.workspace = data.default_workspace;
        } catch (_) {}
      }
    }

    // ── Session ────────────────────────────────────────────────────────────
    _showAllProfiles = false;

    if (sessionInProgress) {
      // The current session has messages and belongs to the previous profile.
      // Start a new session for the new profile so nothing gets cross-tagged.
      await newSession(false);
      // Apply profile default workspace to the newly created session (fixes #424)
      if (S._profileDefaultWorkspace && S.session) {
        try {
          await api('/api/session/update', { method: 'POST', body: JSON.stringify({
            session_id: S.session.session_id,
            workspace: S._profileDefaultWorkspace,
            model: S.session.model,
          })});
          S.session.workspace = S._profileDefaultWorkspace;
        } catch (_) {}
      }
      updateWorkspaceChip();
      await renderSessionList();
      showToast(t('profile_switched_new_conversation', name));
    } else {
      // No messages yet — just refresh the list and topbar in place
      await renderSessionList();
      syncTopbar();
      showToast(t('profile_switched', name));
    }

    // ── Sidebar panels ─────────────────────────────────────────────────────
    if (_currentPanel === 'skills') await loadSkills();
    if (_currentPanel === 'memory') await loadMemory();
    if (_currentPanel === 'tasks') await loadCrons();
    if (_currentPanel === 'profiles') await loadProfilesPanel();
    if (_currentPanel === 'workspaces') await loadWorkspacesPanel();

  } catch (e) { showToast(t('switch_failed') + e.message); }
}

function toggleProfileForm() {
  const form = $('profileCreateForm');
  if (!form) return;
  form.style.display = form.style.display === 'none' ? '' : 'none';
  if (form.style.display !== 'none') {
    $('profileFormName').value = '';
    $('profileFormClone').checked = false;
    if ($('profileFormBaseUrl')) $('profileFormBaseUrl').value = '';
    if ($('profileFormApiKey')) $('profileFormApiKey').value = '';
    const errEl = $('profileFormError');
    if (errEl) errEl.style.display = 'none';
    $('profileFormName').focus();
  }
}

async function submitProfileCreate() {
  const name = ($('profileFormName').value || '').trim().toLowerCase();
  const cloneConfig = $('profileFormClone').checked;
  const errEl = $('profileFormError');
  if (!name) { errEl.textContent = t('name_required'); errEl.style.display = ''; return; }
  if (!/^[a-z0-9][a-z0-9_-]{0,63}$/.test(name)) { errEl.textContent = t('profile_name_rule'); errEl.style.display = ''; return; }
  try {
    const baseUrl = (($('profileFormBaseUrl') && $('profileFormBaseUrl').value) || '').trim();
    const apiKey = (($('profileFormApiKey') && $('profileFormApiKey').value) || '').trim();
    if (baseUrl && !/^https?:\/\//.test(baseUrl)) {
      errEl.textContent = t('profile_base_url_rule'); errEl.style.display = ''; return;
    }
    const payload = { name, clone_config: cloneConfig };
    if (baseUrl) payload.base_url = baseUrl;
    if (apiKey) payload.api_key = apiKey;
    await api('/api/profile/create', { method: 'POST', body: JSON.stringify(payload) });
    toggleProfileForm();
    await loadProfilesPanel();
    showToast(t('profile_created', name));
  } catch (e) {
    errEl.textContent = e.message || t('create_failed');
    errEl.style.display = '';
  }
}

async function deleteProfile(name) {
  const _delProf=await showConfirmDialog({title:t('profile_delete_confirm_title',name),message:t('profile_delete_confirm_message'),confirmLabel:t('delete_title'),danger:true,focusCancel:true});
  if(!_delProf) return;
  try {
    await api('/api/profile/delete', { method: 'POST', body: JSON.stringify({ name }) });
    await loadProfilesPanel();
    showToast(t('profile_deleted', name));
  } catch (e) { showToast(t('delete_failed') + e.message); }
}

// ── Gateway panel ──
let _gatewaysCache = null;

async function loadGatewaysPanel() {
  const panel = $('gatewaysPanel');
  if (!panel) return;
  try {
    const data = await api('/api/gateways');
    _gatewaysCache = data;
    panel.innerHTML = '';
    if (!data.gateways || !data.gateways.length) {
      panel.innerHTML = `<div style="padding:16px;color:var(--muted);font-size:12px">${esc(t('gateways_no_gateways'))}</div>`;
      return;
    }
    for (const gw of data.gateways) {
      const card = document.createElement('div');
      card.className = 'profile-card';
      const statusColor = gw.running ? 'var(--link)' : 'var(--muted)';
      const statusDot = gw.running
        ? `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--link);margin-right:6px"></span>`
        : `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--muted);margin-right:6px"></span>`;
      const statusText = gw.running ? esc(t('gateway_running')) : esc(t('gateway_stopped'));
      const typeMeta = gw.type ? `<span style="opacity:.7">${esc(gw.type)}</span>` : '';
      card.innerHTML = `
        <div class="profile-card-header">
          <div style="min-width:0;flex:1">
            <div class="profile-card-name">${statusDot}${esc(gw.name)}</div>
            <div class="profile-card-meta">${statusText}${typeMeta ? ' · ' + typeMeta : ''}</div>
          </div>
          <div class="profile-card-actions">
            ${gw.running
              ? `<button class="ws-action-btn danger" onclick="stopGateway('${esc(gw.name)}')" title="${esc(t('gateway_stop_title'))}">${esc(t('gateway_stop'))}</button>`
              : `<button class="ws-action-btn" onclick="startGateway('${esc(gw.name)}')" title="${esc(t('gateway_start_title'))}">${esc(t('gateway_start'))}</button>`
            }
            <button class="ws-action-btn" onclick="restartGateway('${esc(gw.name)}')" title="${esc(t('gateway_restart_title'))}">${esc(t('gateway_restart'))}</button>
          </div>
        </div>
        ${gw.info ? `<div style="font-size:11px;color:var(--muted);padding:4px 0 8px">${esc(gw.info)}</div>` : ''}`;
      panel.appendChild(card);
    }
    // Add gateway button
    const addBtn = document.createElement('button');
    addBtn.className = 'cron-btn run';
    addBtn.style.cssText = 'width:100%;margin-top:8px;padding:6px';
    addBtn.innerHTML = `+ ${esc(t('gateway_add'))}`;
    addBtn.onclick = () => showAddGatewayDialog();
    panel.appendChild(addBtn);
  } catch (e) {
    panel.innerHTML = `<div style="color:var(--accent);font-size:12px;padding:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function startGateway(name) {
  try {
    await api('/api/gateway/start', { method: 'POST', body: JSON.stringify({ name }) });
    showToast(t('gateway_started', name));
    await loadGatewaysPanel();
  } catch (e) { showToast(t('gateway_start_failed') + e.message); }
}

async function stopGateway(name) {
  try {
    await api('/api/gateway/stop', { method: 'POST', body: JSON.stringify({ name }) });
    showToast(t('gateway_stopped_msg', name));
    await loadGatewaysPanel();
  } catch (e) { showToast(t('gateway_stop_failed') + e.message); }
}

async function restartGateway(name) {
  try {
    await api('/api/gateway/restart', { method: 'POST', body: JSON.stringify({ name }) });
    showToast(t('gateway_restarted', name));
    await loadGatewaysPanel();
  } catch (e) { showToast(t('gateway_restart_failed') + e.message); }
}

async function showAddGatewayDialog() {
  const name = await showPromptDialog({
    title: t('gateway_add_title'),
    message: t('gateway_add_message'),
    confirmLabel: t('gateway_add'),
    placeholder: 'telegram'
  });
  if (!name) return;
  try {
    await api('/api/gateway/add', { method: 'POST', body: JSON.stringify({ name, type: 'telegram' }) });
    showToast(t('gateway_added', name));
    await loadGatewaysPanel();
  } catch (e) { showToast(t('gateway_add_failed') + e.message); }
}

// ── Memory panel ──
async function loadMemory(force) {
  const panel = $('memoryPanel');
  try {
    const data = await api('/api/memory');
    _memoryData = data;  // cache for edit form
    const fmtTime = ts => ts ? new Date(ts*1000).toLocaleString() : '';
    // Mask sensitive data (API tokens, passwords, keys) in displayed memory
    function maskSensitive(md) {
      if (!md) return md;
      return md
        .replace(/([A-Za-z0-9]{32,})\.(?:[A-Za-z0-9+\/=]{10,})/g, '***REDACTED***')  // API tokens like Z.ai
        .replace(/([Aa]pi[_\s]?[Tt]oken[:\s]*)\S{20,}/g, '$1***REDACTED***')  // API Token: xxx
        .replace(/([Aa]PI[_\s]?[Kk]ey[:\s]*)\S{20,}/g, '$1***REDACTED***')  // API Key: xxx
        .replace(/([Pp]assword[:\s]*)\S{8,}/g, '$1***REDACTED***')  // Password: xxx
        .replace(/([Tt]oken[:\s]*)[a-f0-9]{32,}/g, '$1***REDACTED***')  // token: hex
        .replace(/([Ss]ecret[:\s]*)\S{16,}/g, '$1***REDACTED***');  // secret: xxx
    }
    panel.innerHTML = `
      <div class="memory-section">
        <div class="memory-section-title">
          <span style="display:inline-flex;align-items:center;gap:6px">${li('brain',14)} ${esc(t('my_notes'))}</span>
          <span class="memory-mtime">${fmtTime(data.memory_mtime)}</span>
        </div>
        ${data.memory
          ? `<div class="memory-content preview-md">${renderMd(maskSensitive(data.memory))}</div>`
          : `<div class="memory-empty">${esc(t('no_notes_yet'))}</div>`}
      </div>
      <div class="memory-section">
        <div class="memory-section-title">
          <span style="display:inline-flex;align-items:center;gap:6px">${li('user',14)} ${esc(t('user_profile'))}</span>
          <span class="memory-mtime">${fmtTime(data.user_mtime)}</span>
        </div>
        ${data.user
          ? `<div class="memory-content preview-md">${renderMd(maskSensitive(data.user))}</div>`
          : `<div class="memory-empty">${esc(t('no_profile_yet'))}</div>`}
      </div>`;
  } catch(e) { panel.innerHTML = `<div style="color:var(--accent);font-size:12px">${esc(t('error_prefix'))}${esc(e.message)}</div>`; }
}

// Drag and drop
const wrap=$('composerWrap');let dragCounter=0;
document.addEventListener('dragover',e=>e.preventDefault());
document.addEventListener('dragenter',e=>{e.preventDefault();if(e.dataTransfer.types.includes('Files')){dragCounter++;wrap.classList.add('drag-over');}});
document.addEventListener('dragleave',e=>{dragCounter--;if(dragCounter<=0){dragCounter=0;wrap.classList.remove('drag-over');}});
document.addEventListener('drop',e=>{e.preventDefault();dragCounter=0;wrap.classList.remove('drag-over');const files=Array.from(e.dataTransfer.files);if(files.length){addFiles(files);$('msg').focus();}});

// ── Settings panel ───────────────────────────────────────────────────────────

let _settingsDirty = false;
let _settingsThemeOnOpen = null; // track theme at open time for discard revert
let _settingsSection = 'conversation';

function switchSettingsSection(name){
  const section=(name==='preferences'||name==='system'||name==='gateways'||name==='logs')?name:'conversation';
  _settingsSection=section;
  const map={conversation:'Conversation',preferences:'Preferences',system:'System',gateways:'Gateways',logs:'Logs'};
  ['conversation','preferences','system','gateways','logs'].forEach(key=>{
    const tab=$('settingsTab'+map[key]);
    const pane=$('settingsPane'+map[key]);
    const active=key===section;
    if(tab){
      tab.classList.toggle('active',active);
      tab.setAttribute('aria-selected',active?'true':'false');
    }
    if(pane) pane.classList.toggle('active',active);
  });
  if(section==='logs') loadLogsPanel();
}

function _syncHermesPanelSessionActions(){
  const hasSession=!!S.session;
  const visibleMessages=hasSession?(S.messages||[]).filter(m=>m&&m.role&&m.role!=='tool').length:0;
  const title=hasSession?(S.session.title||t('untitled')):t('active_conversation_none');
  const meta=$('hermesSessionMeta');
  if(meta){
    meta.textContent=hasSession
      ? t('active_conversation_meta', title, visibleMessages)
      : t('active_conversation_none');
  }
  const setDisabled=(id,disabled)=>{
    const el=$(id);
    if(!el)return;
    el.disabled=!!disabled;
    el.classList.toggle('disabled',!!disabled);
  };
  setDisabled('btnDownload',!hasSession||visibleMessages===0);
  setDisabled('btnExportJSON',!hasSession);
  setDisabled('btnClearConvModal',!hasSession||visibleMessages===0);
}

function toggleSettings(){
  const overlay=$('settingsOverlay');
  if(!overlay) return;
  if(overlay.style.display==='none'){
    _settingsDirty = false;
    _settingsThemeOnOpen = localStorage.getItem('hermes-theme') || document.documentElement.dataset.theme || 'dark';
    _settingsSection = 'conversation';
    overlay.style.display='';
    loadSettingsPanel();
  } else {
    _closeSettingsPanel();
  }
}

function _resetSettingsPanelState(){
  _settingsSection = 'conversation';
  switchSettingsSection('conversation');
  const bar=$('settingsUnsavedBar');
  if(bar) bar.style.display='none';
}

function _hideSettingsPanel(){
  const overlay=$('settingsOverlay');
  if(!overlay) return;
  _resetSettingsPanelState();
  overlay.style.display='none';
}

// Close with unsaved-changes check. If dirty, show a confirm dialog.
function _closeSettingsPanel(){
  if(!_settingsDirty){
    // Nothing changed -- revert any live preview and close
    _revertSettingsPreview();
    _hideSettingsPanel();
    return;
  }
  // Dirty -- show inline confirm bar
  _showSettingsUnsavedBar();
}

// Revert live DOM/localStorage to what they were when the panel opened
function _revertSettingsPreview(){
  if(_settingsThemeOnOpen){
    localStorage.setItem('hermes-theme', _settingsThemeOnOpen);
    if(typeof _applyTheme==='function') _applyTheme(_settingsThemeOnOpen);
    else document.documentElement.dataset.theme = _settingsThemeOnOpen;
  }
}

// Show the "Unsaved changes" bar inside the settings panel
function _showSettingsUnsavedBar(){
  let bar = $('settingsUnsavedBar');
  if(bar){ bar.style.display=''; return; }
  // Create it
  bar = document.createElement('div');
  bar.id = 'settingsUnsavedBar';
  bar.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;background:rgba(233,69,96,.12);border:1px solid rgba(233,69,96,.3);border-radius:8px;padding:10px 14px;margin:0 0 12px;font-size:13px;';
  bar.innerHTML = `<span style="color:var(--text)">${esc(t('settings_unsaved_changes'))}</span>`
    + '<span style="display:flex;gap:8px">'
    + `<button onclick="_discardSettings()" style="padding:5px 12px;border-radius:6px;border:1px solid var(--border2);background:rgba(255,255,255,.06);color:var(--muted);cursor:pointer;font-size:12px;font-weight:600">${esc(t('discard'))}</button>`
    + `<button onclick="saveSettings(true)" style="padding:5px 12px;border-radius:6px;border:none;background:var(--accent);color:#fff;cursor:pointer;font-size:12px;font-weight:600">${esc(t('save'))}</button>`
    + '</span>';
  const body = document.querySelector('.settings-main') || document.querySelector('.settings-body') || document.querySelector('.settings-panel');
  if(body) body.prepend(bar);
}

function _discardSettings(){
  _revertSettingsPreview();
  _settingsDirty = false;
  _hideSettingsPanel();
}

// Mark settings as dirty whenever anything changes
function _markSettingsDirty(){
  _settingsDirty = true;
}

async function loadSettingsPanel(){
  try{
    const settings=await api('/api/settings');
    const resolvedLanguage=(typeof resolvePreferredLocale==='function')
      ? resolvePreferredLocale(settings.language, localStorage.getItem('hermes-lang'))
      : (settings.language || localStorage.getItem('hermes-lang') || 'en');
    // Keep settings modal and current page strings in sync with the resolved locale.
    if(typeof setLocale==='function'){
      setLocale(resolvedLanguage);
      if(typeof applyLocaleToDOM==='function') applyLocaleToDOM();
    }
    // Populate model dropdown from /api/models
    const modelSel=$('settingsModel');
    if(modelSel){
      modelSel.innerHTML='';
      try{
        const models=await api('/api/models');
        for(const g of (models.groups||[])){
          const og=document.createElement('optgroup');
          og.label=g.provider;
          for(const m of g.models){
            const opt=document.createElement('option');
            opt.value=m.id;opt.textContent=m.label;
            og.appendChild(opt);
          }
          modelSel.appendChild(og);
        }
      }catch(e){}
      modelSel.value=settings.default_model||'';
      modelSel.addEventListener('change',_markSettingsDirty,{once:false});
    }
    // Send key preference
    const sendKeySel=$('settingsSendKey');
    if(sendKeySel){sendKeySel.value=settings.send_key||'enter';sendKeySel.addEventListener('change',_markSettingsDirty,{once:false});}
    // Theme preference
    const themeSel=$('settingsTheme');
    if(themeSel){themeSel.value=settings.theme||'dark';themeSel.addEventListener('change',_markSettingsDirty,{once:false});}
    // Language preference — populate from LOCALES bundle
    const langSel=$('settingsLanguage');
    if(langSel){
      langSel.innerHTML='';
      if(typeof LOCALES!=='undefined'){
        for(const [code,bundle] of Object.entries(LOCALES)){
          const opt=document.createElement('option');
          opt.value=code;opt.textContent=bundle._label||code;
          langSel.appendChild(opt);
        }
      }
      langSel.value=resolvedLanguage;
      langSel.addEventListener('change',_markSettingsDirty,{once:false});
    }
    const showUsageCb=$('settingsShowTokenUsage');
    if(showUsageCb){showUsageCb.checked=!!settings.show_token_usage;showUsageCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const showCliCb=$('settingsShowCliSessions');
    if(showCliCb){showCliCb.checked=!!settings.show_cli_sessions;showCliCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const syncCb=$('settingsSyncInsights');
    if(syncCb){syncCb.checked=!!settings.sync_to_insights;syncCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const updateCb=$('settingsCheckUpdates');
    if(updateCb){updateCb.checked=settings.check_for_updates!==false;updateCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const soundCb=$('settingsSoundEnabled');
    if(soundCb){soundCb.checked=!!settings.sound_enabled;soundCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const notifCb=$('settingsNotificationsEnabled');
    if(notifCb){notifCb.checked=!!settings.notifications_enabled;notifCb.addEventListener('change',_markSettingsDirty,{once:false});}
    const bubbleCb=$('settingsBubbleLayout');
    if(bubbleCb){bubbleCb.checked=!!settings.bubble_layout;bubbleCb.addEventListener('change',_markSettingsDirty,{once:false});}
    // Bot name
    const botNameField=$('settingsBotName');
    if(botNameField){botNameField.value=settings.bot_name||'Hermes';botNameField.addEventListener('input',_markSettingsDirty,{once:false});}
    // Password field: always blank (we don't send hash back)
    const pwField=$('settingsPassword');
    if(pwField){pwField.value='';pwField.addEventListener('input',_markSettingsDirty,{once:false});}
    // Show auth buttons only when auth is active
    try{
      const authStatus=await api('/api/auth/status');
      _setSettingsAuthButtonsVisible(!!authStatus.auth_enabled);
    }catch(e){}
    _syncHermesPanelSessionActions();
    switchSettingsSection(_settingsSection);
    if(_settingsSection==='gateways') loadGatewaysPanel();
  }catch(e){
    showToast(t('settings_load_failed')+e.message);
  }
}

function _setSettingsAuthButtonsVisible(active){
  const signOutBtn=$('btnSignOut');
  if(signOutBtn) signOutBtn.style.display=active?'':'none';
  const disableBtn=$('btnDisableAuth');
  if(disableBtn) disableBtn.style.display=active?'':'none';
}

function _applySavedSettingsUi(saved, body, opts){
  const {sendKey,showTokenUsage,showCliSessions,theme,language}=opts;
  window._sendKey=sendKey||'enter';
  window._showTokenUsage=showTokenUsage;
  window._showCliSessions=showCliSessions;
  window._soundEnabled=body.sound_enabled;
  window._notificationsEnabled=body.notifications_enabled;
  window._botName=body.bot_name||'Hermes';
  document.body.classList.toggle('bubble-layout', !!body.bubble_layout);
  if(typeof applyBotName==='function') applyBotName();
  if(typeof setLocale==='function') setLocale(language);
  if(typeof applyLocaleToDOM==='function') applyLocaleToDOM();
  if(typeof startGatewaySSE==='function'){
    if(showCliSessions) startGatewaySSE();
    else if(typeof stopGatewaySSE==='function') stopGatewaySSE();
  }
  _setSettingsAuthButtonsVisible(!!saved.auth_enabled);
  _settingsDirty=false;
  _settingsThemeOnOpen=theme;
  const bar=$('settingsUnsavedBar');
  if(bar) bar.style.display='none';
  renderMessages();
  if(typeof syncTopbar==='function') syncTopbar();
  if(typeof renderSessionList==='function') renderSessionList();
}

async function saveSettings(andClose){
  const model=($('settingsModel')||{}).value;
  const sendKey=($('settingsSendKey')||{}).value;
  const showTokenUsage=!!($('settingsShowTokenUsage')||{}).checked;
  const showCliSessions=!!($('settingsShowCliSessions')||{}).checked;
  const pw=($('settingsPassword')||{}).value;
  const theme=($('settingsTheme')||{}).value||'dark';
  const language=($('settingsLanguage')||{}).value||'en';
  const body={};
  if(model) body.default_model=model;

  if(sendKey) body.send_key=sendKey;
  body.theme=theme;
  body.language=language;
  body.show_token_usage=showTokenUsage;
  body.show_cli_sessions=showCliSessions;
  body.sync_to_insights=!!($('settingsSyncInsights')||{}).checked;
  body.check_for_updates=!!($('settingsCheckUpdates')||{}).checked;
  body.sound_enabled=!!($('settingsSoundEnabled')||{}).checked;
  body.notifications_enabled=!!($('settingsNotificationsEnabled')||{}).checked;
  body.bubble_layout=!!($('settingsBubbleLayout')||{}).checked;
  document.body.classList.toggle('bubble-layout', body.bubble_layout);
  const botName=(($('settingsBotName')||{}).value||'').trim();
  body.bot_name=botName||'Hermes';
  // Password: only act if the field has content; blank = leave auth unchanged
  if(pw && pw.trim()){
    try{
      const saved=await api('/api/settings',{method:'POST',body:JSON.stringify({...body,_set_password:pw.trim()})});
      _applySavedSettingsUi(saved, body, {sendKey,showTokenUsage,showCliSessions,theme,language});
      showToast(t(saved.auth_just_enabled?'settings_saved_pw':'settings_saved_pw_updated'));
      _hideSettingsPanel();
      return;
    }catch(e){showToast(t('settings_save_failed')+e.message);return;}
  }
  try{
    const saved=await api('/api/settings',{method:'POST',body:JSON.stringify(body)});
    _applySavedSettingsUi(saved, body, {sendKey,showTokenUsage,showCliSessions,theme,language});
    showToast(t('settings_saved'));
    _hideSettingsPanel();
  }catch(e){
    showToast(t('settings_save_failed')+e.message);
  }
}

async function signOut(){
  try{
    await api('/api/auth/logout',{method:'POST',body:'{}'});
    window.location.href='/login';
  }catch(e){
    showToast(t('sign_out_failed')+e.message);
  }
}

async function disableAuth(){
  const _disAuth=await showConfirmDialog({title:t('disable_auth_confirm_title'),message:t('disable_auth_confirm_message'),confirmLabel:t('disable'),danger:true,focusCancel:true});
  if(!_disAuth) return;
  try{
    await api('/api/settings',{method:'POST',body:JSON.stringify({_clear_password:true})});
    showToast(t('auth_disabled'));
    // Hide both auth buttons since auth is now off
    const disableBtn=$('btnDisableAuth');
    if(disableBtn) disableBtn.style.display='none';
    const signOutBtn=$('btnSignOut');
    if(signOutBtn) signOutBtn.style.display='none';
  }catch(e){
    showToast(t('disable_auth_failed')+e.message);
  }
}

// Close settings on overlay click (not panel click) -- with unsaved-changes check
document.addEventListener('click',e=>{
  const overlay=$('settingsOverlay');
  if(overlay&&e.target===overlay) _closeSettingsPanel();
});

// ── Cron completion alerts ────────────────────────────────────────────────────

let _cronPollSince=Date.now()/1000;  // track from page load
let _cronPollTimer=null;
let _cronUnreadCount=0;

function startCronPolling(){
  if(_cronPollTimer) return;
  _cronPollTimer=setInterval(async()=>{
    if(document.hidden) return;  // don't poll when tab is in background
    try{
      const data=await api(`/api/crons/recent?since=${_cronPollSince}`);
      if(data.completions&&data.completions.length>0){
        for(const c of data.completions){
          showToast(t('cron_completion_status', c.name, c.status==='error' ? t('status_failed') : t('status_completed')),4000);
          _cronPollSince=Math.max(_cronPollSince,c.completed_at);
        }
        _cronUnreadCount+=data.completions.length;
        updateCronBadge();
      }
    }catch(e){}
  },30000);
}

function updateCronBadge(){
  const tab=document.querySelector('.nav-tab[data-panel="tasks"]');
  if(!tab) return;
  let badge=tab.querySelector('.cron-badge');
  if(_cronUnreadCount>0){
    if(!badge){
      badge=document.createElement('span');
      badge.className='cron-badge';
      tab.style.position='relative';
      tab.appendChild(badge);
    }
    badge.textContent=_cronUnreadCount>9?'9+':_cronUnreadCount;
    badge.style.display='';
  }else if(badge){
    badge.style.display='none';
  }
}

// Clear cron badge when Tasks tab is opened
const _origSwitchPanel=switchPanel;
switchPanel=async function(name){
  if(name==='tasks'){_cronUnreadCount=0;updateCronBadge();}
  return _origSwitchPanel(name);
};

// Start polling on page load
startCronPolling();

// ── Background agent error tracking ──────────────────────────────────────────

const _backgroundErrors=[];  // {session_id, title, message, ts}

function trackBackgroundError(sessionId, title, message){
  // Only track if user is NOT currently viewing this session
  if(S.session&&S.session.session_id===sessionId) return;
  _backgroundErrors.push({session_id:sessionId, title:title||t('untitled'), message, ts:Date.now()});
  showErrorBanner();
}

function showErrorBanner(){
  let banner=$('bgErrorBanner');
  if(!banner){
    banner=document.createElement('div');
    banner.id='bgErrorBanner';
    banner.className='bg-error-banner';
    const msgs=document.querySelector('.messages');
    if(msgs) msgs.parentNode.insertBefore(banner,msgs);
    else document.body.appendChild(banner);
  }
  const latest=_backgroundErrors[0];  // FIFO: show oldest (first) error
  if(!latest){banner.style.display='none';return;}
  const count=_backgroundErrors.length;
  const msg=count>1?t('bg_error_multi',count):t('bg_error_single',latest.title);
  banner.innerHTML=`<span>\u26a0 ${esc(msg)}</span><div style="display:flex;gap:6px;flex-shrink:0"><button class="reconnect-btn" onclick="navigateToErrorSession()">${esc(t('view'))}</button><button class="reconnect-btn" onclick="dismissErrorBanner()">${esc(t('dismiss'))}</button></div>`;
  banner.style.display='';
}

function navigateToErrorSession(){
  const latest=_backgroundErrors.shift();  // FIFO: show oldest error first
  if(latest){
    loadSession(latest.session_id);renderSessionList();
  }
  if(_backgroundErrors.length===0) dismissErrorBanner();
  else showErrorBanner();
}

function dismissErrorBanner(){
  _backgroundErrors.length=0;
  const banner=$('bgErrorBanner');
  if(banner) banner.style.display='none';
}

// ── Mission Control panel ──
let _mcInterval = null;

async function loadMissionControl() {
  clearInterval(_mcInterval);
  await refreshMC();
  _mcInterval = setInterval(refreshMC, 15000);
}

async function refreshMC() {
  const [statusRes, tasksRes, feedRes, prioritiesRes] = await Promise.all([
    api('/api/mc/status'),
    api('/api/mc/tasks'),
    api('/api/mc/feed?limit=10'),
    api('/api/mc/priorities'),
  ]);

  const status = statusRes;
  const tasks = tasksRes.tasks || [];
  const feed = feedRes.feed || [];
  const priorities = prioritiesRes.priorities || [];

  // ── Health Badge ──
  const h = status.dashboard_health || 'empty';
  const healthColors = { healthy: '#4caf50', active: '#ff9800', warning: '#ff5722', empty: '#9e9e9e', ok: '#4caf50' };
  const healthLabels = { healthy: 'Healthy', active: 'Active', warning: 'Warning', empty: 'No Data', ok: 'OK' };
  const $hb = $('mcHealthBadge');
  $hb.textContent = healthLabels[h] || 'Unknown';
  $hb.style.color = healthColors[h] || '#9e9e9e';
  $hb.style.background = (healthColors[h] || '#9e9e9e') + '22';

  // ── Stats Cards ──
  const totalTasks = tasks.length;
  const doneTasks = tasks.filter(t => t.status === 'done').length;
  const progressPct = totalTasks > 0 ? Math.round((doneTasks / totalTasks) * 100) : 0;

  $('mcTasksCount').textContent = `${doneTasks}/${totalTasks}`;
  $('mcTasksLabel').textContent = `${doneTasks} done · ${totalTasks - doneTasks} open`;
  $('mcPrioritiesCount').textContent = priorities.length;
  $('mcPrioritiesLabel').textContent = `${priorities.filter(p => p.done).length} done`;

  // ── Progress Bar ──
  $('mcProgressBar').style.width = progressPct + '%';
  $('mcProgressPct').textContent = progressPct + '%';

  // ── Priority Filters ──
  const priorityMap = {};
  priorities.forEach(p => { priorityMap[p.id] = p; });

  const priorityEmoji = { 1: '🔴', 2: '🟠', 3: '🟡', 4: '🟢' };
  const pfEl = $('mcPriorityFilters');
  pfEl.innerHTML = [
    `<button onclick="filterMCTasks('all')" style="background:var(--input-bg);border:1px solid var(--border);border-radius:20px;padding:3px 10px;font-size:10px;cursor:pointer;color:var(--text)" id="mcFilterAll">All</button>`,
    ...priorities.map(p =>
      `<button onclick="filterMCTasks(${p.id})" style="background:var(--input-bg);border:1px solid var(--border);border-radius:20px;padding:3px 10px;font-size:10px;cursor:pointer;color:var(--text);display:flex;align-items:center;gap:3px" id="mcFilter${p.id}">${priorityEmoji[p.id] || '•'} ${esc(p.name)}</button>`
    )
  ].join('');

  // ── Tasks List ──
  const listEl = $('mcTasksList');
  const activeFilter = window._mcPriorityFilter || 'all';
  const filteredTasks = activeFilter === 'all' ? tasks : tasks.filter(t => t.priority === activeFilter);

  if (filteredTasks.length === 0) {
    listEl.innerHTML = '<div style="color:var(--muted);font-size:11px;text-align:center;padding:20px">No tasks yet.<br><span style="font-size:10px">Add one above ↑</span></div>';
  } else {
    const statusMeta = {
      backlog: { icon: '○', color: 'var(--muted)', label: 'Backlog' },
      progress: { icon: '◐', color: '#ff9800', label: 'In Progress' },
      done: { icon: '●', color: '#4caf50', label: 'Done' }
    };
    listEl.innerHTML = filteredTasks.map(t => {
      const meta = statusMeta[t.status] || statusMeta.backlog;
      const p = priorityMap[t.priority] || { name: 'Unknown', color: '#808080' };
      const emoji = priorityEmoji[t.priority] || '•';
      return `<div style="background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:6px">
        <div style="display:flex;align-items:flex-start;gap:8px">
          <span style="font-size:16px;flex-shrink:0;cursor:pointer" onclick="toggleMCTask(${t.id},'${t.status}')" title="Toggle status">${meta.icon}</span>
          <div style="flex:1;min-width:0">
            <div style="font-size:12px;color:var(--text);${t.status === 'done' ? 'text-decoration:line-through;opacity:0.5' : ''};word-break:break-word">${esc(t.title)}</div>
            <div style="display:flex;align-items:center;gap:6px;margin-top:4px">
              <span style="font-size:10px;${t.status === 'done' ? 'color:#4caf50' : t.status === 'progress' ? 'color:#ff9800' : 'color:var(--muted)'}">${meta.label}</span>
              <span style="width:6px;height:6px;border-radius:50%;background:${p.color};flex-shrink:0"></span>
              <span style="font-size:10px;color:var(--muted)">${emoji} ${esc(p.name)}</span>
            </div>
          </div>
          <select onchange="updateMCTask(${t.id}, this.value)" style="background:var(--input-bg);border:1px solid var(--border);border-radius:6px;padding:3px 6px;font-size:9px;color:var(--text);cursor:pointer;flex-shrink:0">
            <option value="backlog" ${t.status === 'backlog' ? 'selected' : ''}>○ Backlog</option>
            <option value="progress" ${t.status === 'progress' ? 'selected' : ''}>◐ Progress</option>
            <option value="done" ${t.status === 'done' ? 'selected' : ''}>● Done</option>
          </select>
        </div>
      </div>`;
    }).join('');
  }

  // ── Feed ──
  const feedEl = $('mcFeed');
  if (feed.length === 0) {
    feedEl.innerHTML = '<div style="opacity:0.5;font-size:10px">No recent activity</div>';
  } else {
    feedEl.innerHTML = feed.map(f => {
      const d = new Date(f.timestamp);
      const time = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      return `<div style="padding:2px 0;border-bottom:1px solid var(--border)">
        <span style="color:var(--text);font-size:10px">${esc(f.event)}</span>
        <span style="opacity:0.5;font-size:9px;margin-left:6px">${time}</span>
      </div>`;
    }).join('');
  }
}

function toggleMCTask(id, currentStatus) {
  const next = currentStatus === 'done' ? 'backlog' : currentStatus === 'backlog' ? 'progress' : 'done';
  updateMCTask(id, next);
}

function filterMCTasks(priorityId) {
  window._mcPriorityFilter = priorityId;
  refreshMC();
}

async function createMCTask() {
  const title = $('mcNewTaskTitle').value.trim();
  if (!title) return;
  const priority = parseInt($('mcNewTaskPriority').value);
  const status = $('mcNewTaskStatus').value;
  $('mcNewTaskTitle').value = '';
  await api('/api/mc/task/create', { title, priority, status });
  await refreshMC();
}

async function updateMCTask(id, status) {
  await api('/api/mc/task/update', { id, status });
  await refreshMC();
}

// ── Priority Management ──
async function createMCPriority() {
  const name = prompt('Priority name:');
  if (!name) return;
  const color = prompt('Color (hex, e.g. #ff0000):', '#808080');
  if (!color) return;
  await api('/api/mc/priority/create', { name, color });
  await refreshMC();
}

async function deleteMCPriority(id) {
  if (!confirm('Delete this priority?')) return;
  await api('/api/mc/priority/delete', { id });
  await refreshMC();
}

// ── Agents Panel (Rose + Tier-2) ─────────────────────────────────────────────
let _agentsInterval = null;
let _selectedAgent = null;
let _agentTab = 'overview'; // current tab in detail overlay

const STATUS_COLORS = { active: '#4caf50', idle: '#ff9800', offline: '#9e9e9e' };
const STATUS_LABELS = { active: 'Active', idle: 'Idle', offline: 'Offline' };

function _relTime(ts) {
  if (!ts) return 'N/A';
  try {
    const d = new Date(ts);
    const diff = (Date.now() - d.getTime()) / 1000;
    if (diff < 60) return 'Just now';
    if (diff < 3600) return `${Math.floor(diff/60)}m ago`;
    if (diff < 86400) return `${Math.floor(diff/3600)}h ago`;
    return d.toLocaleDateString();
  } catch { return ts; }
}

async function loadAgentsPanel() {
  clearInterval(_agentsInterval);
  await refreshAgents();
  _agentsInterval = setInterval(refreshAgents, 15000);
}

async function refreshAgents() {
  try {
    const data = await api('/api/agents');
    renderAgentsList(data.agents || []);
  } catch(e) {
    const box = $('agentsList');
    if (box) box.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

function renderAgentsList(agents) {
  const box = $('agentsList');
  if (!box) return;

  const html = agents.map(a => {
    const color = STATUS_COLORS[a.status] || STATUS_COLORS.offline;
    const label = STATUS_LABELS[a.status] || 'Offline';
    const tierBadge = a.tier === 'orchestrator'
      ? '<span class="agent-tier-badge tier-1">🌹 Tier-1</span>'
      : '<span class="agent-tier-badge tier-2">Tier-2</span>';
    const inboxBadge = a.inbox_count > 0
      ? `<span class="agent-inbox-badge">${a.inbox_count}</span>`
      : '';
    const disabled = a.disabled ? 'opacity:0.5;' : '';
    return `<div class="agent-card${a.disabled ? ' agent-card-disabled' : ''}" onclick="openAgentDetail('${a.id}')" style="cursor:pointer;${disabled}">
      <div class="agent-card-left">
        <span style="font-size:28px;line-height:1">${a.emoji}</span>
      </div>
      <div class="agent-card-body">
        <div class="agent-card-name">${esc(a.name)}</div>
        <div class="agent-card-domain">${esc(a.domain)}</div>
        <div class="agent-card-meta">
          <span class="agent-status-dot" style="background:${color}"></span>
          <span style="color:${color};font-size:10px;font-weight:600">${label}</span>
          ${tierBadge}
        </div>
        <div style="font-size:9px;color:var(--muted);margin-top:2px">${_relTime(a.last_activity)}</div>
      </div>
      <div class="agent-card-right">
        ${inboxBadge}
        <span style="opacity:0.3;font-size:18px">›</span>
      </div>
    </div>`;
  }).join('');

  box.innerHTML = html;
}

async function openAgentDetail(agentId) {
  _selectedAgent = agentId;
  _agentTab = 'overview';

  // Highlight card
  document.querySelectorAll('.agent-card').forEach(el => el.classList.remove('selected'));
  const cards = document.querySelectorAll('.agent-card');
  const agentsData = await api('/api/agents');
  const idx = agentsData.agents.findIndex(a => a.id === agentId);
  if (cards[idx]) cards[idx].classList.add('selected');

  const box = $('agentInbox');

  // Fetch full agent data
  let agent;
  try {
    agent = await api(`/api/agents/${agentId}`);
  } catch(e) {
    box.innerHTML = `<div style="padding:16px;color:var(--accent)">Error loading agent: ${esc(e.message)}</div><div style="padding:16px"><button onclick="closeAgentInbox()" class="cron-btn">Close</button></div>`;
    box.style.display = 'block';
    return;
  }

  const color = STATUS_COLORS[agent.status] || STATUS_COLORS.offline;
  const tierBadge = agent.tier === 'orchestrator'
    ? '<span class="agent-tier-badge tier-1">🌹 Tier-1</span>'
    : '<span class="agent-tier-badge tier-2">Tier-2</span>';
  const lastAct = agent.last_activity ? new Date(agent.last_activity).toLocaleString() : 'N/A';
  const canEdit = agentId !== 'rose';

  box.innerHTML = `
    <div class="agent-detail-header">
      <div class="agent-detail-title">
        <span style="font-size:28px">${agent.emoji}</span>
        <div>
          <div style="font-weight:700;font-size:15px">${esc(agent.name)}</div>
          <div style="font-size:10px;opacity:0.6">${esc(agent.domain)}</div>
          <div style="margin-top:4px">${tierBadge}</div>
        </div>
      </div>
      <button onclick="closeAgentInbox()" style="background:rgba(255,255,255,.06);border:1px solid var(--border);border-radius:8px;padding:6px 10px;cursor:pointer;color:var(--muted);font-size:12px;flex-shrink:0">× Close</button>
    </div>

    <div class="agent-detail-status">
      <div class="agent-status-row">
        <span class="agent-status-dot lg" style="background:${color}"></span>
        <span style="color:${color};font-weight:600;font-size:12px">${STATUS_LABELS[agent.status] || 'Offline'}</span>
        ${agent.pid ? `<span style="font-size:9px;color:var(--muted);margin-left:4px">PID ${agent.pid}</span>` : ''}
      </div>
      <div style="font-size:10px;color:var(--muted)">Last active: ${esc(lastAct)}</div>
      ${agent.default_model ? `<div style="font-size:10px;color:var(--muted)">Model: ${esc(agent.default_model)}</div>` : ''}
    </div>

    ${agentId !== 'rose' ? `
    <div class="agent-toggle-row">
      <span style="font-size:11px;color:var(--muted)">Agent ${agent.disabled ? 'disabled' : 'enabled'}</span>
      <button class="agent-toggle-btn${agent.disabled ? '' : ' on'}" onclick="toggleAgentEnabled('${agentId}', ${!agent.disabled})">
        <span class="agent-toggle-knob"></span>
      </button>
    </div>
    ` : ''}

    <div class="agent-detail-actions">
      <button class="agent-action-btn primary" onclick="chatWithAgent('${agentId}')">
        💬 Direct Chat
      </button>
    </div>

    <div class="agent-tabs">
      <button class="agent-tab${_agentTab==='overview'?' active':''}" onclick="switchAgentTab('overview')">Overview</button>
      <button class="agent-tab${_agentTab==='soul'?' active':''}" onclick="switchAgentTab('soul')">soul.md</button>
      <button class="agent-tab${_agentTab==='memory'?' active':''}" onclick="switchAgentTab('memory')">memory.md</button>
      <button class="agent-tab${_agentTab==='inbox'?' active':''}" onclick="switchAgentTab('inbox')">
        Inbox${agent.inbox_count > 0 ? ` <span class="agent-inbox-badge sm">${agent.inbox_count}</span>` : ''}
      </button>
      <button class="agent-tab${_agentTab==='activity'?' active':''}" onclick="switchAgentTab('activity')">Activity</button>
      <button class="agent-tab${_agentTab==='errors'?' active':''}" onclick="switchAgentTab('errors')">Errors</button>
      <button class="agent-tab${_agentTab==='chat'?' active':''}" onclick="switchAgentTab('chat')">Chat History</button>
      <button class="agent-tab${_agentTab==='tasks'?' active':''}" onclick="switchAgentTab('tasks')">Tasks</button>
      <button class="agent-tab${_agentTab==='bus'?' active':''}" onclick="switchAgentTab('bus')">Message Bus</button>
      <button class="agent-tab${_agentTab==='usage'?' active':''}" onclick="switchAgentTab('usage')">Usage</button>
      <button class="agent-tab${_agentTab==='topology'?' active':''}" onclick="switchAgentTab('topology')">Topology</button>
    </div>

    <div id="agentTabContent" class="agent-tab-content">
      <div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Loading...</div>
    </div>
  `;
  box.style.display = 'block';

  // Load first tab content
  await switchAgentTab('overview');
}

async function switchAgentTab(tab) {
  _agentTab = tab;
  const agentId = _selectedAgent;
  if (!agentId) return;

  // Update tab buttons
  document.querySelectorAll('.agent-tab').forEach((el, i) => {
    const tabs = ['overview', 'soul', 'memory', 'inbox', 'activity', 'errors', 'chat', 'tasks', 'bus', 'usage', 'topology'];
    el.classList.toggle('active', tabs[i] === tab);
  });

  const content = $('agentTabContent');

  switch(tab) {
    case 'overview':
      await loadAgentOverview(agentId, content);
      break;
    case 'soul':
      await loadAgentSoul(agentId, content);
      break;
    case 'memory':
      await loadAgentMemory(agentId, content);
      break;
    case 'inbox':
      await loadAgentInboxTab(agentId, content);
      break;
    case 'activity':
      await loadAgentActivity(agentId, content);
      break;
    case 'errors':
      await loadAgentErrors(agentId, content);
      break;
    case 'chat':
      await loadAgentChatHistory(agentId, content);
      break;
    case 'tasks':
      await loadAgentTasks(agentId, content);
      break;
    case 'bus':
      await loadAgentBus(agentId, content);
      break;
    case 'usage':
      await loadAgentUsage(agentId, content);
      break;
    case 'topology':
      await loadAgentTopology(agentId, content);
      break;
  }
}

async function loadAgentOverview(agentId, content) {
  try {
    const agent = await api(`/api/agents/${agentId}`);
    const color = STATUS_COLORS[agent.status] || STATUS_COLORS.offline;
    content.innerHTML = `
      <div class="agent-overview">
        <div class="agent-info-row">
          <span class="agent-info-label">Status</span>
          <span style="display:flex;align-items:center;gap:4px">
            <span class="agent-status-dot" style="background:${color}"></span>
            <span style="color:${color};font-size:11px;font-weight:600">${STATUS_LABELS[agent.status] || 'Offline'}</span>
          </span>
        </div>
        <div class="agent-info-row">
          <span class="agent-info-label">Domain</span>
          <span style="font-size:11px">${esc(agent.domain)}</span>
        </div>
        <div class="agent-info-row">
          <span class="agent-info-label">Tier</span>
          <span style="font-size:11px">${agent.tier === 'orchestrator' ? '🌹 Orchestrator (Tier-1)' : 'Tier-2 Domain Agent'}</span>
        </div>
        <div class="agent-info-row">
          <span class="agent-info-label">Last Active</span>
          <span style="font-size:11px">${agent.last_activity ? new Date(agent.last_activity).toLocaleString() : 'N/A'}</span>
        </div>
        ${agent.default_model ? `
        <div class="agent-info-row">
          <span class="agent-info-label">Model</span>
          <span style="font-size:11px;font-family:monospace">${esc(agent.default_model)}</span>
        </div>` : ''}
        ${agent.inbox_count > 0 ? `
        <div class="agent-info-row">
          <span class="agent-info-label">Inbox</span>
          <span style="font-size:11px"><span class="agent-inbox-badge">${agent.inbox_count}</span> unread messages</span>
        </div>` : ''}
        ${agent.pid ? `
        <div class="agent-info-row">
          <span class="agent-info-label">Process</span>
          <span style="font-size:11px;font-family:monospace">PID ${agent.pid}</span>
        </div>` : ''}
      </div>
    `;

    // Fetch health metrics in parallel
    try {
      const health = await api(`/api/agents/${agentId}/health`);
      if (!health.error && health.status !== 'offline') {
        const memBar = health.memory_mb > 0 ? `<div class="health-bar"><div class="health-bar-fill" style="width:${Math.min(health.memory_mb / 512 * 100, 100)}%"></div></div>` : '';
        const uptime = health.uptime_seconds > 0 ? _formatUptime(health.uptime_seconds) : 'N/A';
        content.innerHTML += `
          <div class="health-metrics" style="margin-top:12px;padding:12px;background:var(--card-bg);border:1px solid var(--border);border-radius:8px">
            <div style="font-size:10px;font-weight:600;color:var(--muted);margin-bottom:8px;text-transform:uppercase">System Health</div>
            <div class="agent-info-row">
              <span class="agent-info-label">CPU</span>
              <span style="font-size:11px">${health.cpu_percent}%</span>
            </div>
            <div class="agent-info-row">
              <span class="agent-info-label">Memory</span>
              <span style="font-size:11px">${health.memory_mb} MB ${memBar}</span>
            </div>
            <div class="agent-info-row">
              <span class="agent-info-label">Threads</span>
              <span style="font-size:11px">${health.threads}</span>
            </div>
          </div>`;
      }
    } catch(e) {}

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

function _formatUptime(seconds) {
  if (!seconds || seconds <= 0) return 'N/A';
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  return h > 0 ? `${h}h ${m}m` : `${m}m`;
}

async function loadAgentSoul(agentId, content) {
  const canEdit = agentId !== 'rose';
  try {
    const agent = await api(`/api/agents/${agentId}`);
    const soul = agent.soul || '';
    if (!soul) {
      content.innerHTML = `<div style="padding:16px;text-align:center;color:var(--muted);font-size:12px">
        <div style="font-size:24px;margin-bottom:8px">📄</div>
        <div>No soul.md found</div>
      </div>`;
      return;
    }
    content.innerHTML = `
      <div id="soulView">
        ${canEdit ? `<button class="agent-edit-btn" onclick="editAgentSoul('${agentId}')">✏️ Edit</button>` : ''}
        <div class="agent-md-content">${renderMarkdown(soul)}</div>
      </div>
      <div id="soulEdit" style="display:none">
        <textarea id="soulEditArea" rows="18" style="width:100%;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:8px;padding:10px;font-family:monospace;font-size:11px;line-height:1.6;resize:vertical;outline:none;box-sizing:border-box">${esc(soul)}</textarea>
        <div style="display:flex;gap:6px;margin-top:8px">
          <button class="cron-btn run" onclick="saveAgentSoul('${agentId}')">💾 Save</button>
          <button class="cron-btn" onclick="cancelEditSoul('${agentId}')">Cancel</button>
        </div>
        <div id="soulEditError" style="color:var(--accent);font-size:11px;margin-top:6px;display:none"></div>
      </div>
    `;
  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function loadAgentMemory(agentId, content) {
  const canEdit = agentId !== 'rose';
  // Fetch memory.md + render search bar
  try {
    const agent = await api(`/api/agents/${agentId}`);
    const memory = agent.memory || '';
    content.innerHTML = `
      <div style="padding:0 0 8px;border-bottom:1px solid var(--border);margin-bottom:8px">
        <div style="display:flex;gap:6px;align-items:center">
          <input id="memSearchInput" placeholder="Search memory... (ChromaDB)" style="flex:1;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;outline:none" onkeydown="if(event.key==='Enter')searchAgentMemory('${agentId}')">
          <button class="cron-btn run" style="flex-shrink:0;padding:5px 10px" onclick="searchAgentMemory('${agentId}')">Search</button>
        </div>
        <div id="memSearchResults" style="display:none;margin-top:8px"></div>
      </div>
      <div id="memoryView">
        ${canEdit ? `<button class="agent-edit-btn" onclick="editAgentMemory('${agentId}')">✏️ Edit</button>` : ''}
        <div class="agent-md-content">${memory ? renderMarkdown(memory) : '<div style="color:var(--muted);font-size:12px;text-align:center;padding:16px">No memory.md found</div>'}</div>
      </div>
      <div id="memoryEdit" style="display:none">
        <textarea id="memoryEditArea" rows="18" style="width:100%;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:8px;padding:10px;font-family:monospace;font-size:11px;line-height:1.6;resize:vertical;outline:none;box-sizing:border-box">${esc(memory)}</textarea>
        <div style="display:flex;gap:6px;margin-top:8px">
          <button class="cron-btn run" onclick="saveAgentMemory('${agentId}')">💾 Save</button>
          <button class="cron-btn" onclick="cancelEditMemory('${agentId}')">Cancel</button>
        </div>
        <div id="memoryEditError" style="color:var(--accent);font-size:11px;margin-top:6px;display:none"></div>
      </div>
    `;
  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function searchAgentMemory(agentId) {
  const q = document.getElementById('memSearchInput').value.trim();
  const resultsBox = document.getElementById('memSearchResults');
  if (!q) return;
  resultsBox.style.display = '';
  resultsBox.innerHTML = `<div style="font-size:10px;color:var(--muted);padding:4px 0">Searching...</div>`;
  try {
    const endpoint = agentId === 'rose'
      ? `/api/agents/memory/search?q=${encodeURIComponent(q)}`
      : `/api/agents/${agentId}/memory/search?q=${encodeURIComponent(q)}`;
    const data = await api(endpoint);
    const results = data.results || [];
    if (!results.length) {
      resultsBox.innerHTML = `<div style="font-size:11px;color:var(--muted);padding:4px 0">No results for "${esc(q)}"</div>`;
      return;
    }
    resultsBox.innerHTML = `
      <div style="font-size:10px;color:var(--muted);margin-bottom:6px;text-transform:uppercase;font-weight:600">${results.length} result${results.length!==1?'s':''}</div>
      ${results.map(r => `
        <div style="border:1px solid var(--border);border-radius:8px;padding:8px;margin-bottom:6px;background:rgba(255,255,255,.02)">
          <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px">
            <span style="font-size:11px;font-weight:600;color:var(--blue)">${esc(r.topic)}</span>
            <span style="font-size:9px;color:var(--muted);opacity:.7">${(r.confidence*100).toFixed(0)}%</span>
          </div>
          <div style="font-size:10px;color:var(--muted);margin-bottom:4px">${r.agent ? 'Agent: '+esc(r.agent)+' · ' : ''}Topic: ${esc(r.topic)}</div>
          <div style="font-size:10px;color:var(--text);line-height:1.4;max-height:60px;overflow:hidden">${esc((r.content||'').slice(0,200))}</div>
        </div>
      `).join('')}
    `;
  } catch(e) {
    resultsBox.innerHTML = `<div style="font-size:11px;color:var(--accent)">Error: ${esc(e.message)}</div>`;
  }
}

async function loadAgentInboxTab(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/inbox`);
    const messages = data.messages || [];
    const agentName = data.agent_name || agentId;

    if (messages.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">📭</div>
          <div>No messages in inbox</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Messages from other agents appear here</div>
        </div>
        <div style="padding:12px;border-top:1px solid var(--border)">
          <div style="font-size:10px;color:var(--muted);margin-bottom:6px;text-transform:uppercase;font-weight:600">Send Message</div>
          <input id="msgToAgentSubject" placeholder="Subject" style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;outline:none;box-sizing:border-box">
          <textarea id="msgToAgentBody" rows="3" placeholder="Message body..." style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;resize:none;outline:none;font-family:inherit;box-sizing:border-box"></textarea>
          <button class="cron-btn run" style="width:100%" onclick="sendToAgent('${agentId}')">Send</button>
          <div id="sendToAgentError" style="color:var(--accent);font-size:11px;margin-top:4px;display:none"></div>
        </div>
      `;
      return;
    }

    const msgsHtml = messages.map(m => {
      const ts = m.timestamp ? new Date(m.timestamp).toLocaleString() : '';
      const isUnread = m.status !== 'read';
      const typeColor = m.type === 'request' ? '#ff9800' : '#4caf50';
      const typeLabel = m.type === 'request' ? '📨 REQUEST' : '✅ REPLY';
      return `
        <div class="inbox-msg${isUnread ? ' unread' : ''}" onclick="toggleInboxMsg(this)">
          <div class="inbox-msg-header">
            <span style="font-size:9px;font-weight:700;color:${typeColor}">${typeLabel}</span>
            <span style="font-size:9px;color:var(--muted)">← ${esc(m.from || 'unknown')}</span>
            <span style="font-size:9px;color:var(--muted);margin-left:auto">${esc(ts)}</span>
          </div>
          <div class="inbox-msg-subject">${esc(m.subject || '(no subject)')}</div>
          <div class="inbox-msg-body">${esc(String(m.content || '').slice(0,200))}</div>
          <div class="inbox-msg-actions">
            ${isUnread ? `<button class="cron-btn" style="padding:2px 8px;font-size:9px" onclick="event.stopPropagation();ackMsg('${agentId}','${m.id}')">✓ Acknowledge</button>` : ''}
          </div>
        </div>`;
    }).join('');

    content.innerHTML = `
      <div class="inbox-messages-list">
        ${msgsHtml}
      </div>
      <div style="padding:12px;border-top:1px solid var(--border);margin-top:8px">
        <div style="font-size:10px;color:var(--muted);margin-bottom:6px;text-transform:uppercase;font-weight:600">Send Message</div>
        <input id="msgToAgentSubject" placeholder="Subject" style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;outline:none;box-sizing:border-box">
        <textarea id="msgToAgentBody" rows="3" placeholder="Message body..." style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;resize:none;outline:none;font-family:inherit;box-sizing:border-box"></textarea>
        <button class="cron-btn run" style="width:100%" onclick="sendToAgent('${agentId}')">Send</button>
        <div id="sendToAgentError" style="color:var(--accent);font-size:11px;margin-top:4px;display:none"></div>
      </div>
    `;
  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function loadAgentActivity(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/activity`);
    const events = data.events || [];

    if (events.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">📋</div>
          <div>No activity recorded yet</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Events like messages, tasks and updates appear here</div>
        </div>`;
      return;
    }

    const EVENT_COLORS = {
      'agent_started': '#4caf50',
      'agent_stopped': '#9e9e9e',
      'message_sent': '#2196f3',
      'message_received': '#00bcd4',
      'task_started': '#ff9800',
      'task_completed': '#4caf50',
      'task_failed': '#f44336',
      'soul_updated': '#9c27b0',
      'memory_updated': '#3f51b5',
      'chat_started': '#ff9800',
      'chat_ended': '#795548',
      'health_check': '#4caf50',
      'config_updated': '#607d8b',
      'error': '#f44336',
    };

    const EVENT_ICONS = {
      'agent_started': '🟢',
      'agent_stopped': '⚫',
      'message_sent': '📤',
      'message_received': '📥',
      'task_started': '▶️',
      'task_completed': '✅',
      'task_failed': '❌',
      'soul_updated': '✏️',
      'memory_updated': '🧠',
      'chat_started': '💬',
      'chat_ended': '💬',
      'health_check': '❤️',
      'config_updated': '⚙️',
      'error': '⚠️',
    };

    const rows = events.map(e => {
      const color = EVENT_COLORS[e.type] || '#9e9e9e';
      const icon = EVENT_ICONS[e.type] || '•';
      const ts = e.timestamp ? new Date(e.timestamp).toLocaleString() : 'N/A';
      const rel = e.timestamp ? _relTime(e.timestamp) : '';
      const details = e.details ? `<span style="font-size:10px;color:var(--muted);margin-left:6px">${esc(e.details)}</span>` : '';
      return `
        <div class="activity-event-row">
          <span style="font-size:14px;flex-shrink:0">${icon}</span>
          <div style="flex:1;min-width:0">
            <div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap">
              <span style="font-size:10px;font-weight:600;color:${color}">${esc(e.type)}</span>
              ${details}
            </div>
            <div style="font-size:9px;color:var(--muted)">${esc(ts)} · ${rel}</div>
          </div>
        </div>`;
    }).join('');

    content.innerHTML = `<div class="activity-list">${rows}</div>`;

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function loadAgentErrors(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/errors`);
    const errors = data.errors || [];

    if (errors.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:#4caf50;font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">✅</div>
          <div>No errors recorded</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">All good — this agent has no logged errors</div>
        </div>`;
      return;
    }

    const rows = errors.map(e => {
      const ts = e.timestamp ? new Date(e.timestamp).toLocaleString() : 'N/A';
      const rel = e.timestamp ? _relTime(e.timestamp) : '';
      return `
        <div class="error-event-row">
          <span style="font-size:14px;flex-shrink:0">⚠️</span>
          <div style="flex:1;min-width:0">
            <div style="font-size:11px;color:#f44336;font-weight:600">${esc(e.details || 'Unknown error')}</div>
            <div style="font-size:9px;color:var(--muted)">${esc(ts)} · ${rel}</div>
          </div>
        </div>`;
    }).join('');

    content.innerHTML = `
      <div style="padding:8px 0 8px;font-size:10px;color:#f44336;margin-bottom:4px">
        ⚠️ ${errors.length} error${errors.length !== 1 ? 's' : ''} total
      </div>
      <div class="error-list">${rows}</div>`;

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

function toggleInboxMsg(el) {
  el.classList.toggle('expanded');
}

async function loadAgentChatHistory(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/chat-history`);
    const sessions = data.sessions || [];

    if (sessions.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">💬</div>
          <div>No chat history yet</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Your conversations with ${agentId} appear here</div>
        </div>`;
      return;
    }

    const rows = sessions.map(s => {
      const created = s.created_at ? new Date(s.created_at).toLocaleString() : 'N/A';
      const rel = s.created_at ? _relTime(s.created_at) : '';
      const model = s.model || 'unknown';
      return `
        <div class="chat-history-row" onclick="openAgentChatSession('${agentId}','${s.session_id}')">
          <div class="chat-history-title">${esc(s.title)}</div>
          <div class="chat-history-meta">
            <span style="font-size:10px;color:var(--muted)">${created} · ${rel}</span>
            <span style="font-size:9px;padding:1px 5px;background:var(--code-bg);border-radius:4px;color:var(--muted);font-family:monospace">${esc(model)}</span>
            <span style="font-size:9px;color:var(--muted)">${s.message_count} msgs</span>
          </div>
        </div>`;
    }).join('');

    content.innerHTML = `<div class="chat-history-list">${rows}</div>`;

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

function openAgentChatSession(agentId, sessionId) {
  // Switch to the chat panel and load this session
  closeAgentDetail();
  if (typeof switchToChatPanel === 'function') switchToChatPanel();
  if (typeof loadSession === 'function') loadSession(sessionId);
  showToast(`Loading chat session...`);
}

async function loadAgentTasks(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/tasks`);
    const tasks = data.tasks || [];

    if (tasks.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">📋</div>
          <div>No tasks in queue</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Tasks will appear here when agents are working on something</div>
        </div>`;
      return;
    }

    const TASK_STATUS_COLORS = { 'running': '#4caf50', 'queued': '#ff9800', 'completed': '#9e9e9e', 'failed': '#f44336' };

    const rows = tasks.map(t => {
      const color = TASK_STATUS_COLORS[t.status] || '#9e9e9e';
      const ts = t.created_at ? new Date(t.created_at).toLocaleString() : '';
      return `
        <div class="task-row">
          <span class="task-status-dot" style="background:${color}"></span>
          <div style="flex:1;min-width:0">
            <div style="font-size:12px">${esc(t.description || 'Task')}</div>
            <div style="font-size:9px;color:var(--muted);margin-top:2px">${esc(ts)} · <span style="color:${color}">${esc(t.status)}</span></div>
          </div>
        </div>`;
    }).join('');

    content.innerHTML = `
      <div style="padding:8px 0 8px;font-size:10px;color:var(--muted)">${tasks.length} task${tasks.length !== 1 ? 's' : ''}</div>
      <div class="task-list">${rows}</div>`;

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function loadAgentBus(agentId, content) {
  try {
    const data = await api(`/api/agents/message-bus`);
    const bus = data.bus || {};

    // Collect all messages across agents, filter to those involving agentId
    const allMsgs = [];
    for (const [aId, aData] of Object.entries(bus)) {
      const msgs = aData.messages || [];
      for (const m of msgs) {
        if (m.from === agentId || m.to === agentId) {
          allMsgs.push({ ...m, _agent: aId });
        }
      }
    }

    // Sort newest first
    allMsgs.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));

    if (allMsgs.length === 0) {
      content.innerHTML = `
        <div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">🚌</div>
          <div>No messages in the bus</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Messages between agents appear here</div>
        </div>
        <div style="padding:12px;border-top:1px solid var(--border);margin-top:8px">
          <div style="font-size:10px;color:var(--muted);margin-bottom:6px;text-transform:uppercase;font-weight:600">Send Message via Bus</div>
          <input id="busSubject" placeholder="Subject" style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;outline:none;box-sizing:border-box">
          <textarea id="busContent" rows="3" placeholder="Message content..." style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;resize:none;outline:none;font-family:inherit;box-sizing:border-box"></textarea>
          <button class="cron-btn run" style="width:100%" onclick="sendBusMessage('${agentId}')">Send via Bus</button>
        </div>`;
      return;
    }

    const rows = allMsgs.map(m => {
      const ts = m.timestamp ? new Date(m.timestamp).toLocaleString() : 'N/A';
      const rel = m.timestamp ? _relTime(m.timestamp) : '';
      const isOutgoing = m.from === agentId;
      const dirIcon = isOutgoing ? '📤' : '📥';
      const dirLabel = isOutgoing ? `→ ${m.to}` : `← ${m.from}`;
      const typeColor = m.type === 'request' ? '#ff9800' : '#4caf50';
      return `
        <div class="bus-msg${isOutgoing ? ' outgoing' : ''}">
          <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
            <span style="font-size:12px">${dirIcon}</span>
            <span style="font-size:9px;font-weight:700;color:${typeColor}">${esc(m.type || '').toUpperCase()}</span>
            <span style="font-size:9px;color:var(--muted)">${dirLabel}</span>
            <span style="font-size:9px;color:var(--muted);margin-left:auto">${esc(ts)} · ${rel}</span>
          </div>
          <div style="font-size:11px;font-weight:500;margin-bottom:2px">${esc(m.subject || '(no subject)')}</div>
          <div style="font-size:10px;color:var(--muted)">${esc(String(m.content || '').slice(0, 120))}</div>
        </div>`;
    }).join('');

    content.innerHTML = `
      <div class="bus-list">${rows}</div>
      <div style="padding:12px;border-top:1px solid var(--border);margin-top:8px">
        <div style="font-size:10px;color:var(--muted);margin-bottom:6px;text-transform:uppercase;font-weight:600">Send Message via Bus</div>
        <input id="busSubject" placeholder="Subject" style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;outline:none;box-sizing:border-box">
        <textarea id="busContent" rows="3" placeholder="Message content..." style="width:100%;background:var(--input-bg);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:11px;margin-bottom:4px;resize:none;outline:none;font-family:inherit;box-sizing:border-box"></textarea>
        <button class="cron-btn run" style="width:100%" onclick="sendBusMessage('${agentId}')">Send via Bus</button>
      </div>`;

  } catch(e) {
    content.innerHTML = `<div style="padding:12px;color:var(--accent);font-size:12px">Error: ${esc(e.message)}</div>`;
  }
}

async function sendBusMessage(toAgent) {
  const subject = document.getElementById('busSubject').value.trim();
  const content = document.getElementById('busContent').value.trim();
  if (!subject && !content) {
    showToast('Please enter a subject or message');
    return;
  }
  try {
    const r = await api(`/api/agents/${toAgent}/bus-message`, {
      method: 'POST',
      body: JSON.stringify({ from_agent: 'rose', subject, content }),
    });
    if (!r.ok) throw new Error(r.error || 'Send failed');
    showToast('Message sent via bus');
    document.getElementById('busSubject').value = '';
    document.getElementById('busContent').value = '';
    // Refresh
    await switchAgentTab('bus');
  } catch(e) {
    showToast('Error: ' + e.message);
  }
}

async function loadAgentTopology(agentId, content) {
  const agents = [
    { id: 'rose', name: 'rose', emoji: '🌹', tier: 'orchestrator', x: 0, y: 0 },
    { id: 'lotus', name: 'lotus', emoji: '🪷', tier: 'tier2', x: 0, y: -1 },
    { id: 'forget-me-not', name: 'forget-me-not', emoji: '🌼', tier: 'tier2', x: 0.7, y: -0.7 },
    { id: 'sunflower', name: 'sunflower', emoji: '🌻', tier: 'tier2', x: -1, y: 0 },
    { id: 'iris', name: 'iris', emoji: '⚜️', tier: 'tier2', x: 0, y: 1 },
    { id: 'ivy', name: 'ivy', emoji: '🌿', tier: 'tier2', x: -0.7, y: 0.7 },
    { id: 'dandelion', name: 'dandelion', emoji: '🛡️', tier: 'tier2', x: 0.7, y: 0.7 },
    { id: 'root', name: 'root', emoji: '🌳', tier: 'tier2', x: 0, y: 0.7 }
  ];

  const connections = [
    { from: 'rose', to: 'lotus' },
    { from: 'rose', to: 'forget-me-not' },
    { from: 'rose', to: 'sunflower' },
    { from: 'rose', to: 'iris' },
    { from: 'rose', to: 'ivy' },
    { from: 'rose', to: 'dandelion' },
    { from: 'rose', to: 'root' },
    { from: 'lotus', to: 'forget-me-not' },
    { from: 'sunflower', to: 'lotus' },
    { from: 'iris', to: 'rose' },
    { from: 'ivy', to: 'rose' },
    { from: 'dandelion', to: 'rose' },
    { from: 'root', to: 'rose' }
  ];

  const scale = 80;
  let svg = '<svg viewBox="-200 -160 400 320" style="width:100%;max-width:500px;display:block;margin:0 auto;background:transparent">';
  svg += '<defs><filter id="glow"><feGaussianBlur stdDeviation="3" result="blur"/><feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs>';

  connections.forEach(conn => {
    const from = agents.find(a => a.id === conn.from);
    const to = agents.find(a => a.id === conn.to);
    if (from && to) {
      svg += '<line x1="' + (from.x * scale) + '" y1="' + (from.y * scale) + '" x2="' + (to.x * scale) + '" y2="' + (to.y * scale) + '" stroke="rgba(255,255,255,0.15)" stroke-width="1.5"/>';
    }
  });

  agents.forEach(agent => {
    const px = agent.x * scale;
    const py = agent.y * scale;
    const isRose = agent.tier === 'orchestrator';
    const color = isRose ? '#F5C542' : '#5B8FA8';
    const r = 28;
    const isActive = agentId === agent.id;

    if (isActive) {
      svg += '<circle cx="' + px + '" cy="' + py + '" r="' + (r + 8) + '" fill="' + color + '" opacity="0.3"><animate attributeName="r" values="' + (r+8) + ';' + (r+14) + ';' + (r+8) + '" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" values="0.3;0.15;0.3" dur="2s" repeatCount="indefinite"/></circle>';
    }

    svg += '<circle cx="' + px + '" cy="' + py + '" r="' + r + '" fill="' + color + '" stroke="' + (isActive ? '#fff' : 'rgba(255,255,255,0.3)') + '" stroke-width="' + (isActive ? 2 : 1) + '" filter="' + (isActive ? 'url(#glow)' : '') + '"/>';
    svg += '<text x="' + px + '" y="' + (py + 5) + '" text-anchor="middle" font-size="16">' + agent.emoji + '</text>';
    svg += '<text x="' + px + '" y="' + (py + r + 14) + '" text-anchor="middle" font-size="9" fill="rgba(255,255,255,0.7)" font-family="system-ui,sans-serif">' + agent.name + '</text>';
  });

  svg += '</svg>';
  content.innerHTML = '<div class="topology-view">' + svg + '</div>';
}

async function loadAgentUsage(agentId, content) {
  try {
    const data = await api(`/api/agents/${agentId}/usage`);
    if (data.error) {
      content.innerHTML = `<div style="padding:24px;text-align:center;color:var(--muted);font-size:12px">${esc(data.error)}</div>`;
      return;
    }

    const fmt = (n) => n.toLocaleString();
    const fmtCost = (c) => '$' + c.toFixed(4);

    const historyRows = (data.history || []).slice(0, 14).map(h => `
      <tr>
        <td style="font-size:10px;padding:4px 8px">${esc(h.date)}</td>
        <td style="font-size:10px;padding:4px 8px;text-align:right">${fmt(h.total_tokens || 0)}</td>
        <td style="font-size:10px;padding:4px 8px;text-align:right">${fmt(h.prompt_tokens || 0)}</td>
        <td style="font-size:10px;padding:4px 8px;text-align:right">${fmt(h.completion_tokens || 0)}</td>
        <td style="font-size:10px;padding:4px 8px;text-align:right;color:#4caf50">${fmtCost(h.cost_usd || 0)}</td>
      </tr>`).join('');

    content.innerHTML = `
      <div style="padding:16px">
        <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px">
          <div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
            <div style="font-size:9px;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:600">Today</div>
            <div style="font-size:20px;font-weight:700;color:var(--text)">${fmt(data.today.tokens)}</div>
            <div style="font-size:11px;color:#4caf50;margin-top:2px">${fmtCost(data.today.cost)}</div>
          </div>
          <div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
            <div style="font-size:9px;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:600">This Week</div>
            <div style="font-size:20px;font-weight:700;color:var(--text)">${fmt(data.week.tokens)}</div>
            <div style="font-size:11px;color:#4caf50;margin-top:2px">${fmtCost(data.week.cost)}</div>
          </div>
          <div style="background:var(--card-bg);border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center">
            <div style="font-size:9px;text-transform:uppercase;color:var(--muted);margin-bottom:6px;font-weight:600">This Month</div>
            <div style="font-size:20px;font-weight:700;color:var(--text)">${fmt(data.month.tokens)}</div>
            <div style="font-size:11px;color:#4caf50;margin-top:2px">${fmtCost(data.month.cost)}</div>
          </div>
        </div>
        ${historyRows ? `
        <div style="font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;margin-bottom:8px">Recent History</div>
        <table style="width:100%;border-collapse:collapse">
          <thead>
            <tr style="border-bottom:1px solid var(--border)">
              <th style="text-align:left;font-size:9px;padding:4px 8px;color:var(--muted)">Date</th>
              <th style="text-align:right;font-size:9px;padding:4px 8px;color:var(--muted)">Total</th>
              <th style="text-align:right;font-size:9px;padding:4px 8px;color:var(--muted)">Prompt</th>
              <th style="text-align:right;font-size:9px;padding:4px 8px;color:var(--muted)">Completion</th>
              <th style="text-align:right;font-size:9px;padding:4px 8px;color:var(--muted)">Cost</th>
            </tr>
          </thead>
          <tbody>${historyRows}</tbody>
        </table>` : `
        <div style="text-align:center;padding:24px;color:var(--muted);font-size:12px">
          <div style="font-size:28px;margin-bottom:8px">📊</div>
          <div>No usage data recorded yet</div>
          <div style="font-size:10px;margin-top:4px;opacity:0.6">Token usage will appear here once recorded</div>
        </div>`}
      </div>`;
  } catch(e) {
    content.innerHTML = `<div style="padding:24px;text-align:center;color:#e94560;font-size:12px">Failed to load usage: ${esc(e.message)}</div>`;
  }
}

// Edit handlers
function editAgentSoul(agentId) {
  document.getElementById('soulView').style.display = 'none';
  document.getElementById('soulEdit').style.display = 'block';
}

function cancelEditSoul(agentId) {
  document.getElementById('soulView').style.display = 'block';
  document.getElementById('soulEdit').style.display = 'none';
}

async function saveAgentSoul(agentId) {
  const content = document.getElementById('soulEditArea').value;
  const errEl = document.getElementById('soulEditError');
  errEl.style.display = 'none';
  try {
    const r = await api(`/api/agents/${agentId}/soul`, { method: 'PUT', body: JSON.stringify({ content }) });
    if (!r.ok) throw new Error(r.error || 'Save failed');
    showToast('soul.md saved');
    await switchAgentTab('soul');
  } catch(e) {
    errEl.textContent = e.message;
    errEl.style.display = 'block';
  }
}

function editAgentMemory(agentId) {
  document.getElementById('memoryView').style.display = 'none';
  document.getElementById('memoryEdit').style.display = 'block';
}

function cancelEditMemory(agentId) {
  document.getElementById('memoryView').style.display = 'block';
  document.getElementById('memoryEdit').style.display = 'none';
}

async function saveAgentMemory(agentId) {
  const content = document.getElementById('memoryEditArea').value;
  const errEl = document.getElementById('memoryEditError');
  errEl.style.display = 'none';
  try {
    const r = await api(`/api/agents/${agentId}/memory`, { method: 'PUT', body: JSON.stringify({ content }) });
    if (!r.ok) throw new Error(r.error || 'Save failed');
    showToast('memory.md saved');
    await switchAgentTab('memory');
  } catch(e) {
    errEl.textContent = e.message;
    errEl.style.display = 'block';
  }
}

async function sendToAgent(agentId) {
  const subject = document.getElementById('msgToAgentSubject').value.trim();
  const body = document.getElementById('msgToAgentBody').value.trim();
  const errEl = document.getElementById('sendToAgentError');
  errEl.style.display = 'none';
  if (!body) { errEl.textContent = 'Message body is required'; errEl.style.display = 'block'; return; }
  try {
    const r = await api(`/api/agents/${agentId}/message`, {
      method: 'POST',
      body: JSON.stringify({ from: 'rose', type: 'request', subject, content: body }),
    });
    if (!r.ok) throw new Error(r.error || 'Send failed');
    showToast('Message sent');
    document.getElementById('msgToAgentSubject').value = '';
    document.getElementById('msgToAgentBody').value = '';
    await switchAgentTab('inbox');
  } catch(e) {
    errEl.textContent = e.message;
    errEl.style.display = 'block';
  }
}

async function ackMsg(agentId, msgId) {
  try {
    await api(`/api/agents/${agentId}/ack/${msgId}`, { method: 'POST' });
    await switchAgentTab('inbox');
    await refreshAgents();
  } catch(e) {
    showToast('Ack failed: ' + e.message);
  }
}

async function toggleAgentEnabled(agentId, enable) {
  try {
    const r = await api(`/api/agents/${agentId}/${enable ? 'enable' : 'disable'}`, { method: 'POST' });
    if (!r.ok) throw new Error(r.error || 'Toggle failed');
    showToast(`Agent ${enable ? 'enabled' : 'disabled'}`);
    await openAgentDetail(agentId);
    await refreshAgents();
  } catch(e) {
    showToast('Error: ' + e.message);
  }
}

function chatWithAgent(agentId) {
  localStorage.setItem('hermes.chat_agent', agentId);
  closeAgentInbox();
  switchPanel('chat');
}

function closeAgentInbox() {
  $('agentInbox').style.display = 'none';
  _selectedAgent = null;
  document.querySelectorAll('.agent-card').forEach(el => el.classList.remove('selected'));
}

// Simple markdown renderer (bold, italic, code, headers, lists, linebreaks)
function renderMarkdown(text) {
  if (!text) return '';
  return esc(text)
    .replace(/&lt;(\/?)(pre|code|strong|b|em|i|li|ul|ol|h[1-6]|br|p)&gt;/gi, '<$1$2>')
    .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.+?)\*/g, '<em>$1</em>')
    .replace(/`(.+?)`/g, '<code style="background:rgba(255,255,255,.08);padding:1px 4px;border-radius:3px;font-size:.9em">$1</code>')
    .replace(/^### (.+)$/gm, '<h4 style="font-size:12px;font-weight:700;margin:8px 0 4px">$1</h3>')
    .replace(/^## (.+)$/gm, '<h3 style="font-size:13px;font-weight:700;margin:10px 0 4px">$1</h3>')
    .replace(/^# (.+)$/gm, '<h2 style="font-size:14px;font-weight:700;margin:12px 0 6px">$1</h2>')
    .replace(/^- (.+)$/gm, '<li style="margin-left:12px">$1</li>')
    .replace(/^(\d+)\. (.+)$/gm, '<li style="margin-left:12px;list-style:decimal">$2</li>')
    .replace(/\n/g, '<br>');
}

// ── Logs Panel ────────────────────────────────────────────────────────────────
let _currentLogFile = null;
let _currentLogContent = '';
let _currentLogLevel = 'all';
let _currentLogSearch = '';
let _logAutoRefreshInterval = null;

async function loadLogsPanel() {
  const el = $('logsFileList');
  if (!el) return;
  el.innerHTML = '<div style="color:var(--muted);font-size:12px;padding:8px">Loading...</div>';
  try {
    const data = await api('/api/logs');
    if (!data.logs) return;
    el.innerHTML = '';
    data.logs.forEach(log => {
      const item = document.createElement('div');
      item.className = 'logs-sidebar-item' + (log.missing ? ' missing' : '') + (_currentLogFile === log.name ? ' active' : '');
      item.onclick = () => { if (!log.missing) selectLog(log.name); };
      item.innerHTML = `
        <div class="logs-sidebar-name">${esc(log.name)}</div>
        <div class="logs-sidebar-meta">${log.missing ? 'Missing' : log.size_human + ' • ' + (log.modified ? _formatDate(log.modified) : 'Unknown')}</div>
      `;
      el.appendChild(item);
    });
  } catch(e) {
    el.innerHTML = '<div style="color:var(--accent);font-size:12px;padding:8px">Failed to load logs.</div>';
  }
}

async function selectLog(name) {
  _currentLogFile = name;
  _currentLogLevel = 'all';
  _currentLogSearch = '';
  $('logsSearchInput').value = '';
  $('logsFileName').textContent = name;
  // Show toolbar controls
  $('logsSearchInput').style.display = '';
  $('logsLevelBtns').style.display = '';
  $('logsAutoRefreshLabel').style.display = '';
  $('btnRefreshLog').style.display = '';
  $('logsFooter').style.display = '';
  $('logsContent').innerHTML = '<div style="color:var(--muted);font-size:12px;padding:12px">Loading...</div>';
  // Reset level buttons
  document.querySelectorAll('.log-level-btn').forEach(b => b.classList.toggle('active', b.dataset.level === 'all'));
  try {
    const data = await api('/api/logs/' + encodeURIComponent(name));
    _currentLogContent = data.content || '';
    _applyLogFilter();
  } catch(e) {
    $('logsContent').innerHTML = '<div style="color:var(--accent);font-size:12px">Failed to load log.</div>';
  }
  // Update active state in list
  document.querySelectorAll('.logs-sidebar-item').forEach(el => {
    el.classList.toggle('active', el.querySelector('.logs-sidebar-name').textContent === name);
  });
  // Stop auto-refresh if running for different log
  if (_logAutoRefreshInterval) {
    clearInterval(_logAutoRefreshInterval);
    _logAutoRefreshInterval = null;
  }
}

function _applyLogFilter() {
  let content = _currentLogContent;
  let lines = content.split('\n');

  // Filter by level
  if (_currentLogLevel !== 'all') {
    const levelMap = { ERROR: ['ERROR', 'CRITICAL', 'FATAL'], WARN: ['WARNING', 'WARN'], INFO: ['INFO', 'DEBUG', 'TRACE'] };
    const allowed = levelMap[_currentLogLevel] || [_currentLogLevel];
    lines = lines.filter(line => allowed.some(l => line.toUpperCase().includes(l)));
  }

  // Filter by search
  const searchLower = _currentLogSearch ? _currentLogSearch.toLowerCase() : '';
  if (searchLower) {
    lines = lines.filter(line => line.toLowerCase().includes(searchLower));
  }

  // Render with highlighting
  const total = _currentLogContent.split('\n').length;
  const shown = lines.length;
  $('logsMatchCount').textContent = searchLower || _currentLogLevel !== 'all'
    ? `${shown} of ${total} lines shown`
    : `${total} lines`;

  if (lines.length === 0) {
    $('logsContent').innerHTML = '<span style="color:var(--muted)">(no matches)</span>';
    return;
  }

  // Escape first, then highlight search terms
  const highlighted = lines.map((line, i) => {
    const escaped = esc(line) || '&nbsp;';
    if (searchLower) {
      // Highlight all occurrences of search term (case-insensitive, preserves case in display)
      const regex = new RegExp(escRegex(_currentLogSearch), 'gi');
      return `<span class="log-line" data-idx="${i}">${escaped.replace(regex, m => `<mark class="log-highlight">${m}</mark>`)}</span>`;
    }
    return `<span class="log-line" data-idx="${i}">${escaped}</span>`;
  }).join('\n');

  $('logsContent').innerHTML = highlighted;

  // Auto-scroll to first match when searching
  if (searchLower) {
    const pre = $('logsContent');
    const first = pre.querySelector('.log-highlight');
    if (first) {
      first.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  } else {
    // Normal: scroll to bottom (newest)
    const pre = $('logsContent');
    pre.scrollTop = pre.scrollHeight;
  }
}

// Escape special regex characters
function escRegex(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function filterLogContent() {
  _currentLogSearch = $('logsSearchInput').value;
  _applyLogFilter();
}

function setLogLevel(level) {
  _currentLogLevel = level;
  document.querySelectorAll('.log-level-btn').forEach(b => b.classList.toggle('active', b.dataset.level === level));
  _applyLogFilter();
}

function toggleLogAutoRefresh() {
  const enabled = $('logsAutoRefresh').checked;
  if (enabled) {
    if (!_logAutoRefreshInterval) {
      _logAutoRefreshInterval = setInterval(() => refreshLog(), 5000);
    }
  } else {
    if (_logAutoRefreshInterval) {
      clearInterval(_logAutoRefreshInterval);
      _logAutoRefreshInterval = null;
    }
  }
}

async function refreshLog() {
  if (!_currentLogFile) return;
  try {
    const data = await api('/api/logs/' + encodeURIComponent(_currentLogFile));
    _currentLogContent = data.content || '';
    _applyLogFilter();
    // Auto-scroll to bottom if near bottom
    const pre = $('logsContent');
    if (pre.scrollHeight - pre.scrollTop - pre.clientHeight < 100) {
      pre.scrollTop = pre.scrollHeight;
    }
  } catch(e) {
    // Silent fail on auto-refresh
  }
}

async function refreshLogManual() {
  if (!_currentLogFile) return;
  try {
    const data = await api('/api/logs/' + encodeURIComponent(_currentLogFile));
    _currentLogContent = data.content || '';
    _applyLogFilter();
    const pre = $('logsContent');
    pre.scrollTop = pre.scrollHeight;
    showToast('Log refreshed');
  } catch(e) {
    showToast('Refresh failed');
  }
}

function _formatDate(ts) {
  if (!ts) return '';
  const d = new Date(ts * 1000);
  return d.toLocaleDateString(undefined, {month:'short', day:'numeric'}) + ' ' +
         d.toLocaleTimeString(undefined, {hour:'2-digit', minute:'2-digit'});
}

// Event wiring
