@hera-al/server 1.6.2 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/agent/prompt-builder.js +1 -1
  2. package/dist/agent/session-agent.d.ts +26 -0
  3. package/dist/agent/session-agent.js +1 -1
  4. package/dist/commands/model.d.ts +11 -2
  5. package/dist/commands/model.js +1 -1
  6. package/dist/commands/models.d.ts +4 -2
  7. package/dist/commands/models.js +1 -1
  8. package/dist/commands/status.d.ts +2 -0
  9. package/dist/commands/status.js +1 -1
  10. package/dist/config.d.ts +61 -4
  11. package/dist/config.js +1 -1
  12. package/dist/gateway/channels/telegram.js +1 -1
  13. package/dist/nostromo/nostromo.js +1 -1
  14. package/dist/nostromo/ui-html-layout.js +1 -1
  15. package/dist/nostromo/ui-js-agent.js +1 -1
  16. package/dist/nostromo/ui-js-config.js +1 -1
  17. package/dist/nostromo/ui-js-core.js +1 -1
  18. package/dist/nostromo/ui-styles.js +1 -1
  19. package/dist/pi-agent-provider/index.d.ts +115 -0
  20. package/dist/pi-agent-provider/index.js +1 -0
  21. package/dist/pi-agent-provider/integration-example.d.ts +83 -0
  22. package/dist/pi-agent-provider/integration-example.js +1 -0
  23. package/dist/pi-agent-provider/pi-context-compactor.d.ts +116 -0
  24. package/dist/pi-agent-provider/pi-context-compactor.js +1 -0
  25. package/dist/pi-agent-provider/pi-mcp-bridge.d.ts +38 -0
  26. package/dist/pi-agent-provider/pi-mcp-bridge.js +1 -0
  27. package/dist/pi-agent-provider/pi-message-adapter.d.ts +93 -0
  28. package/dist/pi-agent-provider/pi-message-adapter.js +1 -0
  29. package/dist/pi-agent-provider/pi-query.d.ts +49 -0
  30. package/dist/pi-agent-provider/pi-query.js +1 -0
  31. package/dist/pi-agent-provider/pi-skill-loader.d.ts +15 -0
  32. package/dist/pi-agent-provider/pi-skill-loader.js +1 -0
  33. package/dist/pi-agent-provider/pi-tool-adapter.d.ts +105 -0
  34. package/dist/pi-agent-provider/pi-tool-adapter.js +1 -0
  35. package/dist/pi-agent-provider/pi-tool-executor.d.ts +95 -0
  36. package/dist/pi-agent-provider/pi-tool-executor.js +1 -0
  37. package/dist/pi-agent-provider/pi-types.d.ts +179 -0
  38. package/dist/pi-agent-provider/pi-types.js +1 -0
  39. package/dist/server.js +1 -1
  40. package/dist/stt/stt-loader.js +1 -1
  41. package/package.json +2 -1
@@ -1 +1 @@
1
- import{hostname as e}from"node:os";export function renderLayout(n){return`\n\x3c!-- Login view --\x3e\n<div id="loginView" class="login-wrap">\n <div class="login-card">\n <div style="margin:0 auto 12px;width:150px;height:150px">\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 240" width="150" height="138">\n <defs>\n <linearGradient id="gBody" x1="0" y1="0" x2=".3" y2="1"><stop offset="0" stop-color="#f472b6"/><stop offset="1" stop-color="#9d174d"/></linearGradient>\n <linearGradient id="gTop" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0" stop-color="#fce7f3"/><stop offset="1" stop-color="#f9a8d4"/></linearGradient>\n <linearGradient id="gScr" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0" stop-color="#1e293b"/><stop offset="1" stop-color="#0f172a"/></linearGradient>\n </defs>\n \x3c!-- monitor body --\x3e\n <rect x="18" y="8" width="224" height="172" rx="14" fill="#334155"/>\n <rect x="20" y="10" width="220" height="168" rx="13" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>\n \x3c!-- screen bezel --\x3e\n <rect x="32" y="20" width="196" height="148" rx="6" fill="url(#gScr)"/>\n \x3c!-- screen glow --\x3e\n <rect x="34" y="22" width="192" height="144" rx="5" fill="#0f172a" opacity=".6"/>\n \x3c!-- power led --\x3e\n <circle cx="130" cy="186" r="2.5" fill="#4ade80" opacity=".7"/>\n \x3c!-- stand --\x3e\n <path d="M105 188 L95 218 H165 L155 188" fill="#334155"/>\n <rect x="85" y="218" width="90" height="8" rx="3" fill="#475569"/>\n \x3c!-- gem centered on screen --\x3e\n <g transform="translate(130,96) scale(.56)">\n <path d="M0 90 L-82 0 -62 -46 Q-42 -74 0 -38 Q42 -74 62 -46 L82 0 Z" fill="url(#gBody)"/>\n <path d="M-62 -46 Q-42 -74 0 -38 Q42 -74 62 -46 L82 0 H-82 Z" fill="url(#gTop)" opacity=".55"/>\n <path d="M-82 0 L-62 -46 0 -38 Z" fill="#ec4899" opacity=".7"/>\n <path d="M82 0 L62 -46 0 -38 Z" fill="#be185d" opacity=".7"/>\n <path d="M0 -38 L-32 0 32 0 Z" fill="#fce7f3" opacity=".35"/>\n <path d="M-62 -46 L-82 0 -32 0 0 -38 Z" fill="#f9a8d4" opacity=".30"/>\n <path d="M62 -46 L82 0 32 0 0 -38 Z" fill="#db2777" opacity=".30"/>\n <path d="M-82 0 H-32 L0 90 Z" fill="#ec4899" opacity=".45"/>\n <path d="M-32 0 H32 L0 90 Z" fill="#db2777" opacity=".85"/>\n <path d="M32 0 H82 L0 90 Z" fill="#9d174d" opacity=".55"/>\n <path d="M-20 -28 L-14 -38 -8 -28 -14 -32Z" fill="#fff" opacity=".7"/>\n <circle cx="-22" cy="-40" r="2" fill="#fff" opacity=".6"/>\n </g>\n \x3c!-- subtle screen scanline overlay --\x3e\n <line x1="34" y1="60" x2="226" y2="60" stroke="#94a3b8" stroke-width=".3" opacity=".15"/>\n <line x1="34" y1="100" x2="226" y2="100" stroke="#94a3b8" stroke-width=".3" opacity=".12"/>\n <line x1="34" y1="140" x2="226" y2="140" stroke="#94a3b8" stroke-width=".3" opacity=".10"/>\n </svg>\n </div>\n <h1>Nostromo</h1>\n <p class="subtitle" id="loginSubtitle">Enter your access key</p>\n <div class="field">\n <label>Access Key</label>\n <div class="key-row">\n <div class="key-inputs" id="keyInputs">\n <input id="k0" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k1" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k2" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k3" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n </div>\n <button type="button" class="eye-btn" id="eyeBtn" onclick="toggleKeyVis()" title="Show/hide key">\n <svg id="eyeOff" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>\n <svg id="eyeOn" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>\n </button>\n </div>\n </div>\n <button class="btn" id="loginBtn" style="width:100%;margin-top:4px" onclick="doLogin()">Unlock</button>\n <div id="loginError" class="login-error"></div>\n </div>\n</div>\n\n\x3c!-- App shell (hidden until authenticated) --\x3e\n<div id="appShell" class="shell" style="display:none">\n <aside class="sidebar">\n <div class="logo">\n <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>\n <span>Nostromo</span>\n </div>\n <nav id="navMenu">\n <a href="#dashboard" data-section="dashboard" class="active">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>\n <span>Dashboard</span>\n </a>\n <div class="nav-group collapsed" id="navGroupEngine">\n <div class="nav-group-header" onclick="toggleNavGroup('Engine')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>\n <span>Engine</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#models" data-section="models">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>\n <span>Models</span>\n </a>\n <a href="#agent" data-section="agent">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>\n <span>Agent</span>\n </a>\n <a href="#subagents" data-section="subagents">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>\n <span>Sub Agents</span>\n </a>\n <a href="#vars" data-section="vars">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/><circle cx="12" cy="16" r="1"/></svg>\n <span>Vars</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupIdentity">\n <div class="nav-group-header" onclick="toggleNavGroup('Identity')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>\n <span>Artificial Identity</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#memory" data-section="memory">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/></svg>\n <span>Memories</span>\n </a>\n <a href="#prompts" data-section="prompts">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>\n <span>Prompts</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupCompetences">\n <div class="nav-group-header" onclick="toggleNavGroup('Competences')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>\n <span>Competences</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#commands" data-section="commands">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Commands</span>\n </a>\n <a href="#skills" data-section="skills">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>\n <span>Skills</span>\n </a>\n <a href="#plugins" data-section="plugins">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v6m0 0l3-3m-3 3L9 5"/><rect x="4" y="8" width="16" height="12" rx="2"/><path d="M9 8V6a3 3 0 0 1 6 0v2"/></svg>\n <span>Plugins</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupInteractions">\n <div class="nav-group-header" onclick="toggleNavGroup('Interactions')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>\n <span>Interactions</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#channels" data-section="channels">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>\n <span>Channels</span>\n </a>\n <a href="#stt" data-section="stt">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>\n <span>STT</span>\n </a>\n <a href="#tts" data-section="tts">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>\n <span>TTS</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupOperations">\n <div class="nav-group-header" onclick="toggleNavGroup('Operations')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Operations</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#logs" data-section="logs">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Logs</span>\n </a>\n <a href="#cron" data-section="cron">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>\n <span>Cron</span>\n </a>\n <a href="#nodes" data-section="nodes">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>\n <span>Nodes</span>\n </a>\n <a href="#tokens" data-section="tokens">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>\n <span>Tokens</span>\n </a>\n </div>\n </div>\n <a href="#settings" data-section="settings">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>\n <span>Settings</span>\n </a>\n <span style="display:block;padding:8px 18px 0;font-size:12px;color:var(--text-muted);opacity:.6">Host: ${e()}</span>\n </nav>\n </aside>\n <main class="main">\n\n \x3c!-- Dashboard --\x3e\n <div id="sec-dashboard" class="section active">\n <div class="section-header"><h1>Dashboard</h1></div>\n <div class="card">\n <div class="card-header"><span class="card-title">Server Status</span><div style="display:flex;align-items:center;gap:8px"><span id="restartPending" class="restart-pending" style="display:none">Restart pending</span><span id="statusBadge" class="badge badge-green">Online</span><button class="btn-danger btn-sm" onclick="showRestartModal()" style="font-size:12px;padding:3px 8px">Restart</button></div></div>\n <div style="display:flex;justify-content:space-between;font-size:14px">\n <div><span style="color:var(--text-muted)">Uptime:</span> <span id="statusUptime">--</span></div>\n <div style="text-align:right">\n <div><span style="color:var(--text-muted)">Agent:</span> <span id="statusModel">--</span></div>\n <div style="margin-top:4px"><span style="color:var(--text-muted)">Fallback:</span> <span id="statusFallback">--</span></div>\n </div>\n </div>\n <div style="margin-top:10px;font-size:14px"><span style="color:var(--text-muted)">Auto-Restart:</span> <span id="statusAutoRestart">--</span></div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:10px">Enabled Channels</div>\n <div id="dashChannels" style="display:flex;flex-wrap:wrap;gap:8px"></div>\n </div>\n </div>\n\n \x3c!-- Channels --\x3e\n <div id="sec-channels" class="section">\n <div class="section-header"><h1>Channels</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div id="channelCards"></div>\n </div>\n\n \x3c!-- Models --\x3e\n <div id="sec-models" class="section">\n <div class="section-header"><h1>Models</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Model Registry</span>\n <button class="btn btn-sm" onclick="showAddModel()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Model\n </button>\n </div>\n <div id="addModelForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Model ID</label><input id="newModelId" type="text" placeholder="claude-sonnet-4-5-20250514"></div>\n <div class="field"><label>Display Name</label><input id="newModelName" type="text" placeholder="Friendly label for this model"></div>\n <div class="field"><label>Type</label><select id="newModelType" onchange="updateNewModelApiFields()">\n <option value="internal">internal</option>\n <option value="external">external</option>\n </select></div>\n <div id="newModelProxyField" style="display:none">\n <div class="field"><label>Other LLM Provider</label><select id="newModelProxy" onchange="updateNewModelProxyFields()">\n <option value="not-used">not-used</option>\n <option value="direct">direct</option>\n <option value="proxied">proxied</option>\n </select></div>\n <div class="field"><label>OpenAI Compatible Proxy URL</label><input id="newModelFastUrl" type="text" placeholder="http://localhost:4181" disabled></div>\n <div class="field"><label>Proxy API Key</label><input id="newModelFastProxyApiKey" type="text" placeholder="sk-..." disabled></div>\n </div>\n <div id="newModelApiFields" style="display:none">\n <div class="field"><label>Use Env Var <span style="color:var(--text-muted);font-weight:400">(if empty, OPENAI_API_KEY is used)</span></label><input id="newModelEnvVar" type="text" placeholder="Force Variable Name" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div id="newModelBaseURLField" class="field"><label>API Base URL <span style="color:var(--text-muted);font-weight:400">(leave empty for provider default)</span></label><input id="newModelBaseURL" type="text" placeholder="https://api.openai.com/v1"></div>\n <div class="field"><label>API Key</label><input id="newModelApiKey" type="text" placeholder="The actual value for this variable"></div>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addModel()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddModel()">Cancel</button>\n </div>\n </div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Model ID</th><th>Types</th><th></th></tr></thead>\n <tbody id="modelsBody"></tbody>\n </table>\n <div id="editModelForm" style="display:none;margin:12px 0;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Model ID</label><input id="editModelId" type="text"></div>\n <div class="field"><label>Display Name</label><input id="editModelName" type="text"></div>\n <div class="field"><label>Type</label><select id="editModelType" onchange="updateEditModelApiFields()">\n <option value="internal">internal</option>\n <option value="external">external</option>\n </select></div>\n <div id="editModelProxyField" style="display:none">\n <div class="field"><label>Other LLM Provider</label><select id="editModelProxy" onchange="updateEditModelProxyFields()">\n <option value="not-used">not-used</option>\n <option value="direct">direct</option>\n <option value="proxied">proxied</option>\n </select></div>\n <div class="field"><label>OpenAI Compatible Proxy URL</label><input id="editModelFastUrl" type="text" placeholder="http://localhost:4181" disabled></div>\n <div class="field"><label>Proxy API Key</label><input id="editModelFastProxyApiKey" type="text" placeholder="sk-..." disabled></div>\n </div>\n <div id="editModelApiFields" style="display:none">\n <div class="field"><label>Use Env Var <span style="color:var(--text-muted);font-weight:400">(if empty, OPENAI_API_KEY is used)</span></label><input id="editModelEnvVar" type="text" placeholder="Force Variable Name" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div id="editModelBaseURLField" class="field"><label>API Base URL</label><input id="editModelBaseURL" type="text"></div>\n <div class="field"><label>API Key</label><input id="editModelApiKey" type="text"></div>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="finishEditModel()">Save</button>\n <button class="btn-ghost btn-sm" onclick="cancelEditModel()">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Vars --\x3e\n <div id="sec-vars" class="section">\n <div class="section-header"><h1>Vars</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Vars Registry</span>\n <button class="btn btn-sm" onclick="showAddVar()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Var\n </button>\n </div>\n <div id="addVarForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Display Name</label><input id="newVarName" type="text" placeholder="Friendly label for this variable"></div>\n <div class="field"><label>Use Env Var</label><input id="newVarEnvVar" type="text" placeholder="VARIABLE_NAME" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div class="field"><label>Value</label><input id="newVarApiKey" type="text" placeholder="The actual value for this variable"></div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addVar()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddVar()">Cancel</button>\n </div>\n </div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Env Var</th><th></th></tr></thead>\n <tbody id="varsBody"></tbody>\n </table>\n <div id="editVarForm" style="display:none;margin:12px 0;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Display Name</label><input id="editVarName" type="text"></div>\n <div class="field"><label>Use Env Var</label><input id="editVarEnvVar" type="text" placeholder="VARIABLE_NAME" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div class="field"><label>Value</label><input id="editVarApiKey" type="text"></div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="finishEditVar()">Save</button>\n <button class="btn-ghost btn-sm" onclick="cancelEditVar()">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Agent --\x3e\n <div id="sec-agent" class="section">\n <div class="section-header"><h1>Agent</h1><div style="display:flex;gap:8px"><button class="btn btn-ghost btn-sm" onclick="document.getElementById('agentHelpModal').classList.add('open')">Help</button><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div></div>\n <h2>Main</h2>\n <div class="card">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Default Model</label>\n <select id="agentModel"></select>\n </div>\n <div class="field" style="flex:1">\n <label>Fallback Model <span style="color:var(--text-muted);font-weight:400">— used on invocation errors</span></label>\n <select id="agentMainFallback"></select>\n </div>\n </div>\n </div>\n <h2>Configuration</h2>\n <div class="card" id="agentCard">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Max Turns</label>\n <input id="agentMaxTurns" type="number" min="1" max="100">\n </div>\n <div class="field" style="flex:1">\n <label>Permission Mode</label>\n <select id="agentPermMode">\n <option value="default">default</option>\n <option value="acceptEdits">acceptEdits</option>\n <option value="bypassPermissions">bypassPermissions</option>\n <option value="plan">plan</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Session TTL (seconds)</label>\n <input id="agentSessionTTL" type="number" min="60">\n </div>\n <div class="field" style="flex:1">\n <label>Setting Sources <span style="color:var(--text-muted);font-weight:400">— SDK settings to load</span></label>\n <select id="agentSettingSources">\n <option value="nothing">nothing</option>\n <option value="user">user</option>\n <option value="project">project</option>\n <option value="both">both</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Coder Skill <span style="color:var(--text-muted);font-weight:400">— always prepend builtin coding prompt</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentCoderSkill"><span></span></label></div>\n </div>\n <div class="field" style="flex:1">\n <label>AutoNew by inactivity (hr)</label>\n <select id="agentAutoRenew">\n <option value="0">Never</option>\n <option value="2">2</option>\n <option value="4">4</option>\n <option value="6">6</option>\n <option value="8">8</option>\n <option value="12">12</option>\n <option value="24">24</option>\n </select>\n </div>\n </div>\n <div class="field">\n <label>Allowed Tools</label>\n <div id="agentToolsGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:6px">\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Read"><span></span></label> Read</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Write"><span></span></label> Write</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Edit"><span></span></label> Edit</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Bash"><span></span></label> Bash</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Glob"><span></span></label> Glob</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Grep"><span></span></label> Grep</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="WebSearch"><span></span></label> WebSearch</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="WebFetch"><span></span></label> WebFetch</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Task"><span></span></label> Task</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Skill"><span></span></label> Skill</label>\n </div>\n <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="openInternalToolsModal()">Discover Internal Tools</button>\n </div>\n </div>\n <h2>Message Queue</h2>\n <div class="card" id="queueCard">\n <div class="field">\n <label>Queue Mode</label>\n <select id="agentQueueMode" onchange="updateQueueFields()">\n <option value="queue">queue — simple FIFO, one message = one response</option>\n <option value="collect">collect — batch messages while agent is busy</option>\n <option value="steer">steer — new message interrupts the current one</option>\n </select>\n </div>\n <div id="queueCollectFields">\n <div class="field">\n <label>Debounce (ms) <span style="color:var(--text-muted);font-weight:400">— quiet period after last message before flushing the batch</span></label>\n <input id="agentDebounceMs" type="number" min="0" step="100">\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Queue Cap <span style="color:var(--text-muted);font-weight:400">(0 = unlimited)</span></label>\n <input id="agentQueueCap" type="number" min="0">\n </div>\n <div class="field" style="flex:1">\n <label>Drop Policy <span style="color:var(--text-muted);font-weight:400">— when cap is exceeded</span></label>\n <select id="agentDropPolicy">\n <option value="new">new — reject incoming message</option>\n <option value="old">old — drop oldest buffered message</option>\n <option value="summarize">summarize — drop oldest, keep summary</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Inflight Typing <span style="color:var(--text-muted);font-weight:400">— keep typing indicator active when multiple messages are in-flight</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentInflightTyping"><span></span></label></div>\n </div>\n <div class="field" style="flex:1">\n <label>Auto Approve Tools <span style="color:var(--text-muted);font-weight:400">— auto-approve tool permissions; when off, asks user via channel buttons</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentAutoApprove"><span></span></label></div>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- SubAgents --\x3e\n <div id="sec-subagents" class="section">\n <div class="section-header"><h1>Custom Sub Agents</h1><div style="display:flex;gap:8px"><button class="btn btn-ghost btn-sm" onclick="loadSubAgents()">Refresh</button><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Sub Agents</span>\n <button class="btn btn-sm" onclick="showAddSubAgent()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Sub Agent\n </button>\n </div>\n <div id="addSubAgentForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Name</label><input id="newSaName" type="text" placeholder="code-reviewer"></div>\n <div class="field"><label>Description <span style="color:var(--text-muted);font-weight:400">— tells Claude when to use this subagent</span></label><textarea id="newSaDesc" rows="2" placeholder="Reviews code for bugs, style issues, and security vulnerabilities"></textarea></div>\n <div class="field"><label>Prompt <span style="color:var(--text-muted);font-weight:400">— defines the subagent's behavior and expertise</span></label><textarea id="newSaPrompt" rows="3" placeholder="You are an expert code reviewer. Analyze the provided code for correctness, style, performance, and security..."></textarea></div>\n <div class="field"><label>Model</label><select id="newSaModel"><option value="inherit">inherit</option><option value="sonnet">sonnet</option><option value="opus">opus</option><option value="haiku">haiku</option></select></div>\n <div class="field"><label>Tools</label><div id="newSaToolsGrid" style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Read" checked><span></span></label> Read</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Write" checked><span></span></label> Write</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Edit" checked><span></span></label> Edit</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Bash"><span></span></label> Bash</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Glob" checked><span></span></label> Glob</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Grep" checked><span></span></label> Grep</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="WebSearch" checked><span></span></label> WebSearch</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="WebFetch" checked><span></span></label> WebFetch</label>\n </div></div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500">Expand Context <span style="color:var(--text-muted);font-weight:400">— prepend main system prompt to subagent prompt</span></span>\n <label class="toggle"><input type="checkbox" id="newSaExpandContext"><span></span></label>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addSubAgent()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddSubAgent()">Cancel</button>\n </div>\n </div>\n <div id="subAgentCards" style="max-height:60vh;overflow-y:auto"></div>\n <div id="subAgentEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No custom sub agents configured. Click "Add Sub Agent" to create one.</div>\n </div>\n </div>\n\n \x3c!-- Commands --\x3e\n <div id="sec-commands" class="section">\n <div class="section-header"><h1>Commands</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Commands</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadCommands()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddStandaloneCommand()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n New Command\n </button>\n <button class="btn btn-sm" onclick="showAddCommand()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>\n Upload Folder\n </button>\n </div>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Project-scoped commands defined in the workspace <code>.claude/commands</code> directory.</p>\n <div id="commandCreateWrap" style="display:none;margin-bottom:16px;border:1px solid var(--border);border-radius:8px;padding:16px">\n <h4 style="margin:0 0 12px">New Standalone Command</h4>\n <div style="display:flex;flex-direction:column;gap:10px">\n <div class="field"><label>Name <span style="color:var(--text-muted);font-weight:400">(a-z, A-Z, 0-9, -, _)</span></label><input id="cmdCreateName" type="text" placeholder="my-command" pattern="[a-zA-Z0-9_-]+" style="font-family:monospace"></div>\n <div class="field"><label>Description <span style="color:var(--text-muted);font-weight:400">(optional)</span></label><input id="cmdCreateDesc" type="text" placeholder="What this command does"></div>\n <div class="field"><label>Model <span style="color:var(--text-muted);font-weight:400">(optional)</span></label><input id="cmdCreateModel" type="text" placeholder="e.g. claude-sonnet-4-20250514"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:14px">\n <button class="btn-ghost btn-sm" onclick="hideAddStandaloneCommand()">Cancel</button>\n <button class="btn btn-sm" onclick="doCreateStandaloneCommand()">Create</button>\n </div>\n </div>\n <div id="commandAddWrap" style="display:none;margin-bottom:16px">\n <div id="commandDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a command folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="commandFolderInput" type="file" webkitdirectory style="display:none">\n <div id="commandUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddCommand()">Cancel</button>\n </div>\n </div>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="commandsFilter" type="text" placeholder="type to filter..." oninput="applyCommandsFilter()" style="font-size:12px"></div>\n <div id="commandsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="commandsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No commands found.</div>\n </div>\n </div>\n\n \x3c!-- Skills --\x3e\n <div id="sec-skills" class="section">\n <div class="section-header"><h1>Skills</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Skills</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadSkills()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddSkill()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Skill\n </button>\n </div>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Project-scoped skills defined in the workspace <code>.claude/skills</code> directory.</p>\n <div id="skillAddWrap" style="display:none;margin-bottom:16px">\n <div id="skillDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a skill folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="skillFolderInput" type="file" webkitdirectory style="display:none">\n <div id="skillUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddSkill()">Cancel</button>\n </div>\n </div>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="skillsFilter" type="text" placeholder="type to filter..." oninput="applySkillsFilter()" style="font-size:12px"></div>\n <div id="skillsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="skillsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No skills installed.</div>\n </div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Bundled Skills</span>\n <button class="btn-ghost btn-sm" onclick="loadSkills()">Refresh</button>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Pre-packaged skills ready to activate. Click <strong>Use</strong> to install a skill to your project.</p>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="bundledSkillsFilter" type="text" placeholder="type to filter..." oninput="applyBundledSkillsFilter()" style="font-size:12px"></div>\n <div id="bundledSkillsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="bundledSkillsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No bundled skills found.</div>\n </div>\n </div>\n\n \x3c!-- Plugins --\x3e\n <div id="sec-plugins" class="section">\n <div class="section-header"><h1>Plugins</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Plugins</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadPlugins()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddPlugin()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Plugin\n </button>\n </div>\n </div>\n <div id="pluginAddWrap" style="display:none;margin-bottom:16px">\n <div class="field" style="margin-bottom:10px">\n <label>Destination Path <span style="color:var(--text-muted);font-weight:400">— where the plugin folder will be created</span></label>\n <div style="display:flex;gap:8px;align-items:center">\n <input id="pluginDestPath" type="text" style="flex:1">\n <button class="btn btn-sm" onclick="verifyPluginDest()" id="pluginDestSetBtn">Set</button>\n </div>\n </div>\n <div id="pluginDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a plugin folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="pluginFolderInput" type="file" webkitdirectory style="display:none">\n <div id="pluginUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddPlugin()">Cancel</button>\n </div>\n </div>\n <div id="pluginCards" style="max-height:400px;overflow-y:auto"></div>\n <div id="pluginEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No plugins configured. Click "Add Plugin" to add one.</div>\n </div>\n </div>\n\n \x3c!-- STT --\x3e\n <div id="sec-stt" class="section">\n <div class="section-header"><h1>Speech-to-Text</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">STT</span>\n <label class="toggle"><input type="checkbox" id="sttEnabled"><span></span></label>\n </div>\n <div class="field">\n <label>Provider</label>\n <select id="sttProvider">\n <option value="openai-whisper">openai-whisper</option>\n <option value="local-whisper">local-whisper</option>\n </select>\n </div>\n <div id="sttOpenAI">\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model</label><select id="sttModelRef"></select></div>\n <div class="field" style="flex:1"><label>Language <span style="color:var(--text-muted);font-weight:400">— empty = auto-detect, or en, it, de, ...</span></label><input id="sttOAILang" type="text" placeholder="auto-detect"></div>\n </div>\n </div>\n <div id="sttLocal" style="display:none">\n <div class="field"><label>Binary Path</label><input id="sttLocalBin" type="text"></div>\n <div class="field"><label>Model</label><input id="sttLocalModel" type="text"></div>\n </div>\n </div>\n </div>\n\n \x3c!-- TTS --\x3e\n <div id="sec-tts" class="section">\n <div class="section-header"><h1>Text-to-Speech</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">TTS</span>\n <label class="toggle"><input type="checkbox" id="ttsEnabled"><span></span></label>\n </div>\n <div class="field">\n <label>Provider</label>\n <select id="ttsProvider">\n <option value="openai">OpenAI</option>\n <option value="edge">Edge TTS (free)</option>\n <option value="elevenlabs">ElevenLabs</option>\n </select>\n </div>\n <div id="ttsEdge" style="display:none">\n <div class="field"><label>Voice</label><input id="ttsEdgeVoice" type="text" placeholder="en-US-MichelleNeural"></div>\n </div>\n <div id="ttsOpenAI">\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model Ref <span style="color:var(--text-muted);font-weight:400">— from registry (for API key)</span></label><select id="ttsOAIModelRef"></select></div>\n <div class="field" style="flex:1"><label>Model</label><input id="ttsOAIModel" type="text" placeholder="gpt-4o-mini-tts"></div>\n </div>\n <div class="field"><label>Voice</label><input id="ttsOAIVoice" type="text" placeholder="alloy"></div>\n </div>\n <div id="ttsElevenLabs" style="display:none">\n <div class="field"><label>Model Ref <span style="color:var(--text-muted);font-weight:400">— from registry (for API key)</span></label><select id="ttsELModelRef"></select></div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Voice ID</label><input id="ttsELVoiceId" type="text" placeholder="pMsXgVXv3BLzUgSXRplE"></div>\n <div class="field" style="flex:1"><label>Model ID</label><input id="ttsELModelId" type="text" placeholder="eleven_multilingual_v2"></div>\n </div>\n </div>\n <div class="field-row" style="margin-top:12px">\n <div class="field" style="flex:1"><label>Max text length</label><input id="ttsMaxTextLength" type="number" value="4096"></div>\n <div class="field" style="flex:1"><label>Timeout (ms)</label><input id="ttsTimeoutMs" type="number" value="30000"></div>\n </div>\n </div>\n </div>\n\n \x3c!-- Memories --\x3e\n <div id="sec-memory" class="section">\n <div class="section-header"><h1>Memories</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Memories</span>\n <label class="toggle"><input type="checkbox" id="memEnabled"><span></span></label>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">These settings do not affect how MEMORY.md is updated. They control whether the agent can search through past conversation memories using dedicated memory tools. When Recall Strategy is set to <strong>search</strong>, the <code>{{SEARCH_IN_MEMORIES}}</code> placeholder in the system prompt template is populated with instructions for <code>memory_search</code> and <code>memory_get</code>. When set to <strong>builtin-only</strong>, the placeholder resolves to empty.</p>\n <div class="field"><label>Directory</label><input id="memDir" type="text"></div>\n <div class="field">\n <label>Recall Strategy</label>\n <select id="memStrategy" onchange="updateMemSearchFields()">\n <option value="builtin-only">builtin-only</option>\n <option value="search">search</option>\n </select>\n </div>\n <div id="memSearchSettings" style="display:none">\n <div style="border-top:1px solid var(--border);margin-top:12px;padding-top:12px">\n <span style="font-size:13px;font-weight:600;color:var(--text-primary)">Search Settings</span>\n <p style="font-size:12px;color:var(--text-muted);margin:4px 0 12px">Hybrid BM25 + semantic search over conversation memories. Requires an OpenAI-compatible embedding API.</p>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model (API key)</label><select id="memSearchModelRef"></select></div>\n <div class="field" style="flex:1"><label>Embedding Model</label><input id="memSearchEmbModel" type="text" value="text-embedding-3-small"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Prefix Query</label><input id="memSearchPrefixQuery" type="text" placeholder=""></div>\n <div class="field" style="flex:1"><label>Prefix Document</label><input id="memSearchPrefixDocument" type="text" placeholder=""></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Embedding Dimensions: <strong id="memSearchDimsValue">1536</strong></label>\n <input id="memSearchDims" type="range" min="512" max="4096" step="1" value="1536"\n oninput="document.getElementById('memSearchDimsValue').textContent=this.value">\n <div style="display:flex;position:relative;margin-top:2px;height:14px;pointer-events:none;user-select:none">\n <span style="position:absolute;left:0;font-size:11px;color:var(--text-muted)">512</span>\n <span style="position:absolute;left:28.6%;font-size:11px;color:var(--text-muted);transform:translateX(-50%)">1536</span>\n <span style="position:absolute;right:0;font-size:11px;color:var(--text-muted)">4096</span>\n </div>\n </div>\n <div class="field" style="flex:1"><label>Max Results</label><input id="memSearchMaxResults" type="number" value="6" min="1" max="100"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Index Debounce (ms)</label><input id="memSearchDebounce" type="number" value="3000" min="500"></div>\n <div class="field" style="flex:1"><label>Embed Interval (ms)</label><input id="memSearchEmbedInterval" type="number" value="300000" min="10000"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Max Snippet Chars</label><input id="memSearchMaxSnippet" type="number" value="700" min="100"></div>\n <div class="field" style="flex:1"><label>Max Injected Chars <span style="color:var(--text-muted);font-weight:400">— total output cap for search results (0 = unlimited)</span></label><input id="memSearchMaxInjected" type="number" value="4000" min="0"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>RRF K <span style="color:var(--text-muted);font-weight:400">— fusion constant, higher values blend keyword &amp; semantic more evenly (default 60)</span></label><input id="memSearchRrfK" type="number" value="60" min="1"></div>\n </div>\n <div style="margin-top:12px;display:flex;align-items:center;gap:12px">\n <button class="btn" onclick="testEmbedding()" id="memSearchTestBtn">Test Embedding</button>\n <span id="memSearchTestResult" style="font-size:13px"></span>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Cron --\x3e\n <div id="sec-cron" class="section">\n <div class="section-header"><h1>Cron &amp; Heartbeat</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Cron Scheduler</span>\n <label class="toggle"><input type="checkbox" id="cronEnabled"><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;padding:0 16px 12px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Isolated <span style="font-weight:400">— jobs run in their own session (<code>cron:name</code>) instead of sharing user chat context</span></span>\n <label class="toggle"><input type="checkbox" id="cronIsolated"><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;padding:0 16px 12px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Broadcast Events <span style="font-weight:400">— deliver responses to all known chats across all active channels</span></span>\n <label class="toggle"><input type="checkbox" id="cronBroadcast"><span></span></label>\n </div>\n </div>\n\n <h2>Heartbeat</h2>\n <div class="card" id="heartbeatCard">\n <div class="card-header">\n <span class="card-title">Heartbeat</span>\n <label class="toggle"><input type="checkbox" id="hbEnabled"><span></span></label>\n </div>\n <div id="hbFields">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Channel</label>\n <select id="hbChannel" onchange="onHbChannelChange();updateHbFields()">\n <option value="">-- select --</option>\n </select>\n </div>\n <div class="field" style="flex:1">\n <label>Chat ID</label>\n <select id="hbChatId" onchange="updateHbFields()">\n <option value="">-- select --</option>\n </select>\n </div>\n </div>\n <div id="hbWarning" style="display:none;font-size:12px;color:var(--warning);margin-top:4px"></div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Interval (ms) <span id="hbEveryHuman" style="font-weight:400;color:var(--text-muted)"></span></label>\n <input id="hbEvery" type="number" min="10000" step="1000" oninput="updateHbEveryHuman()">\n </div>\n <div class="field" style="flex:1">\n <label>Ack Max Chars</label>\n <input id="hbAckMaxChars" type="number" min="0">\n </div>\n </div>\n <div class="field" style="margin-top:8px">\n <label>Heartbeat Message</label>\n <textarea id="hbMessage" rows="4" oninput="updateHbFields()" style="width:100%;font-family:var(--mono);font-size:13px;resize:vertical;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px;color:var(--text)"></textarea>\n <div style="font-size:11px;color:var(--text-muted);margin-top:4px">The message sent to the agent on each heartbeat tick. The agent reads HEARTBEAT.md and follows its instructions.</div>\n </div>\n <div style="font-size:12px;color:var(--text-muted);margin-top:12px;line-height:1.5">\n <strong>How it works:</strong> Every N ms the system checks <code>HEARTBEAT.md</code> in the data directory.\n If the file is empty (only headers or comments), the check is skipped to save API calls.\n If it has actionable content, the agent is asked to evaluate the tasks and respond:\n <code>HEARTBEAT_OK</code> if nothing needs attention (suppressed, not delivered),\n or an alert message delivered to the configured channel.\n Heartbeat exchanges are never written to conversation memory.<br>\n <strong>Ack Max Chars:</strong> after stripping <code>HEARTBEAT_OK</code>, if the remaining text\n is shorter than this threshold it is treated as a courtesy acknowledgment and suppressed.\n Only responses exceeding this limit are delivered as real alerts.\n </div>\n <div style="margin-top:12px">\n <button class="btn btn-sm" onclick="simulateHeartbeat()">Simulate Heartbeat</button>\n </div>\n </div>\n </div>\n\n <h2>Cron Jobs</h2>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Jobs</span>\n <div style="display:flex;gap:8px">\n <span id="cronStatus" style="font-size:13px;color:var(--text-muted);align-self:center"></span>\n <button class="btn-ghost btn-sm" onclick="loadCronJobs()" title="Refresh">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>\n </button>\n <button class="btn btn-sm" onclick="showAddJob()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Job\n </button>\n </div>\n </div>\n\n <div id="addJobForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Name</label><input id="newJobName" type="text" placeholder="my-job"></div>\n <div class="field"><label>Description</label><input id="newJobDesc" type="text" placeholder="optional"></div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Channel</label>\n <select id="newJobChannel" onchange="onNewJobChannelChange()">\n <option value="">-- select --</option>\n </select>\n </div>\n <div class="field" style="flex:1">\n <label>Chat ID</label>\n <select id="newJobChatId">\n <option value="">-- select --</option>\n </select>\n </div>\n </div>\n <div class="field">\n <label>Message</label>\n <textarea id="newJobMessage" rows="2" placeholder="Message to send to agent"></textarea>\n </div>\n <div class="field">\n <label>Schedule Type</label>\n <select id="newJobSchedKind" onchange="updateJobSchedFields()">\n <option value="every">Interval (every N ms)</option>\n <option value="cron">Cron expression</option>\n <option value="at">One-shot (at date/time)</option>\n </select>\n </div>\n <div id="newJobSchedEvery" class="field">\n <label>Interval (ms)</label>\n <input id="newJobEveryMs" type="number" min="1000" step="1000" value="60000">\n </div>\n <div id="newJobSchedCron" class="field" style="display:none">\n <label>Cron Expression <span style="color:var(--text-muted);font-weight:400">(e.g. 0 */5 * * * *)</span></label>\n <input id="newJobCronExpr" type="text" placeholder="0 */5 * * * *">\n </div>\n <div id="newJobSchedAt" class="field" style="display:none">\n <label>Run At (ISO datetime)</label>\n <input id="newJobAtTime" type="datetime-local">\n </div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Isolated <span style="font-weight:400">— job runs in its own session (<code>cron:name</code>) instead of sharing the user's chat context</span></span>\n <label class="toggle"><input type="checkbox" id="newJobIsolated" checked><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Suppress HEARTBEAT_OK <span style="font-weight:400">— if the agent replies with HEARTBEAT_OK the message is not delivered to the chat</span></span>\n <label class="toggle"><input type="checkbox" id="newJobSuppress"><span></span></label>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addJob()">Create Job</button>\n <button class="btn-ghost btn-sm" onclick="hideAddJob()">Cancel</button>\n </div>\n </div>\n\n <div class="field" style="margin-bottom:10px;max-width:360px">\n <label>Filter</label>\n <input id="cronJobFilter" type="text" placeholder="type to filter..." oninput="applyCronJobFilter()" style="font-size:12px">\n </div>\n <div id="cronJobsWrap" style="position:relative;overflow-x:auto;overflow-y:auto;max-height:500px">\n <table class="tbl" style="min-width:max-content">\n <thead><tr><th>Name</th><th>Schedule</th><th>Session</th><th>Delivery</th><th>Status</th><th>Next Run</th><th></th></tr></thead>\n <tbody id="cronJobsBody"></tbody>\n </table>\n <div id="cronScrollHint" style="display:none;position:sticky;bottom:0;right:0;text-align:right;pointer-events:none;padding:4px 8px">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.6"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Logs --\x3e\n <div id="sec-logs" class="section">\n <div class="section-header"><h1>Logs</h1></div>\n <div class="card">\n <div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-bottom:14px">\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Log Level</label>\n <select id="logLevelSelect" onchange="changeLogLevel()" style="width:120px">\n <option value="debug">debug</option>\n <option value="info">info</option>\n <option value="warn">warn</option>\n <option value="error">error</option>\n </select>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Lines</label>\n <select id="logLinesSelect" onchange="changeLogLines()" style="width:100px">\n <option value="200">200</option>\n <option value="500">500</option>\n <option value="1000">1000</option>\n </select>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Auto Refresh</label>\n <label class="toggle"><input type="checkbox" id="logAutoRefresh" onchange="toggleLogAutoRefresh()"><span></span></label>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Verbose Logs</label>\n <label class="toggle"><input type="checkbox" id="logVerbose" onchange="toggleVerboseDebugLogs()"><span></span></label>\n </div>\n <div class="field" style="margin-bottom:0;flex:1;min-width:140px">\n <label>Filter</label>\n <input id="logFilter" type="text" placeholder="type to filter..." oninput="applyLogFilter()" style="font-size:12px">\n </div>\n <div style="margin-left:auto;display:flex;gap:8px;align-self:flex-end">\n <button class="btn btn-sm" onclick="loadLogLines()">Refresh</button>\n <button class="btn-ghost btn-sm" onclick="downloadCurrentLog()">Download</button>\n </div>\n </div>\n <pre id="logViewer" style="background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;font-size:12px;line-height:1.6;max-height:500px;overflow-y:auto;white-space:pre-wrap;word-break:break-all;color:var(--text)"></pre>\n <div style="margin-top:6px;font-size:12px;color:var(--text-muted)" id="logTotal"></div>\n </div>\n\n <h2>Archived Logs</h2>\n <div class="card">\n <table class="tbl">\n <thead><tr><th>File</th><th>Size</th><th>Modified</th><th></th></tr></thead>\n <tbody id="logFilesBody"></tbody>\n </table>\n </div>\n\n <h2>Memory Logs</h2>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:12px">Conversation logs organized by session. Select a session folder to browse its log files.</p>\n <div class="card">\n <div class="field">\n <label>Session</label>\n <select id="memorySessionSelect" onchange="loadMemoryFiles()">\n <option value="">Select a session...</option>\n </select>\n </div>\n <div id="memoryFileList" style="margin-top:12px;max-height:400px;overflow-y:auto"></div>\n </div>\n </div>\n\n \x3c!-- Nodes --\x3e\n <div id="sec-nodes" class="section">\n <div class="section-header"><h1>Nodes</h1></div>\n\n <div class="card">\n <div class="card-header"><span class="card-title">Connected Nodes</span><button class="btn btn-sm" onclick="loadNodes()" style="font-size:12px;padding:3px 8px">Refresh</button></div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Hostname</th><th>Platform</th><th>Arch</th><th>Connected Since</th></tr></thead>\n <tbody id="connectedNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="pendingNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Pending Approvals</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Signature</th><th>Requested At</th><th></th></tr></thead>\n <tbody id="pendingNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="approvedNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Approved Nodes</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Signature</th><th>Approved At</th><th>Last Seen</th><th></th></tr></thead>\n <tbody id="approvedNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="revokedNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Revoked Nodes</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Revoked At</th><th></th></tr></thead>\n <tbody id="revokedNodesBody"></tbody>\n </table>\n </div>\n </div>\n\n \x3c!-- Tokens --\x3e\n <div id="sec-tokens" class="section">\n <div class="section-header"><h1>Tokens</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">API Tokens</span>\n <button class="btn btn-sm" onclick="showCreateToken()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Create\n </button>\n </div>\n <div id="createTokenForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>User ID</label><input id="newTokenUser" type="text" placeholder="Unique user identifier (min 5 chars)"></div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Channel</label><select id="newTokenChannel"><option value="*">* (all)</option><option value="nostromo">nostromo</option><option value="responses">responses</option><option value="webchat">webchat</option></select></div>\n <div class="field" style="flex:1"><label>Label</label><input id="newTokenLabel" type="text" placeholder="optional label"></div>\n </div>\n <div style="display:flex;gap:8px;margin-top:4px">\n <button class="btn btn-sm" onclick="createToken()">Create Token</button>\n <button class="btn-ghost btn-sm" onclick="hideCreateToken()">Cancel</button>\n </div>\n </div>\n <div id="tokenTableWrap">\n <table class="tbl">\n <thead><tr><th>ID</th><th>Token</th><th>User</th><th>Channel</th><th>Label</th><th>Status</th><th></th></tr></thead>\n <tbody id="tokenBody"></tbody>\n </table>\n </div>\n </div>\n </div>\n\n \x3c!-- Prompts --\x3e\n <div id="sec-prompts" class="section">\n <div class="section-header">\n <h1>Prompts</h1>\n <button class="btn" onclick="simulatePrompt()">Simulate Building</button>\n </div>\n\n <div class="card">\n <div class="card-title" style="margin-bottom:4px">File Editor</div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:12px">Edit system prompt templates and workspace files. Templates use <code>{{PLACEHOLDER}}</code> syntax.</p>\n <div class="file-select-row">\n <select id="promptFileSelect" onchange="selectPromptFile()">\n <option value="">Select a file...</option>\n </select>\n <span id="promptFileBadge" class="file-badge" style="display:none"></span>\n <button class="btn btn-sm" id="promptSaveBtn" onclick="saveCurrentFile()" style="display:none">Save</button>\n <button class="btn-ghost btn-sm" onclick="showPlaceholderRef()" title="Placeholder Reference">{{...}}</button>\n </div>\n <div id="promptEditorWrap" class="monaco-wrap" style="display:none"></div>\n </div>\n\n </div>\n\n \x3c!-- Settings --\x3e\n <div id="sec-settings" class="section">\n <div class="section-header"><h1>Settings</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Appearance</div>\n <div style="display:flex;align-items:center;gap:12px">\n <span style="font-size:14px">Theme</span>\n <label class="toggle">\n <input type="checkbox" id="themeToggle" onchange="toggleTheme()">\n <span></span>\n </label>\n <span style="font-size:13px;color:var(--text-muted)" id="themeLabel">Light</span>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Server</div>\n <div class="field-row">\n <div class="field"><label>Bind Host</label><input type="text" value="" id="settingsHost" placeholder="127.0.0.1"></div>\n <div class="field"><label>Port</label><input type="number" value="" id="settingsUiPort"></div>\n <div class="field"><label>Timezone</label><input type="text" value="" id="settingsTimezone" placeholder="Europe/Rome"></div>\n </div>\n <div style="margin-top:8px">\n <p style="font-size:12px;color:var(--text-muted)">CLI <code>--host</code> / <code>--port</code> override config values at runtime. All network changes require a restart.</p>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Config Watcher</div>\n <div class="field-row" style="align-items:center">\n <div style="display:flex;align-items:center;gap:12px;flex:1">\n <label class="toggle">\n <input type="checkbox" id="settingsAutoRestart">\n <span></span>\n </label>\n <span style="font-size:14px">Auto-restart on config.yaml changes</span>\n </div>\n <div class="field" style="flex:0 0 160px;margin-bottom:0">\n <label>Check interval (sec)</label>\n <input type="number" id="settingsConfigCheckInterval" min="1" max="300">\n </div>\n </div>\n <div style="margin-top:8px">\n <p style="font-size:12px;color:var(--text-muted)">When auto-restart is disabled, config file changes are detected but the server does not restart. The check interval controls how often the config file is polled for changes.</p>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Access Key</div>\n <div id="currentKeyRow" class="current-key-row" style="display:none">\n <code id="currentKeyValue" class="current-key-value"></code>\n <button class="icon-btn" onclick="toggleKeyVisibility()" title="Show / Hide" id="keyEyeBtn">\n <svg id="keyEyeOff" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><path d="M1 1l22 22"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>\n <svg id="keyEyeOn" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>\n </button>\n <button class="icon-btn" onclick="copyKey()" title="Copy to clipboard">\n <svg id="keyCopyIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>\n <svg id="keyCheckIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><polyline points="20 6 9 17 4 12"/></svg>\n </button>\n </div>\n <p style="font-size:14px;color:var(--text-muted);margin-bottom:12px">Generate a new access key. The current key will be invalidated immediately.</p>\n <button class="btn" onclick="confirmRegenKey()">Regenerate Key</button>\n <div id="newKeyDisplay"></div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Session</div>\n <button class="btn-ghost" onclick="doLogout()">Log out</button>\n </div>\n </div>\n\n </main>\n</div>\n\n\x3c!-- Floating restart indicator --\x3e\n<div id="floatingRestart" class="floating-restart" style="display:none" onclick="showRestartModal()" title="Config changed — click to restart">\n <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>\n</div>\n\n\x3c!-- Toast --\x3e\n<div id="toast" class="toast"></div>\n`}
1
+ import{hostname as e}from"node:os";export function renderLayout(n){return`\n\x3c!-- Login view --\x3e\n<div id="loginView" class="login-wrap">\n <div class="login-card">\n <div style="margin:0 auto 12px;width:150px;height:150px">\n <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 240" width="150" height="138">\n <defs>\n <linearGradient id="gBody" x1="0" y1="0" x2=".3" y2="1"><stop offset="0" stop-color="#f472b6"/><stop offset="1" stop-color="#9d174d"/></linearGradient>\n <linearGradient id="gTop" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0" stop-color="#fce7f3"/><stop offset="1" stop-color="#f9a8d4"/></linearGradient>\n <linearGradient id="gScr" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0" stop-color="#1e293b"/><stop offset="1" stop-color="#0f172a"/></linearGradient>\n </defs>\n \x3c!-- monitor body --\x3e\n <rect x="18" y="8" width="224" height="172" rx="14" fill="#334155"/>\n <rect x="20" y="10" width="220" height="168" rx="13" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>\n \x3c!-- screen bezel --\x3e\n <rect x="32" y="20" width="196" height="148" rx="6" fill="url(#gScr)"/>\n \x3c!-- screen glow --\x3e\n <rect x="34" y="22" width="192" height="144" rx="5" fill="#0f172a" opacity=".6"/>\n \x3c!-- power led --\x3e\n <circle cx="130" cy="186" r="2.5" fill="#4ade80" opacity=".7"/>\n \x3c!-- stand --\x3e\n <path d="M105 188 L95 218 H165 L155 188" fill="#334155"/>\n <rect x="85" y="218" width="90" height="8" rx="3" fill="#475569"/>\n \x3c!-- gem centered on screen --\x3e\n <g transform="translate(130,96) scale(.56)">\n <path d="M0 90 L-82 0 -62 -46 Q-42 -74 0 -38 Q42 -74 62 -46 L82 0 Z" fill="url(#gBody)"/>\n <path d="M-62 -46 Q-42 -74 0 -38 Q42 -74 62 -46 L82 0 H-82 Z" fill="url(#gTop)" opacity=".55"/>\n <path d="M-82 0 L-62 -46 0 -38 Z" fill="#ec4899" opacity=".7"/>\n <path d="M82 0 L62 -46 0 -38 Z" fill="#be185d" opacity=".7"/>\n <path d="M0 -38 L-32 0 32 0 Z" fill="#fce7f3" opacity=".35"/>\n <path d="M-62 -46 L-82 0 -32 0 0 -38 Z" fill="#f9a8d4" opacity=".30"/>\n <path d="M62 -46 L82 0 32 0 0 -38 Z" fill="#db2777" opacity=".30"/>\n <path d="M-82 0 H-32 L0 90 Z" fill="#ec4899" opacity=".45"/>\n <path d="M-32 0 H32 L0 90 Z" fill="#db2777" opacity=".85"/>\n <path d="M32 0 H82 L0 90 Z" fill="#9d174d" opacity=".55"/>\n <path d="M-20 -28 L-14 -38 -8 -28 -14 -32Z" fill="#fff" opacity=".7"/>\n <circle cx="-22" cy="-40" r="2" fill="#fff" opacity=".6"/>\n </g>\n \x3c!-- subtle screen scanline overlay --\x3e\n <line x1="34" y1="60" x2="226" y2="60" stroke="#94a3b8" stroke-width=".3" opacity=".15"/>\n <line x1="34" y1="100" x2="226" y2="100" stroke="#94a3b8" stroke-width=".3" opacity=".12"/>\n <line x1="34" y1="140" x2="226" y2="140" stroke="#94a3b8" stroke-width=".3" opacity=".10"/>\n </svg>\n </div>\n <h1>Nostromo</h1>\n <p class="subtitle" id="loginSubtitle">Enter your access key</p>\n <div class="field">\n <label>Access Key</label>\n <div class="key-row">\n <div class="key-inputs" id="keyInputs">\n <input id="k0" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k1" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k2" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n <span class="key-sep">&ndash;</span>\n <input id="k3" type="password" maxlength="4" autocomplete="off" spellcheck="false" placeholder="">\n </div>\n <button type="button" class="eye-btn" id="eyeBtn" onclick="toggleKeyVis()" title="Show/hide key">\n <svg id="eyeOff" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><line x1="1" y1="1" x2="23" y2="23"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>\n <svg id="eyeOn" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>\n </button>\n </div>\n </div>\n <button class="btn" id="loginBtn" style="width:100%;margin-top:4px" onclick="doLogin()">Unlock</button>\n <div id="loginError" class="login-error"></div>\n </div>\n</div>\n\n\x3c!-- App shell (hidden until authenticated) --\x3e\n<div id="appShell" class="shell" style="display:none">\n <aside class="sidebar">\n <div class="logo">\n <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>\n <span>Nostromo</span>\n </div>\n <nav id="navMenu">\n <a href="#dashboard" data-section="dashboard" class="active">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>\n <span>Dashboard</span>\n </a>\n <div class="nav-group collapsed" id="navGroupEngine">\n <div class="nav-group-header" onclick="toggleNavGroup('Engine')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>\n <span>Engine</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#models" data-section="models">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>\n <span>Models</span>\n </a>\n <a href="#agent" data-section="agent">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>\n <span>Agent</span>\n </a>\n <a href="#subagents" data-section="subagents">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>\n <span>Sub Agents</span>\n </a>\n <a href="#vars" data-section="vars">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/><circle cx="12" cy="16" r="1"/></svg>\n <span>Vars</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupIdentity">\n <div class="nav-group-header" onclick="toggleNavGroup('Identity')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>\n <span>Artificial Identity</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#memory" data-section="memory">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"/></svg>\n <span>Memories</span>\n </a>\n <a href="#prompts" data-section="prompts">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>\n <span>Prompts</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupCompetences">\n <div class="nav-group-header" onclick="toggleNavGroup('Competences')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>\n <span>Competences</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#commands" data-section="commands">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Commands</span>\n </a>\n <a href="#skills" data-section="skills">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>\n <span>Skills</span>\n </a>\n <a href="#plugins" data-section="plugins">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v6m0 0l3-3m-3 3L9 5"/><rect x="4" y="8" width="16" height="12" rx="2"/><path d="M9 8V6a3 3 0 0 1 6 0v2"/></svg>\n <span>Plugins</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupInteractions">\n <div class="nav-group-header" onclick="toggleNavGroup('Interactions')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>\n <span>Interactions</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#channels" data-section="channels">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>\n <span>Channels</span>\n </a>\n <a href="#stt" data-section="stt">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>\n <span>STT</span>\n </a>\n <a href="#tts" data-section="tts">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>\n <span>TTS</span>\n </a>\n </div>\n </div>\n <div class="nav-group collapsed" id="navGroupOperations">\n <div class="nav-group-header" onclick="toggleNavGroup('Operations')">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Operations</span>\n <svg class="nav-group-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>\n </div>\n <div class="nav-group-items">\n <a href="#logs" data-section="logs">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>\n <span>Logs</span>\n </a>\n <a href="#cron" data-section="cron">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>\n <span>Cron</span>\n </a>\n <a href="#nodes" data-section="nodes">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>\n <span>Nodes</span>\n </a>\n <a href="#tokens" data-section="tokens">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>\n <span>Tokens</span>\n </a>\n </div>\n </div>\n <a href="#settings" data-section="settings">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>\n <span>Settings</span>\n </a>\n <span style="display:block;padding:8px 18px 0;font-size:12px;color:var(--text-muted);opacity:.6">Host: ${e()}</span>\n </nav>\n </aside>\n <main class="main">\n\n \x3c!-- Dashboard --\x3e\n <div id="sec-dashboard" class="section active">\n <div class="section-header"><h1>Dashboard</h1></div>\n <div class="card">\n <div class="card-header"><span class="card-title">Server Status</span><div style="display:flex;align-items:center;gap:8px"><span id="restartPending" class="restart-pending" style="display:none">Restart pending</span><span id="statusBadge" class="badge badge-green">Online</span><button class="btn-danger btn-sm" onclick="showRestartModal()" style="font-size:12px;padding:3px 8px">Restart</button></div></div>\n <div style="display:flex;justify-content:space-between;font-size:14px">\n <div><span style="color:var(--text-muted)">Uptime:</span> <span id="statusUptime">--</span></div>\n <div style="text-align:right">\n <div><span style="color:var(--text-muted)">Agent:</span> <span id="statusModel">--</span></div>\n <div style="margin-top:4px"><span style="color:var(--text-muted)">Fallback:</span> <span id="statusFallback">--</span></div>\n </div>\n </div>\n <div style="margin-top:10px;font-size:14px"><span style="color:var(--text-muted)">Auto-Restart:</span> <span id="statusAutoRestart">--</span></div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:10px">Enabled Channels</div>\n <div id="dashChannels" style="display:flex;flex-wrap:wrap;gap:8px"></div>\n </div>\n </div>\n\n \x3c!-- Channels --\x3e\n <div id="sec-channels" class="section">\n <div class="section-header"><h1>Channels</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div id="channelCards"></div>\n </div>\n\n \x3c!-- Models --\x3e\n <div id="sec-models" class="section">\n <div class="section-header"><h1>Models</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Model Registry</span>\n <button class="btn btn-sm" onclick="showAddModel()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Model\n </button>\n </div>\n <div id="addModelForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Model ID</label><input id="newModelId" type="text" placeholder="claude-sonnet-4-5-20250514"></div>\n <div class="field"><label>Display Name</label><input id="newModelName" type="text" placeholder="Friendly label for this model"></div>\n <div class="field"><label>Type</label><select id="newModelType" onchange="updateNewModelApiFields()">\n <option value="internal">internal</option>\n <option value="external">external</option>\n </select></div>\n <div id="newModelProxyField" style="display:none">\n <div class="field"><label>Other LLM Provider</label><select id="newModelProxy" onchange="updateNewModelProxyFields()">\n <option value="not-used">not-used</option>\n <option value="direct">direct</option>\n <option value="proxied">proxied</option>\n </select></div>\n <div class="field"><label>OpenAI Compatible Proxy URL</label><input id="newModelFastUrl" type="text" placeholder="http://localhost:4181" disabled></div>\n <div class="field"><label>Proxy API Key</label><input id="newModelFastProxyApiKey" type="text" placeholder="sk-..." disabled></div>\n </div>\n <div id="newModelApiFields" style="display:none">\n <div class="field"><label>Use Env Var <span style="color:var(--text-muted);font-weight:400">(if empty, OPENAI_API_KEY is used)</span></label><input id="newModelEnvVar" type="text" placeholder="Force Variable Name" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div id="newModelBaseURLField" class="field"><label>API Base URL <span style="color:var(--text-muted);font-weight:400">(leave empty for provider default)</span></label><input id="newModelBaseURL" type="text" placeholder="https://api.openai.com/v1"></div>\n <div class="field"><label>API Key</label><input id="newModelApiKey" type="text" placeholder="The actual value for this variable"></div>\n </div>\n <div id="newModelExtraFields" style="display:none">\n <div class="field">\n <label>Context Window <span style="color:var(--text-muted);font-weight:400">(tokens)</span></label>\n <input id="newModelContextWindow" type="text" inputmode="numeric" value="200000" onkeypress="return /[0-9]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9]/g,'')">\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Cost Input <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="newModelCostInput" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n <div class="field" style="flex:1"><label>Cost Output <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="newModelCostOutput" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Cost Cache Read <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="newModelCostCacheRead" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n <div class="field" style="flex:1"><label>Cost Cache Write <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="newModelCostCacheWrite" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n </div>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addModel()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddModel()">Cancel</button>\n </div>\n </div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Model ID</th><th>Types</th><th></th></tr></thead>\n <tbody id="modelsBody"></tbody>\n </table>\n <div id="editModelForm" style="display:none;margin:12px 0;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Model ID</label><input id="editModelId" type="text"></div>\n <div class="field"><label>Display Name</label><input id="editModelName" type="text"></div>\n <div class="field"><label>Type</label><select id="editModelType" onchange="updateEditModelApiFields()">\n <option value="internal">internal</option>\n <option value="external">external</option>\n </select></div>\n <div id="editModelProxyField" style="display:none">\n <div class="field"><label>Other LLM Provider</label><select id="editModelProxy" onchange="updateEditModelProxyFields()">\n <option value="not-used">not-used</option>\n <option value="direct">direct</option>\n <option value="proxied">proxied</option>\n </select></div>\n <div class="field"><label>OpenAI Compatible Proxy URL</label><input id="editModelFastUrl" type="text" placeholder="http://localhost:4181" disabled></div>\n <div class="field"><label>Proxy API Key</label><input id="editModelFastProxyApiKey" type="text" placeholder="sk-..." disabled></div>\n </div>\n <div id="editModelApiFields" style="display:none">\n <div class="field"><label>Use Env Var <span style="color:var(--text-muted);font-weight:400">(if empty, OPENAI_API_KEY is used)</span></label><input id="editModelEnvVar" type="text" placeholder="Force Variable Name" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div id="editModelBaseURLField" class="field"><label>API Base URL</label><input id="editModelBaseURL" type="text"></div>\n <div class="field"><label>API Key</label><input id="editModelApiKey" type="text"></div>\n </div>\n <div id="editModelExtraFields" style="display:none">\n <div class="field">\n <label>Context Window <span style="color:var(--text-muted);font-weight:400">(tokens)</span></label>\n <input id="editModelContextWindow" type="text" inputmode="numeric" value="200000" onkeypress="return /[0-9]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9]/g,'')">\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Cost Input <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="editModelCostInput" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n <div class="field" style="flex:1"><label>Cost Output <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="editModelCostOutput" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Cost Cache Read <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="editModelCostCacheRead" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n <div class="field" style="flex:1"><label>Cost Cache Write <span style="color:var(--text-muted);font-weight:400">($/1M tok)</span></label>\n <input id="editModelCostCacheWrite" type="text" inputmode="decimal" value="0" onkeypress="return /[0-9.]/.test(event.key)" oninput="this.value=this.value.replace(/[^0-9.]/g,'')"></div>\n </div>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="finishEditModel()">Save</button>\n <button class="btn-ghost btn-sm" onclick="cancelEditModel()">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Vars --\x3e\n <div id="sec-vars" class="section">\n <div class="section-header"><h1>Vars</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Vars Registry</span>\n <button class="btn btn-sm" onclick="showAddVar()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Var\n </button>\n </div>\n <div id="addVarForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Display Name</label><input id="newVarName" type="text" placeholder="Friendly label for this variable"></div>\n <div class="field"><label>Use Env Var</label><input id="newVarEnvVar" type="text" placeholder="VARIABLE_NAME" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div class="field"><label>Value</label><input id="newVarApiKey" type="text" placeholder="The actual value for this variable"></div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addVar()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddVar()">Cancel</button>\n </div>\n </div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Env Var</th><th></th></tr></thead>\n <tbody id="varsBody"></tbody>\n </table>\n <div id="editVarForm" style="display:none;margin:12px 0;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Display Name</label><input id="editVarName" type="text"></div>\n <div class="field"><label>Use Env Var</label><input id="editVarEnvVar" type="text" placeholder="VARIABLE_NAME" oninput="sanitizeEnvVarInput(this)" style="text-transform:uppercase"></div>\n <div class="field"><label>Value</label><input id="editVarApiKey" type="text"></div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="finishEditVar()">Save</button>\n <button class="btn-ghost btn-sm" onclick="cancelEditVar()">Cancel</button>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Agent --\x3e\n <div id="sec-agent" class="section">\n <div class="section-header"><h1>Agent</h1><div style="display:flex;gap:8px"><button class="btn btn-ghost btn-sm" onclick="document.getElementById('agentHelpModal').classList.add('open')">Help</button><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div></div>\n <h2>Main</h2>\n <div class="card">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Default Model</label>\n <select id="agentModel"></select>\n </div>\n <div class="field" style="flex:1">\n <label>Fallback Model <span style="color:var(--text-muted);font-weight:400">— used on invocation errors</span></label>\n <select id="agentMainFallback"></select>\n </div>\n </div>\n </div>\n <h2>Configuration</h2>\n <div class="card" id="agentCard">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Max Turns</label>\n <input id="agentMaxTurns" type="number" min="1" max="100">\n </div>\n <div class="field" style="flex:1">\n <label>Permission Mode</label>\n <select id="agentPermMode">\n <option value="default">default</option>\n <option value="acceptEdits">acceptEdits</option>\n <option value="bypassPermissions">bypassPermissions</option>\n <option value="plan">plan</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Session TTL (seconds)</label>\n <input id="agentSessionTTL" type="number" min="60">\n </div>\n <div class="field" style="flex:1">\n <label>Setting Sources <span style="color:var(--text-muted);font-weight:400">— SDK settings to load</span></label>\n <select id="agentSettingSources">\n <option value="nothing">nothing</option>\n <option value="user">user</option>\n <option value="project">project</option>\n <option value="both">both</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Coder Skill <span style="color:var(--text-muted);font-weight:400">— always prepend builtin coding prompt</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentCoderSkill"><span></span></label></div>\n </div>\n <div class="field" style="flex:1">\n <label>AutoNew by inactivity (hr)</label>\n <select id="agentAutoRenew">\n <option value="0">Never</option>\n <option value="2">2</option>\n <option value="4">4</option>\n <option value="6">6</option>\n <option value="8">8</option>\n <option value="12">12</option>\n <option value="24">24</option>\n </select>\n </div>\n </div>\n <div class="field">\n <label>Allowed Tools</label>\n <div id="agentToolsGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:6px">\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Read"><span></span></label> Read</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Write"><span></span></label> Write</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Edit"><span></span></label> Edit</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Bash"><span></span></label> Bash</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Glob"><span></span></label> Glob</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Grep"><span></span></label> Grep</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="WebSearch"><span></span></label> WebSearch</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="WebFetch"><span></span></label> WebFetch</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Task"><span></span></label> Task</label>\n <label class="tool-toggle"><label class="toggle"><input type="checkbox" data-tool="Skill"><span></span></label> Skill</label>\n </div>\n <button class="btn btn-ghost btn-sm" style="margin-top:8px" onclick="openInternalToolsModal()">Discover Internal Tools</button>\n </div>\n </div>\n <h2>Message Queue</h2>\n <div class="card" id="queueCard">\n <div class="field">\n <label>Queue Mode</label>\n <select id="agentQueueMode" onchange="updateQueueFields()">\n <option value="queue">queue — simple FIFO, one message = one response</option>\n <option value="collect">collect — batch messages while agent is busy</option>\n <option value="steer">steer — new message interrupts the current one</option>\n </select>\n </div>\n <div id="queueCollectFields">\n <div class="field">\n <label>Debounce (ms) <span style="color:var(--text-muted);font-weight:400">— quiet period after last message before flushing the batch</span></label>\n <input id="agentDebounceMs" type="number" min="0" step="100">\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Queue Cap <span style="color:var(--text-muted);font-weight:400">(0 = unlimited)</span></label>\n <input id="agentQueueCap" type="number" min="0">\n </div>\n <div class="field" style="flex:1">\n <label>Drop Policy <span style="color:var(--text-muted);font-weight:400">— when cap is exceeded</span></label>\n <select id="agentDropPolicy">\n <option value="new">new — reject incoming message</option>\n <option value="old">old — drop oldest buffered message</option>\n <option value="summarize">summarize — drop oldest, keep summary</option>\n </select>\n </div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Inflight Typing <span style="color:var(--text-muted);font-weight:400">— keep typing indicator active when multiple messages are in-flight</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentInflightTyping"><span></span></label></div>\n </div>\n <div class="field" style="flex:1">\n <label>Auto Approve Tools <span style="color:var(--text-muted);font-weight:400">— auto-approve tool permissions; when off, asks user via channel buttons</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="agentAutoApprove"><span></span></label></div>\n </div>\n </div>\n </div>\n <h2>Pico Agent</h2>\n <div class="card" id="picoCard">\n <div class="field">\n <label>Enable Pico Agent <span style="color:var(--text-muted);font-weight:400">— use pi-ai multi-provider engine</span></label>\n <div style="margin-top:6px"><label class="toggle"><input type="checkbox" id="picoEnabled" onchange="updatePicoFields()"><span></span></label></div>\n </div>\n <div id="picoFields" style="display:none">\n <div class="field">\n <label>Add Model</label>\n <div style="display:flex;gap:8px">\n <select id="picoModelSelect" style="flex:1"></select>\n <button class="btn btn-sm" onclick="addPicoModel()">Add</button>\n </div>\n </div>\n <div id="picoModelList" class="pico-model-list"></div>\n <div class="field" style="margin-top:14px">\n <label>Rolling Memory Model <span style="color:var(--text-muted);font-weight:400">— for context summarization (blank = use default model)</span></label>\n <div style="display:flex;gap:8px;align-items:center">\n <select id="picoRollingModel" style="flex:1"></select>\n <button class="btn-ghost btn-sm" onclick="clearRollingModel()" title="Clear" style="font-size:18px;padding:2px 8px">&times;</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- SubAgents --\x3e\n <div id="sec-subagents" class="section">\n <div class="section-header"><h1>Custom Sub Agents</h1><div style="display:flex;gap:8px"><button class="btn btn-ghost btn-sm" onclick="loadSubAgents()">Refresh</button><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Sub Agents</span>\n <button class="btn btn-sm" onclick="showAddSubAgent()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Sub Agent\n </button>\n </div>\n <div id="addSubAgentForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Name</label><input id="newSaName" type="text" placeholder="code-reviewer"></div>\n <div class="field"><label>Description <span style="color:var(--text-muted);font-weight:400">— tells Claude when to use this subagent</span></label><textarea id="newSaDesc" rows="2" placeholder="Reviews code for bugs, style issues, and security vulnerabilities"></textarea></div>\n <div class="field"><label>Prompt <span style="color:var(--text-muted);font-weight:400">— defines the subagent's behavior and expertise</span></label><textarea id="newSaPrompt" rows="3" placeholder="You are an expert code reviewer. Analyze the provided code for correctness, style, performance, and security..."></textarea></div>\n <div class="field"><label>Model</label><select id="newSaModel"><option value="inherit">inherit</option><option value="sonnet">sonnet</option><option value="opus">opus</option><option value="haiku">haiku</option></select></div>\n <div class="field"><label>Tools</label><div id="newSaToolsGrid" style="display:flex;flex-wrap:wrap;gap:4px;margin-top:4px">\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Read" checked><span></span></label> Read</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Write" checked><span></span></label> Write</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Edit" checked><span></span></label> Edit</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Bash"><span></span></label> Bash</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Glob" checked><span></span></label> Glob</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="Grep" checked><span></span></label> Grep</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="WebSearch" checked><span></span></label> WebSearch</label>\n <label class="tool-toggle-sm"><label class="toggle-sm"><input type="checkbox" data-new-sa-tool="WebFetch" checked><span></span></label> WebFetch</label>\n </div></div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500">Expand Context <span style="color:var(--text-muted);font-weight:400">— prepend main system prompt to subagent prompt</span></span>\n <label class="toggle"><input type="checkbox" id="newSaExpandContext"><span></span></label>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addSubAgent()">Add</button>\n <button class="btn-ghost btn-sm" onclick="hideAddSubAgent()">Cancel</button>\n </div>\n </div>\n <div id="subAgentCards" style="max-height:60vh;overflow-y:auto"></div>\n <div id="subAgentEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No custom sub agents configured. Click "Add Sub Agent" to create one.</div>\n </div>\n </div>\n\n \x3c!-- Commands --\x3e\n <div id="sec-commands" class="section">\n <div class="section-header"><h1>Commands</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Commands</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadCommands()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddStandaloneCommand()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n New Command\n </button>\n <button class="btn btn-sm" onclick="showAddCommand()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>\n Upload Folder\n </button>\n </div>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Project-scoped commands defined in the workspace <code>.claude/commands</code> directory.</p>\n <div id="commandCreateWrap" style="display:none;margin-bottom:16px;border:1px solid var(--border);border-radius:8px;padding:16px">\n <h4 style="margin:0 0 12px">New Standalone Command</h4>\n <div style="display:flex;flex-direction:column;gap:10px">\n <div class="field"><label>Name <span style="color:var(--text-muted);font-weight:400">(a-z, A-Z, 0-9, -, _)</span></label><input id="cmdCreateName" type="text" placeholder="my-command" pattern="[a-zA-Z0-9_-]+" style="font-family:monospace"></div>\n <div class="field"><label>Description <span style="color:var(--text-muted);font-weight:400">(optional)</span></label><input id="cmdCreateDesc" type="text" placeholder="What this command does"></div>\n <div class="field"><label>Model <span style="color:var(--text-muted);font-weight:400">(optional)</span></label><input id="cmdCreateModel" type="text" placeholder="e.g. claude-sonnet-4-20250514"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:14px">\n <button class="btn-ghost btn-sm" onclick="hideAddStandaloneCommand()">Cancel</button>\n <button class="btn btn-sm" onclick="doCreateStandaloneCommand()">Create</button>\n </div>\n </div>\n <div id="commandAddWrap" style="display:none;margin-bottom:16px">\n <div id="commandDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a command folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="commandFolderInput" type="file" webkitdirectory style="display:none">\n <div id="commandUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddCommand()">Cancel</button>\n </div>\n </div>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="commandsFilter" type="text" placeholder="type to filter..." oninput="applyCommandsFilter()" style="font-size:12px"></div>\n <div id="commandsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="commandsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No commands found.</div>\n </div>\n </div>\n\n \x3c!-- Skills --\x3e\n <div id="sec-skills" class="section">\n <div class="section-header"><h1>Skills</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Skills</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadSkills()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddSkill()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Skill\n </button>\n </div>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Project-scoped skills defined in the workspace <code>.claude/skills</code> directory.</p>\n <div id="skillAddWrap" style="display:none;margin-bottom:16px">\n <div id="skillDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a skill folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="skillFolderInput" type="file" webkitdirectory style="display:none">\n <div id="skillUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddSkill()">Cancel</button>\n </div>\n </div>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="skillsFilter" type="text" placeholder="type to filter..." oninput="applySkillsFilter()" style="font-size:12px"></div>\n <div id="skillsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="skillsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No skills installed.</div>\n </div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Bundled Skills</span>\n <button class="btn-ghost btn-sm" onclick="loadSkills()">Refresh</button>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">Pre-packaged skills ready to activate. Click <strong>Use</strong> to install a skill to your project.</p>\n <div class="field" style="margin-bottom:10px;max-width:360px"><label>Filter</label><input id="bundledSkillsFilter" type="text" placeholder="type to filter..." oninput="applyBundledSkillsFilter()" style="font-size:12px"></div>\n <div id="bundledSkillsList" style="max-height:400px;overflow-y:auto"></div>\n <div id="bundledSkillsEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No bundled skills found.</div>\n </div>\n </div>\n\n \x3c!-- Plugins --\x3e\n <div id="sec-plugins" class="section">\n <div class="section-header"><h1>Plugins</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Installed Plugins</span>\n <div style="display:flex;gap:8px">\n <button class="btn-ghost btn-sm" onclick="loadPlugins()">Refresh</button>\n <button class="btn btn-sm" onclick="showAddPlugin()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Plugin\n </button>\n </div>\n </div>\n <div id="pluginAddWrap" style="display:none;margin-bottom:16px">\n <div class="field" style="margin-bottom:10px">\n <label>Destination Path <span style="color:var(--text-muted);font-weight:400">— where the plugin folder will be created</span></label>\n <div style="display:flex;gap:8px;align-items:center">\n <input id="pluginDestPath" type="text" style="flex:1">\n <button class="btn btn-sm" onclick="verifyPluginDest()" id="pluginDestSetBtn">Set</button>\n </div>\n </div>\n <div id="pluginDropZone" class="drop-zone">\n <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--text-muted)"><path d="M12 16V4"/><path d="M8 8l4-4 4 4"/><path d="M20 21H4"/><path d="M20 17v4H4v-4"/></svg>\n <div style="font-weight:600;margin:8px 0 4px">Drop a plugin folder here</div>\n <div style="font-size:13px;color:var(--text-muted)">or click to select a folder</div>\n <input id="pluginFolderInput" type="file" webkitdirectory style="display:none">\n <div id="pluginUploadStatus" style="display:none;margin-top:12px;font-size:13px"></div>\n </div>\n <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:10px">\n <button class="btn-ghost btn-sm" onclick="hideAddPlugin()">Cancel</button>\n </div>\n </div>\n <div id="pluginCards" style="max-height:400px;overflow-y:auto"></div>\n <div id="pluginEmpty" style="font-size:14px;color:var(--text-muted);padding:8px 0">No plugins configured. Click "Add Plugin" to add one.</div>\n </div>\n </div>\n\n \x3c!-- STT --\x3e\n <div id="sec-stt" class="section">\n <div class="section-header"><h1>Speech-to-Text</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">STT</span>\n <label class="toggle"><input type="checkbox" id="sttEnabled"><span></span></label>\n </div>\n <div class="field">\n <label>Provider</label>\n <select id="sttProvider">\n <option value="openai-whisper">openai-whisper</option>\n <option value="local-whisper">local-whisper</option>\n </select>\n </div>\n <div id="sttOpenAI">\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model</label><select id="sttModelRef"></select></div>\n <div class="field" style="flex:1"><label>Language <span style="color:var(--text-muted);font-weight:400">— empty = auto-detect, or en, it, de, ...</span></label><input id="sttOAILang" type="text" placeholder="auto-detect"></div>\n </div>\n </div>\n <div id="sttLocal" style="display:none">\n <div class="field"><label>Binary Path</label><input id="sttLocalBin" type="text"></div>\n <div class="field"><label>Model</label><input id="sttLocalModel" type="text"></div>\n </div>\n </div>\n </div>\n\n \x3c!-- TTS --\x3e\n <div id="sec-tts" class="section">\n <div class="section-header"><h1>Text-to-Speech</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">TTS</span>\n <label class="toggle"><input type="checkbox" id="ttsEnabled"><span></span></label>\n </div>\n <div class="field">\n <label>Provider</label>\n <select id="ttsProvider">\n <option value="openai">OpenAI</option>\n <option value="edge">Edge TTS (free)</option>\n <option value="elevenlabs">ElevenLabs</option>\n </select>\n </div>\n <div id="ttsEdge" style="display:none">\n <div class="field"><label>Voice</label><input id="ttsEdgeVoice" type="text" placeholder="en-US-MichelleNeural"></div>\n </div>\n <div id="ttsOpenAI">\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model Ref <span style="color:var(--text-muted);font-weight:400">— from registry (for API key)</span></label><select id="ttsOAIModelRef"></select></div>\n <div class="field" style="flex:1"><label>Model</label><input id="ttsOAIModel" type="text" placeholder="gpt-4o-mini-tts"></div>\n </div>\n <div class="field"><label>Voice</label><input id="ttsOAIVoice" type="text" placeholder="alloy"></div>\n </div>\n <div id="ttsElevenLabs" style="display:none">\n <div class="field"><label>Model Ref <span style="color:var(--text-muted);font-weight:400">— from registry (for API key)</span></label><select id="ttsELModelRef"></select></div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Voice ID</label><input id="ttsELVoiceId" type="text" placeholder="pMsXgVXv3BLzUgSXRplE"></div>\n <div class="field" style="flex:1"><label>Model ID</label><input id="ttsELModelId" type="text" placeholder="eleven_multilingual_v2"></div>\n </div>\n </div>\n <div class="field-row" style="margin-top:12px">\n <div class="field" style="flex:1"><label>Max text length</label><input id="ttsMaxTextLength" type="number" value="4096"></div>\n <div class="field" style="flex:1"><label>Timeout (ms)</label><input id="ttsTimeoutMs" type="number" value="30000"></div>\n </div>\n </div>\n </div>\n\n \x3c!-- Memories --\x3e\n <div id="sec-memory" class="section">\n <div class="section-header"><h1>Memories</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Memories</span>\n <label class="toggle"><input type="checkbox" id="memEnabled"><span></span></label>\n </div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:14px">These settings do not affect how MEMORY.md is updated. They control whether the agent can search through past conversation memories using dedicated memory tools. When Recall Strategy is set to <strong>search</strong>, the <code>{{SEARCH_IN_MEMORIES}}</code> placeholder in the system prompt template is populated with instructions for <code>memory_search</code> and <code>memory_get</code>. When set to <strong>builtin-only</strong>, the placeholder resolves to empty.</p>\n <div class="field"><label>Directory</label><input id="memDir" type="text"></div>\n <div class="field">\n <label>Recall Strategy</label>\n <select id="memStrategy" onchange="updateMemSearchFields()">\n <option value="builtin-only">builtin-only</option>\n <option value="search">search</option>\n </select>\n </div>\n <div id="memSearchSettings" style="display:none">\n <div style="border-top:1px solid var(--border);margin-top:12px;padding-top:12px">\n <span style="font-size:13px;font-weight:600;color:var(--text-primary)">Search Settings</span>\n <p style="font-size:12px;color:var(--text-muted);margin:4px 0 12px">Hybrid BM25 + semantic search over conversation memories. Requires an OpenAI-compatible embedding API.</p>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Model (API key)</label><select id="memSearchModelRef"></select></div>\n <div class="field" style="flex:1"><label>Embedding Model</label><input id="memSearchEmbModel" type="text" value="text-embedding-3-small"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Prefix Query</label><input id="memSearchPrefixQuery" type="text" placeholder=""></div>\n <div class="field" style="flex:1"><label>Prefix Document</label><input id="memSearchPrefixDocument" type="text" placeholder=""></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Embedding Dimensions: <strong id="memSearchDimsValue">1536</strong></label>\n <input id="memSearchDims" type="range" min="512" max="4096" step="1" value="1536"\n oninput="document.getElementById('memSearchDimsValue').textContent=this.value">\n <div style="display:flex;position:relative;margin-top:2px;height:14px;pointer-events:none;user-select:none">\n <span style="position:absolute;left:0;font-size:11px;color:var(--text-muted)">512</span>\n <span style="position:absolute;left:28.6%;font-size:11px;color:var(--text-muted);transform:translateX(-50%)">1536</span>\n <span style="position:absolute;right:0;font-size:11px;color:var(--text-muted)">4096</span>\n </div>\n </div>\n <div class="field" style="flex:1"><label>Max Results</label><input id="memSearchMaxResults" type="number" value="6" min="1" max="100"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Index Debounce (ms)</label><input id="memSearchDebounce" type="number" value="3000" min="500"></div>\n <div class="field" style="flex:1"><label>Embed Interval (ms)</label><input id="memSearchEmbedInterval" type="number" value="300000" min="10000"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Max Snippet Chars</label><input id="memSearchMaxSnippet" type="number" value="700" min="100"></div>\n <div class="field" style="flex:1"><label>Max Injected Chars <span style="color:var(--text-muted);font-weight:400">— total output cap for search results (0 = unlimited)</span></label><input id="memSearchMaxInjected" type="number" value="4000" min="0"></div>\n </div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>RRF K <span style="color:var(--text-muted);font-weight:400">— fusion constant, higher values blend keyword &amp; semantic more evenly (default 60)</span></label><input id="memSearchRrfK" type="number" value="60" min="1"></div>\n </div>\n <div style="margin-top:12px;display:flex;align-items:center;gap:12px">\n <button class="btn" onclick="testEmbedding()" id="memSearchTestBtn">Test Embedding</button>\n <span id="memSearchTestResult" style="font-size:13px"></span>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Cron --\x3e\n <div id="sec-cron" class="section">\n <div class="section-header"><h1>Cron &amp; Heartbeat</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Cron Scheduler</span>\n <label class="toggle"><input type="checkbox" id="cronEnabled"><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;padding:0 16px 12px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Isolated <span style="font-weight:400">— jobs run in their own session (<code>cron:name</code>) instead of sharing user chat context</span></span>\n <label class="toggle"><input type="checkbox" id="cronIsolated"><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;padding:0 16px 12px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Broadcast Events <span style="font-weight:400">— deliver responses to all known chats across all active channels</span></span>\n <label class="toggle"><input type="checkbox" id="cronBroadcast"><span></span></label>\n </div>\n </div>\n\n <h2>Heartbeat</h2>\n <div class="card" id="heartbeatCard">\n <div class="card-header">\n <span class="card-title">Heartbeat</span>\n <label class="toggle"><input type="checkbox" id="hbEnabled"><span></span></label>\n </div>\n <div id="hbFields">\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Channel</label>\n <select id="hbChannel" onchange="onHbChannelChange();updateHbFields()">\n <option value="">-- select --</option>\n </select>\n </div>\n <div class="field" style="flex:1">\n <label>Chat ID</label>\n <select id="hbChatId" onchange="updateHbFields()">\n <option value="">-- select --</option>\n </select>\n </div>\n </div>\n <div id="hbWarning" style="display:none;font-size:12px;color:var(--warning);margin-top:4px"></div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Interval (ms) <span id="hbEveryHuman" style="font-weight:400;color:var(--text-muted)"></span></label>\n <input id="hbEvery" type="number" min="10000" step="1000" oninput="updateHbEveryHuman()">\n </div>\n <div class="field" style="flex:1">\n <label>Ack Max Chars</label>\n <input id="hbAckMaxChars" type="number" min="0">\n </div>\n </div>\n <div class="field" style="margin-top:8px">\n <label>Heartbeat Message</label>\n <textarea id="hbMessage" rows="4" oninput="updateHbFields()" style="width:100%;font-family:var(--mono);font-size:13px;resize:vertical;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:10px;color:var(--text)"></textarea>\n <div style="font-size:11px;color:var(--text-muted);margin-top:4px">The message sent to the agent on each heartbeat tick. The agent reads HEARTBEAT.md and follows its instructions.</div>\n </div>\n <div style="font-size:12px;color:var(--text-muted);margin-top:12px;line-height:1.5">\n <strong>How it works:</strong> Every N ms the system checks <code>HEARTBEAT.md</code> in the data directory.\n If the file is empty (only headers or comments), the check is skipped to save API calls.\n If it has actionable content, the agent is asked to evaluate the tasks and respond:\n <code>HEARTBEAT_OK</code> if nothing needs attention (suppressed, not delivered),\n or an alert message delivered to the configured channel.\n Heartbeat exchanges are never written to conversation memory.<br>\n <strong>Ack Max Chars:</strong> after stripping <code>HEARTBEAT_OK</code>, if the remaining text\n is shorter than this threshold it is treated as a courtesy acknowledgment and suppressed.\n Only responses exceeding this limit are delivered as real alerts.\n </div>\n <div style="margin-top:12px">\n <button class="btn btn-sm" onclick="simulateHeartbeat()">Simulate Heartbeat</button>\n </div>\n </div>\n </div>\n\n <h2>Cron Jobs</h2>\n <div class="card">\n <div class="card-header">\n <span class="card-title">Jobs</span>\n <div style="display:flex;gap:8px">\n <span id="cronStatus" style="font-size:13px;color:var(--text-muted);align-self:center"></span>\n <button class="btn-ghost btn-sm" onclick="loadCronJobs()" title="Refresh">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>\n </button>\n <button class="btn btn-sm" onclick="showAddJob()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Add Job\n </button>\n </div>\n </div>\n\n <div id="addJobForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>Name</label><input id="newJobName" type="text" placeholder="my-job"></div>\n <div class="field"><label>Description</label><input id="newJobDesc" type="text" placeholder="optional"></div>\n <div class="field-row">\n <div class="field" style="flex:1">\n <label>Channel</label>\n <select id="newJobChannel" onchange="onNewJobChannelChange()">\n <option value="">-- select --</option>\n </select>\n </div>\n <div class="field" style="flex:1">\n <label>Chat ID</label>\n <select id="newJobChatId">\n <option value="">-- select --</option>\n </select>\n </div>\n </div>\n <div class="field">\n <label>Message</label>\n <textarea id="newJobMessage" rows="2" placeholder="Message to send to agent"></textarea>\n </div>\n <div class="field">\n <label>Schedule Type</label>\n <select id="newJobSchedKind" onchange="updateJobSchedFields()">\n <option value="every">Interval (every N ms)</option>\n <option value="cron">Cron expression</option>\n <option value="at">One-shot (at date/time)</option>\n </select>\n </div>\n <div id="newJobSchedEvery" class="field">\n <label>Interval (ms)</label>\n <input id="newJobEveryMs" type="number" min="1000" step="1000" value="60000">\n </div>\n <div id="newJobSchedCron" class="field" style="display:none">\n <label>Cron Expression <span style="color:var(--text-muted);font-weight:400">(e.g. 0 */5 * * * *)</span></label>\n <input id="newJobCronExpr" type="text" placeholder="0 */5 * * * *">\n </div>\n <div id="newJobSchedAt" class="field" style="display:none">\n <label>Run At (ISO datetime)</label>\n <input id="newJobAtTime" type="datetime-local">\n </div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Isolated <span style="font-weight:400">— job runs in its own session (<code>cron:name</code>) instead of sharing the user's chat context</span></span>\n <label class="toggle"><input type="checkbox" id="newJobIsolated" checked><span></span></label>\n </div>\n <div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">\n <span style="font-size:13px;font-weight:500;color:var(--text-muted)">Suppress HEARTBEAT_OK <span style="font-weight:400">— if the agent replies with HEARTBEAT_OK the message is not delivered to the chat</span></span>\n <label class="toggle"><input type="checkbox" id="newJobSuppress"><span></span></label>\n </div>\n <div style="display:flex;gap:8px">\n <button class="btn btn-sm" onclick="addJob()">Create Job</button>\n <button class="btn-ghost btn-sm" onclick="hideAddJob()">Cancel</button>\n </div>\n </div>\n\n <div class="field" style="margin-bottom:10px;max-width:360px">\n <label>Filter</label>\n <input id="cronJobFilter" type="text" placeholder="type to filter..." oninput="applyCronJobFilter()" style="font-size:12px">\n </div>\n <div id="cronJobsWrap" style="position:relative;overflow-x:auto;overflow-y:auto;max-height:500px">\n <table class="tbl" style="min-width:max-content">\n <thead><tr><th>Name</th><th>Schedule</th><th>Session</th><th>Delivery</th><th>Status</th><th>Next Run</th><th></th></tr></thead>\n <tbody id="cronJobsBody"></tbody>\n </table>\n <div id="cronScrollHint" style="display:none;position:sticky;bottom:0;right:0;text-align:right;pointer-events:none;padding:4px 8px">\n <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.6"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>\n </div>\n </div>\n </div>\n </div>\n\n \x3c!-- Logs --\x3e\n <div id="sec-logs" class="section">\n <div class="section-header"><h1>Logs</h1></div>\n <div class="card">\n <div style="display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-bottom:14px">\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Log Level</label>\n <select id="logLevelSelect" onchange="changeLogLevel()" style="width:120px">\n <option value="debug">debug</option>\n <option value="info">info</option>\n <option value="warn">warn</option>\n <option value="error">error</option>\n </select>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Lines</label>\n <select id="logLinesSelect" onchange="changeLogLines()" style="width:100px">\n <option value="200">200</option>\n <option value="500">500</option>\n <option value="1000">1000</option>\n </select>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Auto Refresh</label>\n <label class="toggle"><input type="checkbox" id="logAutoRefresh" onchange="toggleLogAutoRefresh()"><span></span></label>\n </div>\n <div class="field" style="margin-bottom:0;flex:0 0 auto">\n <label>Verbose Logs</label>\n <label class="toggle"><input type="checkbox" id="logVerbose" onchange="toggleVerboseDebugLogs()"><span></span></label>\n </div>\n <div class="field" style="margin-bottom:0;flex:1;min-width:140px">\n <label>Filter</label>\n <input id="logFilter" type="text" placeholder="type to filter..." oninput="applyLogFilter()" style="font-size:12px">\n </div>\n <div style="margin-left:auto;display:flex;gap:8px;align-self:flex-end">\n <button class="btn btn-sm" onclick="loadLogLines()">Refresh</button>\n <button class="btn-ghost btn-sm" onclick="downloadCurrentLog()">Download</button>\n </div>\n </div>\n <pre id="logViewer" style="background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:12px;font-size:12px;line-height:1.6;max-height:500px;overflow-y:auto;white-space:pre-wrap;word-break:break-all;color:var(--text)"></pre>\n <div style="margin-top:6px;font-size:12px;color:var(--text-muted)" id="logTotal"></div>\n </div>\n\n <h2>Archived Logs</h2>\n <div class="card">\n <table class="tbl">\n <thead><tr><th>File</th><th>Size</th><th>Modified</th><th></th></tr></thead>\n <tbody id="logFilesBody"></tbody>\n </table>\n </div>\n\n <h2>Memory Logs</h2>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:12px">Conversation logs organized by session. Select a session folder to browse its log files.</p>\n <div class="card">\n <div class="field">\n <label>Session</label>\n <select id="memorySessionSelect" onchange="loadMemoryFiles()">\n <option value="">Select a session...</option>\n </select>\n </div>\n <div id="memoryFileList" style="margin-top:12px;max-height:400px;overflow-y:auto"></div>\n </div>\n </div>\n\n \x3c!-- Nodes --\x3e\n <div id="sec-nodes" class="section">\n <div class="section-header"><h1>Nodes</h1></div>\n\n <div class="card">\n <div class="card-header"><span class="card-title">Connected Nodes</span><button class="btn btn-sm" onclick="loadNodes()" style="font-size:12px;padding:3px 8px">Refresh</button></div>\n <table class="tbl">\n <thead><tr><th>Name</th><th>Hostname</th><th>Platform</th><th>Arch</th><th>Connected Since</th></tr></thead>\n <tbody id="connectedNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="pendingNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Pending Approvals</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Signature</th><th>Requested At</th><th></th></tr></thead>\n <tbody id="pendingNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="approvedNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Approved Nodes</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Signature</th><th>Approved At</th><th>Last Seen</th><th></th></tr></thead>\n <tbody id="approvedNodesBody"></tbody>\n </table>\n </div>\n\n <div class="card" id="revokedNodesCard" style="display:none">\n <div class="card-header"><span class="card-title">Revoked Nodes</span></div>\n <table class="tbl">\n <thead><tr><th>Display Name</th><th>Hostname</th><th>Revoked At</th><th></th></tr></thead>\n <tbody id="revokedNodesBody"></tbody>\n </table>\n </div>\n </div>\n\n \x3c!-- Tokens --\x3e\n <div id="sec-tokens" class="section">\n <div class="section-header"><h1>Tokens</h1></div>\n <div class="card">\n <div class="card-header">\n <span class="card-title">API Tokens</span>\n <button class="btn btn-sm" onclick="showCreateToken()">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>\n Create\n </button>\n </div>\n <div id="createTokenForm" style="display:none;margin-bottom:16px;padding:14px;border:1px solid var(--border);border-radius:var(--radius)">\n <div class="field"><label>User ID</label><input id="newTokenUser" type="text" placeholder="Unique user identifier (min 5 chars)"></div>\n <div class="field-row">\n <div class="field" style="flex:1"><label>Channel</label><select id="newTokenChannel"><option value="*">* (all)</option><option value="nostromo">nostromo</option><option value="responses">responses</option><option value="webchat">webchat</option></select></div>\n <div class="field" style="flex:1"><label>Label</label><input id="newTokenLabel" type="text" placeholder="optional label"></div>\n </div>\n <div style="display:flex;gap:8px;margin-top:4px">\n <button class="btn btn-sm" onclick="createToken()">Create Token</button>\n <button class="btn-ghost btn-sm" onclick="hideCreateToken()">Cancel</button>\n </div>\n </div>\n <div id="tokenTableWrap">\n <table class="tbl">\n <thead><tr><th>ID</th><th>Token</th><th>User</th><th>Channel</th><th>Label</th><th>Status</th><th></th></tr></thead>\n <tbody id="tokenBody"></tbody>\n </table>\n </div>\n </div>\n </div>\n\n \x3c!-- Prompts --\x3e\n <div id="sec-prompts" class="section">\n <div class="section-header">\n <h1>Prompts</h1>\n <button class="btn" onclick="simulatePrompt()">Simulate Building</button>\n </div>\n\n <div class="card">\n <div class="card-title" style="margin-bottom:4px">File Editor</div>\n <p style="font-size:13px;color:var(--text-muted);margin-bottom:12px">Edit system prompt templates and workspace files. Templates use <code>{{PLACEHOLDER}}</code> syntax.</p>\n <div class="file-select-row">\n <select id="promptFileSelect" onchange="selectPromptFile()">\n <option value="">Select a file...</option>\n </select>\n <span id="promptFileBadge" class="file-badge" style="display:none"></span>\n <button class="btn btn-sm" id="promptSaveBtn" onclick="saveCurrentFile()" style="display:none">Save</button>\n <button class="btn-ghost btn-sm" onclick="showPlaceholderRef()" title="Placeholder Reference">{{...}}</button>\n </div>\n <div id="promptEditorWrap" class="monaco-wrap" style="display:none"></div>\n </div>\n\n </div>\n\n \x3c!-- Settings --\x3e\n <div id="sec-settings" class="section">\n <div class="section-header"><h1>Settings</h1><button class="btn save-btn" onclick="saveConfig()" disabled>Save Changes</button></div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Appearance</div>\n <div style="display:flex;align-items:center;gap:12px">\n <span style="font-size:14px">Theme</span>\n <label class="toggle">\n <input type="checkbox" id="themeToggle" onchange="toggleTheme()">\n <span></span>\n </label>\n <span style="font-size:13px;color:var(--text-muted)" id="themeLabel">Light</span>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Server</div>\n <div class="field-row">\n <div class="field"><label>Bind Host</label><input type="text" value="" id="settingsHost" placeholder="127.0.0.1"></div>\n <div class="field"><label>Port</label><input type="number" value="" id="settingsUiPort"></div>\n <div class="field"><label>Timezone</label><input type="text" value="" id="settingsTimezone" placeholder="Europe/Rome"></div>\n </div>\n <div style="margin-top:8px">\n <p style="font-size:12px;color:var(--text-muted)">CLI <code>--host</code> / <code>--port</code> override config values at runtime. All network changes require a restart.</p>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Config Watcher</div>\n <div class="field-row" style="align-items:center">\n <div style="display:flex;align-items:center;gap:12px;flex:1">\n <label class="toggle">\n <input type="checkbox" id="settingsAutoRestart">\n <span></span>\n </label>\n <span style="font-size:14px">Auto-restart on config.yaml changes</span>\n </div>\n <div class="field" style="flex:0 0 160px;margin-bottom:0">\n <label>Check interval (sec)</label>\n <input type="number" id="settingsConfigCheckInterval" min="1" max="300">\n </div>\n </div>\n <div style="margin-top:8px">\n <p style="font-size:12px;color:var(--text-muted)">When auto-restart is disabled, config file changes are detected but the server does not restart. The check interval controls how often the config file is polled for changes.</p>\n </div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Access Key</div>\n <div id="currentKeyRow" class="current-key-row" style="display:none">\n <code id="currentKeyValue" class="current-key-value"></code>\n <button class="icon-btn" onclick="toggleKeyVisibility()" title="Show / Hide" id="keyEyeBtn">\n <svg id="keyEyeOff" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/><path d="M1 1l22 22"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg>\n <svg id="keyEyeOn" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>\n </button>\n <button class="icon-btn" onclick="copyKey()" title="Copy to clipboard">\n <svg id="keyCopyIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>\n <svg id="keyCheckIcon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none"><polyline points="20 6 9 17 4 12"/></svg>\n </button>\n </div>\n <p style="font-size:14px;color:var(--text-muted);margin-bottom:12px">Generate a new access key. The current key will be invalidated immediately.</p>\n <button class="btn" onclick="confirmRegenKey()">Regenerate Key</button>\n <div id="newKeyDisplay"></div>\n </div>\n <div class="card">\n <div class="card-title" style="margin-bottom:12px">Session</div>\n <button class="btn-ghost" onclick="doLogout()">Log out</button>\n </div>\n </div>\n\n </main>\n</div>\n\n\x3c!-- Floating restart indicator --\x3e\n<div id="floatingRestart" class="floating-restart" style="display:none" onclick="showRestartModal()" title="Config changed — click to restart">\n <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></svg>\n</div>\n\n\x3c!-- Toast --\x3e\n<div id="toast" class="toast"></div>\n`}