aidaemon 0.11.11

A personal AI agent that runs as a background daemon, accessible via Telegram, Slack, or Discord, with tool use, MCP integration, and persistent memory
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>aidaemon dashboard</title>
<style>
:root {
  --bg: #fff; --bg2: #f5f5f7; --fg: #1d1d1f; --fg2: #6e6e73;
  --border: #d2d2d7; --accent: #0071e3; --err: #ff3b30;
  --card-bg: #fff; --card-shadow: 0 1px 3px rgba(0,0,0,.08);
  --radius: 8px; --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1c1c1e; --bg2: #2c2c2e; --fg: #f5f5f7; --fg2: #98989d;
    --border: #38383a; --card-bg: #2c2c2e; --card-shadow: 0 1px 3px rgba(0,0,0,.3);
  }
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font); background: var(--bg); color: var(--fg); line-height: 1.5; padding: 1rem; max-width: 960px; margin: 0 auto; }
h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; }
h2 { font-size: .9rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--fg2); margin-bottom: .5rem; }
.cards { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 600px) { .cards { grid-template-columns: 1fr 1fr; } }
.card { background: var(--card-bg); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem; box-shadow: var(--card-shadow); }
.card.wide { grid-column: 1 / -1; }
table { width: 100%; border-collapse: collapse; font-size: .85rem; }
th { text-align: left; font-weight: 500; color: var(--fg2); border-bottom: 1px solid var(--border); padding: .35rem .5rem; }
td { padding: .35rem .5rem; border-bottom: 1px solid var(--border); }
tr:last-child td { border-bottom: none; }
.mono { font-family: "SF Mono", "Fira Code", "Consolas", monospace; font-size: .8rem; }
.status-grid { display: grid; grid-template-columns: auto 1fr; gap: .25rem .75rem; font-size: .9rem; }
.status-grid dt { color: var(--fg2); }
.badge { display: inline-block; font-size: .75rem; padding: .1rem .4rem; border-radius: 4px; }
.badge-ok { background: #34c75920; color: #30d158; }
.badge-paused { background: #ff9f0a20; color: #ff9f0a; }
.badge-warn { background: #ffcc0022; color: #b45309; }
.badge-err { background: #ff3b3022; color: #d70015; }
.empty { color: var(--fg2); font-style: italic; font-size: .85rem; }
#auth-overlay { position: fixed; inset: 0; background: var(--bg); display: flex; align-items: center; justify-content: center; z-index: 100; }
#auth-box { text-align: center; }
#auth-box input { font-family: var(--font); font-size: .9rem; padding: .5rem .75rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg2); color: var(--fg); width: 300px; margin: .5rem 0; }
#auth-box button { font-family: var(--font); font-size: .9rem; padding: .5rem 1.25rem; border: none; border-radius: var(--radius); background: var(--accent); color: #fff; cursor: pointer; }
#auth-box .err { color: var(--err); font-size: .85rem; margin-top: .25rem; }
.refresh-info { font-size: .75rem; color: var(--fg2); margin-top: 1rem; text-align: center; }
.truncate { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
</style>
</head>
<body>

<div id="auth-overlay">
  <div id="auth-box">
    <h1><a href="/" style="color:inherit;text-decoration:none">aidaemon</a></h1>
    <p style="color:var(--fg2);margin-bottom:.5rem">Enter dashboard token</p>
    <div><input id="token-input" type="password" placeholder="Bearer token" autocomplete="off"></div>
    <div><button onclick="submitToken()">Connect</button></div>
    <div class="err" id="auth-err"></div>
  </div>
</div>

<div id="app" style="display:none">
  <h1><a href="/" style="color:inherit;text-decoration:none">aidaemon</a> <span style="font-weight:400;color:var(--fg2);font-size:.85rem" id="version"></span></h1>
  <div class="cards">
    <div class="card" id="status-card">
      <h2>Status</h2>
      <dl class="status-grid" id="status-dl"></dl>
    </div>
    <div class="card" id="tasks-card">
      <h2>Scheduled Tasks</h2>
      <div id="tasks-body"></div>
    </div>
    <div class="card wide" id="maintenance-card">
      <h2>Maintenance Jobs</h2>
      <div id="maintenance-body"></div>
    </div>
    <div class="card wide" id="queues-card">
      <h2>Queue Health</h2>
      <div id="queues-body"></div>
    </div>
    <div class="card wide" id="llm-latency-card">
      <h2>LLM Latency (last 24h)</h2>
      <div id="llm-latency-body"></div>
    </div>
    <div class="card wide" id="policy-card">
      <h2>Policy Metrics</h2>
      <div id="policy-body"></div>
    </div>
    <div class="card wide" id="writes-card">
      <h2>Write Consistency</h2>
      <div id="writes-body"></div>
    </div>
    <div class="card wide" id="usage-card">
      <h2>Token Usage (7 days)</h2>
      <div id="usage-body"></div>
    </div>
    <div class="card wide" id="sessions-card">
      <h2>Recent Sessions</h2>
      <div id="sessions-body"></div>
    </div>
  </div>
  <div class="refresh-info">Auto-refresh every 30s &middot; <a href="#" onclick="fetchAll();return false" style="color:var(--accent)">refresh now</a></div>
</div>

<script>
let TOKEN = sessionStorage.getItem('aidaemon_token') || '';

function submitToken() {
  TOKEN = document.getElementById('token-input').value.trim();
  if (!TOKEN) return;
  sessionStorage.setItem('aidaemon_token', TOKEN);
  tryConnect();
}

document.getElementById('token-input').addEventListener('keydown', e => {
  if (e.key === 'Enter') submitToken();
});

async function api(path) {
  const r = await fetch(path, { headers: { 'Authorization': 'Bearer ' + TOKEN } });
  if (r.status === 401) { showAuth('Invalid or expired token'); throw new Error('401'); }
  return r.json();
}

function showAuth(msg) {
  document.getElementById('auth-overlay').style.display = 'flex';
  document.getElementById('app').style.display = 'none';
  if (msg) document.getElementById('auth-err').textContent = msg;
}

function showApp() {
  document.getElementById('auth-overlay').style.display = 'none';
  document.getElementById('app').style.display = '';
}

async function tryConnect() {
  try {
    await api('/api/status');
    showApp();
    fetchAll();
  } catch { showAuth('Could not connect'); }
}

function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }

function fmtDuration(secs) {
  const d = Math.floor(secs / 86400), h = Math.floor((secs % 86400) / 3600), m = Math.floor((secs % 3600) / 60);
  if (d > 0) return d + 'd ' + h + 'h';
  if (h > 0) return h + 'h ' + m + 'm';
  return m + 'm';
}

function fmtNum(n) { return n != null ? n.toLocaleString() : '-'; }
function fmtTs(ts) { return ts ? ts.slice(0, 16).replace('T', ' ') : '-'; }

async function fetchStatus() {
  try {
    const d = await api('/api/status');
    document.getElementById('version').textContent = 'v' + esc(d.version);
    let html = '';
    const fallbackModels = Array.isArray(d.models.fallback) && d.models.fallback.length
      ? d.models.fallback.join(', ')
      : '-';
    const items = [
      ['Provider', d.provider],
      ['Default', d.models.default || d.models.primary],
      ['Fallbacks', fallbackModels],
      ['Primary', d.models.primary],
      ['Fast', d.models.fast],
      ['Smart', d.models.smart],
      ['Uptime', fmtDuration(d.uptime_secs)],
    ];
    if (d.daily_token_budget) items.push(['Daily Budget', fmtNum(d.daily_token_budget) + ' tokens']);
    for (const [k, v] of items) html += '<dt>' + esc(k) + '</dt><dd>' + esc(String(v)) + '</dd>';
    document.getElementById('status-dl').innerHTML = html;
  } catch {}
}

async function fetchUsage() {
  try {
    const rows = await api('/api/usage?days=7');
    if (!rows.length) { document.getElementById('usage-body').innerHTML = '<p class="empty">No usage data</p>'; return; }
    let html = '<table><tr><th>Date</th><th>Model</th><th>Input</th><th>Output</th><th>Requests</th></tr>';
    for (const r of rows) html += '<tr><td class="mono">' + esc(r.day) + '</td><td class="truncate">' + esc(r.model) + '</td><td>' + fmtNum(r.input_tokens) + '</td><td>' + fmtNum(r.output_tokens) + '</td><td>' + fmtNum(r.request_count) + '</td></tr>';
    html += '</table>';
    document.getElementById('usage-body').innerHTML = html;
  } catch {}
}

async function fetchSessions() {
  try {
    const rows = await api('/api/sessions?limit=20');
    if (!rows.length) { document.getElementById('sessions-body').innerHTML = '<p class="empty">No sessions</p>'; return; }
    let html = '<table><tr><th>Session</th><th>Messages</th><th>First</th><th>Last Activity</th></tr>';
    for (const r of rows) html += '<tr><td class="mono truncate">' + esc(r.session_id) + '</td><td>' + fmtNum(r.message_count) + '</td><td class="mono">' + esc((r.first_message||'').slice(0,16)) + '</td><td class="mono">' + esc((r.last_activity||'').slice(0,16)) + '</td></tr>';
    html += '</table>';
    document.getElementById('sessions-body').innerHTML = html;
  } catch {}
}

async function fetchTasks() {
  try {
    const rows = await api('/api/tasks');
    if (!rows.length) { document.getElementById('tasks-body').innerHTML = '<p class="empty">No schedules</p>'; return; }
    let html = '<table><tr><th>Goal</th><th>Schedule</th><th>Goal Status</th><th>Schedule</th><th>Next Run</th></tr>';
    for (const r of rows) {
      const scheduleText = r.original_schedule || r.cron_expr || '';
      const schedBadge = r.is_paused ? '<span class="badge badge-paused">paused</span>' : '<span class="badge badge-ok">active</span>';
      html += '<tr>'
        + '<td class="truncate">' + esc(r.goal_description || r.goal_id || '-') + '</td>'
        + '<td class="mono truncate">' + esc(scheduleText) + '</td>'
        + '<td class="mono">' + esc(r.goal_status || '-') + '</td>'
        + '<td>' + schedBadge + '</td>'
        + '<td class="mono">' + esc((r.next_run_at||'-').slice(0,16)) + '</td>'
        + '</tr>';
    }
    html += '</table>';
    document.getElementById('tasks-body').innerHTML = html;
  } catch {}
}

async function fetchMaintenanceJobs() {
  try {
    const payload = await api('/api/heartbeat/jobs');
    const rows = payload.maintenance_jobs || [];
    if (!rows.length) {
      document.getElementById('maintenance-body').innerHTML = '<p class="empty">No maintenance telemetry available</p>';
      return;
    }
    let html = '<table><tr><th>Job</th><th>Last Run</th><th>Last Success</th><th>Failures</th><th>Last Error</th></tr>';
    for (const r of rows) {
      html += '<tr>'
        + '<td class="mono">' + esc(r.name) + '</td>'
        + '<td class="mono">' + esc(fmtTs(r.last_run_at)) + '</td>'
        + '<td class="mono">' + esc(fmtTs(r.last_success_at)) + '</td>'
        + '<td>' + fmtNum(r.consecutive_failures) + '</td>'
        + '<td class="truncate">' + esc(r.last_error || '-') + '</td>'
        + '</tr>';
    }
    html += '</table>';
    document.getElementById('maintenance-body').innerHTML = html;
  } catch {}
}

async function fetchPolicyMetrics() {
  try {
    const d = await api('/api/policy/metrics');
    const rows = [
      ['Tool exposure samples', fmtNum(d.tool_exposure_samples)],
      ['Avg tools before filter', (d.avg_tools_before_filter || 0).toFixed(2)],
      ['Avg tools after filter', (d.avg_tools_after_filter || 0).toFixed(2)],
      ['Ambiguity detected', fmtNum(d.ambiguity_detected_total)],
      ['Uncertainty clarifications', fmtNum(d.uncertainty_clarify_total)],
      ['Current uncertainty threshold', (d.uncertainty_threshold || 0).toFixed(2)],
      ['Context refreshes', fmtNum(d.context_refresh_total)],
      ['Escalations', fmtNum(d.escalation_total)],
      ['Fallback expansions', fmtNum(d.fallback_expansion_total)],
      ['Route reason: clarification_required', fmtNum(d.orchestration_route_clarification_required_total)],
      ['Route reason: tools_required', fmtNum(d.orchestration_route_tools_required_total)],
      ['Route reason: short_correction_direct_reply', fmtNum(d.orchestration_route_short_correction_direct_reply_total)],
      ['Route reason: acknowledgment_direct_reply', fmtNum(d.orchestration_route_acknowledgment_direct_reply_total)],
      ['Route reason: default_continue', fmtNum(d.orchestration_route_default_continue_total)],
      ['Tool schema contract rejections', fmtNum(d.tool_schema_contract_rejections_total)],
      ['Route drift alerts', fmtNum(d.route_drift_alert_total)],
      ['Route fail-safe activations', fmtNum(d.route_drift_failsafe_activation_total)],
      ['Route fail-safe active turns', fmtNum(d.route_failsafe_active_turn_total)],
    ];
    let html = '<table><tr><th>Metric</th><th>Value</th></tr>';
    for (const [k, v] of rows) html += '<tr><td>' + esc(k) + '</td><td class="mono">' + esc(String(v)) + '</td></tr>';
    html += '</table>';
    document.getElementById('policy-body').innerHTML = html;
  } catch {
    document.getElementById('policy-body').innerHTML = '<p class="empty">Policy metrics unavailable</p>';
  }
}

async function fetchLlmLatency() {
  try {
    const d = await api('/api/llm/latency?hours=24');
    if (d.error || !d.total_calls) {
      document.getElementById('llm-latency-body').innerHTML = '<p class="empty">No LLM calls recorded yet</p>';
      return;
    }
    const rows = [
      ['Total calls', fmtNum(d.total_calls)],
      ['Avg latency', fmtNum(d.avg_latency_ms) + ' ms'],
      ['p50 latency', fmtNum(d.p50_latency_ms) + ' ms'],
      ['p95 latency', fmtNum(d.p95_latency_ms) + ' ms'],
      ['Max latency', fmtNum(d.max_latency_ms) + ' ms'],
      ['Calls with fallback', fmtNum(d.fell_back_count)],
      ['Avg input tokens', fmtNum(d.avg_input_tokens)],
      ['Avg output tokens', fmtNum(d.avg_output_tokens)],
    ];
    let html = '<table><tr><th>Metric</th><th>Value</th></tr>';
    for (const [k, v] of rows) html += '<tr><td>' + esc(k) + '</td><td class="mono">' + esc(String(v)) + '</td></tr>';
    html += '</table>';
    document.getElementById('llm-latency-body').innerHTML = html;
  } catch {
    document.getElementById('llm-latency-body').innerHTML = '<p class="empty">LLM latency unavailable</p>';
  }
}

async function fetchQueues() {
  try {
    const payload = await api('/api/queues');
    const rows = payload.queues || [];
    if (!rows.length) {
      document.getElementById('queues-body').innerHTML = '<p class="empty">Queue telemetry unavailable</p>';
      return;
    }
    let html = '<table><tr><th>Queue</th><th>Depth</th><th>Saturation</th><th>High-Water</th><th>Dropped</th><th>Failed</th><th>Overload Events</th><th>Status</th></tr>';
    for (const r of rows) {
      const saturation = ((r.saturation || 0) * 100).toFixed(0) + '%';
      const depth = String(r.current_depth) + ' / ' + String(r.capacity);
      let status = '<span class="badge badge-ok">normal</span>';
      if (r.overload_active) status = '<span class="badge badge-err">overload</span>';
      else if (r.current_depth >= r.warn_depth) status = '<span class="badge badge-warn">warning</span>';
      html += '<tr>'
        + '<td class="mono">' + esc(r.name) + '</td>'
        + '<td class="mono">' + esc(depth) + '</td>'
        + '<td>' + esc(saturation) + '</td>'
        + '<td>' + fmtNum(r.high_watermark) + '</td>'
        + '<td>' + fmtNum(r.dropped_total) + '</td>'
        + '<td>' + fmtNum(r.failed_total) + '</td>'
        + '<td>' + fmtNum(r.overload_events_total) + '</td>'
        + '<td>' + status + '</td>'
        + '</tr>';
    }
    html += '</table>';
    document.getElementById('queues-body').innerHTML = html;
  } catch {
    document.getElementById('queues-body').innerHTML = '<p class="empty">Queue telemetry unavailable</p>';
  }
}

async function fetchWriteConsistency() {
  try {
    const d = await api('/api/writes/consistency');
    if (d.error) {
      document.getElementById('writes-body').innerHTML = '<p class="empty">' + esc(d.error) + '</p>';
      return;
    }
    const gate = d.gate || {};
    const gateBadge = gate.passed
      ? '<span class="badge badge-ok">PASS</span>'
      : '<span class="badge badge-warn">WARN</span>';
    const thresholds = gate.thresholds || {};
    let html = '<table><tr><th>Metric</th><th>Value</th></tr>';
    html += '<tr><td>Generated At</td><td class="mono">' + esc(fmtTs(d.generated_at)) + '</td></tr>';
    html += '<tr><td>Consistency Gate</td><td>' + gateBadge + '</td></tr>';
    html += '<tr><td>Event Rows</td><td class="mono">' + fmtNum(d.conversation_event_rows) + '</td></tr>';
    html += '<tr><td>Missing Message IDs</td><td class="mono">' + fmtNum(d.missing_message_id_events) + '</td></tr>';
    html += '<tr><td>Global Delta</td><td class="mono">' + fmtNum(d.global_delta) + '</td></tr>';
    html += '<tr><td>Sessions with Drift</td><td class="mono">' + fmtNum(d.session_mismatch_count) + '</td></tr>';
    html += '<tr><td>Stale Task Starts</td><td class="mono">' + fmtNum(d.stale_task_starts) + '</td></tr>';
    if (thresholds.max_abs_global_delta != null) {
      html += '<tr><td>Gate Thresholds</td><td class="mono">delta&lt;=' + fmtNum(thresholds.max_abs_global_delta)
        + ', mismatches&lt;=' + fmtNum(thresholds.max_session_mismatch_count)
        + ', stale&lt;=' + fmtNum(thresholds.max_stale_task_starts)
        + ', missing_ids&lt;=' + fmtNum(thresholds.max_missing_message_id_events) + '</td></tr>';
    }
    html += '</table>';
    if (gate.reasons && gate.reasons.length) {
      html += '<div style="margin-top:.5rem"></div>';
      html += '<table><tr><th>Guardrail Warnings</th></tr>';
      for (const reason of gate.reasons) {
        html += '<tr><td>' + esc(reason) + '</td></tr>';
      }
      html += '</table>';
    }

    const drifts = d.top_session_drifts || [];
    if (drifts.length) {
      html += '<div style="margin-top:.75rem"></div>';
      html += '<table><tr><th>Session</th><th>Messages</th><th>Events</th><th>Delta</th></tr>';
      for (const r of drifts) {
        html += '<tr>'
          + '<td class="mono truncate">' + esc(r.session_id) + '</td>'
          + '<td>' + fmtNum(r.message_rows) + '</td>'
          + '<td>' + fmtNum(r.event_rows) + '</td>'
          + '<td class="mono">' + fmtNum(r.delta) + '</td>'
          + '</tr>';
      }
      html += '</table>';
    }
    document.getElementById('writes-body').innerHTML = html;
  } catch {
    document.getElementById('writes-body').innerHTML = '<p class="empty">Write consistency report unavailable</p>';
  }
}

function fetchAll() { fetchStatus(); fetchUsage(); fetchSessions(); fetchTasks(); fetchMaintenanceJobs(); fetchQueues(); fetchLlmLatency(); fetchPolicyMetrics(); fetchWriteConsistency(); }

// Startup
if (TOKEN) { tryConnect(); } else { showAuth(); }
setInterval(fetchAll, 30000);
</script>
</body>
</html>