roboticus-api 0.11.4

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
App._renderOverviewShell = function() {
      return ''
        + '<div class="card overview-status">'
        + '  <div class="overview-meta" id="ov-meta"></div>'
        + '</div>'
        + '<div class="card overview-attention" id="ov-attention" style="display:none"></div>'
        + '<div class="card overview-onboarding" id="ov-onboarding" style="display:none"></div>'
        + '<div class="overview-grid" id="overview-grid">'
        + '  <div id="ov-card-cost"></div>'
        + '  <div id="ov-card-tokens"></div>'
        + '  <div id="ov-card-cache"></div>'
        + '  <div id="ov-card-wallet"></div>'
        + '  <div id="ov-card-latency"></div>'
        + '  <div id="ov-card-sessions"></div>'
        + '  <div id="ov-card-system"></div>'
        + '  <div id="ov-card-errors"></div>'
        + '  <div id="ov-card-fleet" class="overview-fleet-slot" style="display:none"></div>'
        + '</div>'
        + '<div class="card" id="ov-integrations" style="margin-top:1rem"></div>'
        + '<div class="card" id="ov-throttle" style="margin-top:1rem"></div>';
};
App._applyOverviewCards = function(cards) {
      var self = this;
      var slotMap = {
        cost: 'ov-card-cost',
        tokens: 'ov-card-tokens',
        cache: 'ov-card-cache',
        wallet: 'ov-card-wallet',
        latency: 'ov-card-latency',
        sessions: 'ov-card-sessions',
        system: 'ov-card-system',
        errors: 'ov-card-errors',
        fleet: 'ov-card-fleet'
      };
      var meta = cards && cards.__meta ? cards.__meta : {};
      var attention = cards && cards.__attention ? cards.__attention : [];
      var failures = cards && cards.__failures ? cards.__failures : [];
      var ovMeta = document.getElementById('ov-meta');
      if (ovMeta) {
        var staleBadge = failures.length > 0
          ? '<span class="badge warning">Partial data (' + failures.length + ' source' + (failures.length > 1 ? 's' : '') + ' unavailable)</span>'
          : '<span class="badge success">All sources healthy</span>';
        var updated = meta.updated_at ? esc(meta.updated_at) : 'unknown';
        setHtml(ovMeta, staleBadge + '<span class="badge muted">Last updated: ' + updated + '</span>');
      }
      var ovAttention = document.getElementById('ov-attention');
      if (ovAttention) {
        if (attention.length > 0) {
          var items = attention.map(function(item) {
            return '<li>' + esc(item) + '</li>';
          }).join('');
          setHtml(ovAttention, '<strong>Attention needed</strong><ul style="margin:0.5rem 0 0 1rem">' + items + '</ul>');
          ovAttention.style.display = '';
        } else {
          ovAttention.style.display = 'none';
        }
      }
      var ovOnboarding = document.getElementById('ov-onboarding');
      if (ovOnboarding && hintsEnabled() && !window.localStorage.getItem('ic_dash_onboarding_dismissed')) {
        setHtml(
          ovOnboarding,
          '<div style="display:flex;justify-content:space-between;gap:1rem;align-items:flex-start">'
            + '<div><strong>Quick Start</strong><div style="margin-top:0.35rem;color:var(--muted);font-size:0.85rem">1) Open <em>Sessions</em> and start a conversation. 2) Use <em>Context</em> to inspect token and reasoning traces. 3) Use <em>Prompt Performance</em> to tune latency/cost.</div></div>'
            + '<button class="btn secondary" id="dismiss-onboarding" title="Dismiss hint" aria-label="Dismiss hint">x</button>'
            + '</div>'
        );
        ovOnboarding.style.display = '';
      } else if (ovOnboarding) {
        ovOnboarding.style.display = 'none';
      }
      Object.keys(slotMap).forEach(function(key) {
        var el = document.getElementById(slotMap[key]);
        if (!el) return;
        var html = cards && cards[key] ? cards[key] : '';
        if (html) {
          setHtml(el, html);
          el.style.display = '';
        } else {
          setHtml(el, '');
          if (key === 'fleet') el.style.display = 'none';
        }
      });
      // Render integrations panel
      var intEl = document.getElementById('ov-integrations');
      if (intEl) {
        var chStatuses = cards && cards.__channelStatuses ? cards.__channelStatuses : [];
        var intHtml = '<div class="card-title">Integrations</div>';
        if (Array.isArray(chStatuses) && chStatuses.length > 0) {
          intHtml += '<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:0.75rem">';
          chStatuses.forEach(function(ch) {
            var dot = ch.connected ? 'background:#22c55e' : 'background:#ef4444';
            var statusText = ch.connected ? 'Connected' : (ch.last_error || 'Disconnected');
            var stats = '';
            if (ch.messages_received != null) stats += '<div style="font-size:0.65rem;color:var(--muted)">In: ' + (ch.messages_received || 0) + ' / Out: ' + (ch.messages_sent || 0) + '</div>';
            intHtml += '<div style="padding:0.65rem;border:1px solid var(--border);border-radius:6px">'
              + '<div style="display:flex;align-items:center;gap:0.4rem;margin-bottom:0.3rem">'
              + '<span style="width:8px;height:8px;border-radius:50%;display:inline-block;' + dot + '"></span>'
              + '<span style="font-weight:600;font-size:0.8rem">' + esc(ch.name || 'unknown') + '</span>'
              + '</div>'
              + '<div style="font-size:0.7rem;color:' + (ch.connected ? 'var(--text)' : 'var(--warning, #f59e0b)') + '">' + esc(statusText.substring(0, 80)) + '</div>'
              + stats
              + '<button class="btn secondary channel-test-btn" data-test-channel="' + esc(ch.name || '') + '" style="font-size:0.65rem;padding:0.2rem 0.5rem;margin-top:0.35rem">Test</button>'
              + '</div>';
          });
          intHtml += '</div>';
        } else {
          intHtml += '<div style="font-size:0.75rem;color:var(--muted)">No integrations configured. Add channels in Settings.</div>';
        }
        setHtml(intEl, intHtml);
      }
      // Render throttle / rate-limit panel
      var thrEl = document.getElementById('ov-throttle');
      if (thrEl) {
        var thr = cards && cards.__throttle ? cards.__throttle : {};
        var thrHtml = '<div class="card-title">Rate Limiting</div>';
        var gCount = Number(thr.global_count) || 0;
        var gCap = Number(thr.global_capacity) || 1;
        var pct = Math.min(100, Math.round((gCount / gCap) * 100));
        var barColor = pct >= 90 ? '#ef4444' : (pct >= 70 ? '#f59e0b' : '#22c55e');
        thrHtml += '<div style="margin-bottom:0.75rem">'
          + '<div style="font-size:0.75rem;color:var(--muted);margin-bottom:0.25rem">Window utilization (' + esc(String(gCount)) + ' / ' + esc(String(gCap)) + ')</div>'
          + '<div style="background:var(--border);border-radius:4px;height:8px;overflow:hidden">'
          + '<div style="width:' + pct + '%;height:100%;background:' + barColor + ';border-radius:4px;transition:width 0.3s"></div>'
          + '</div>'
          + '</div>';
        var activeIps = Number(thr.active_ips) || 0;
        var activeActors = Number(thr.active_actors) || 0;
        var throttledGlobal = Number(thr.throttled_global) || 0;
        var windowSecs = Number(thr.window_secs) || 60;
        thrHtml += '<div style="display:flex;gap:1.5rem;margin-bottom:0.75rem;font-size:0.75rem">'
          + '<div><span style="color:var(--muted)">Active IPs:</span> ' + esc(String(activeIps)) + '</div>'
          + '<div><span style="color:var(--muted)">Active Actors:</span> ' + esc(String(activeActors)) + '</div>'
          + '<div><span style="color:var(--muted)">Throttled (global):</span> ' + esc(String(throttledGlobal)) + '</div>'
          + '<div><span style="color:var(--muted)">Window:</span> ' + esc(String(windowSecs)) + 's</div>'
          + '</div>';
        var topIps = Array.isArray(thr.top_throttled_ips) ? thr.top_throttled_ips : [];
        var topActors = Array.isArray(thr.top_throttled_actors) ? thr.top_throttled_actors : [];
        thrHtml += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">';
        thrHtml += '<div>';
        thrHtml += '<div style="font-size:0.7rem;font-weight:600;margin-bottom:0.35rem">Top Throttled IPs</div>';
        if (topIps.length > 0) {
          thrHtml += '<table style="width:100%;font-size:0.7rem;border-collapse:collapse">';
          thrHtml += '<tr style="color:var(--muted)"><td style="padding:0.15rem 0.5rem 0.15rem 0">IP</td><td style="padding:0.15rem 0;text-align:right">Count</td></tr>';
          topIps.forEach(function(entry) {
            thrHtml += '<tr><td style="padding:0.15rem 0.5rem 0.15rem 0">' + esc(String(entry[0])) + '</td><td style="padding:0.15rem 0;text-align:right">' + esc(String(entry[1])) + '</td></tr>';
          });
          thrHtml += '</table>';
        } else {
          thrHtml += '<div style="font-size:0.7rem;color:var(--muted)">No throttled IPs</div>';
        }
        thrHtml += '</div>';
        thrHtml += '<div>';
        thrHtml += '<div style="font-size:0.7rem;font-weight:600;margin-bottom:0.35rem">Top Throttled Actors</div>';
        if (topActors.length > 0) {
          thrHtml += '<table style="width:100%;font-size:0.7rem;border-collapse:collapse">';
          thrHtml += '<tr style="color:var(--muted)"><td style="padding:0.15rem 0.5rem 0.15rem 0">Actor</td><td style="padding:0.15rem 0;text-align:right">Count</td></tr>';
          topActors.forEach(function(entry) {
            thrHtml += '<tr><td style="padding:0.15rem 0.5rem 0.15rem 0">' + esc(String(entry[0])) + '</td><td style="padding:0.15rem 0;text-align:right">' + esc(String(entry[1])) + '</td></tr>';
          });
          thrHtml += '</table>';
        } else {
          thrHtml += '<div style="font-size:0.7rem;color:var(--muted)">No throttled actors</div>';
        }
        thrHtml += '</div>';
        thrHtml += '</div>';
        setHtml(thrEl, thrHtml);
      }
};
App.renderOverview = function() {
      return Promise.all([
        fetchWithFallback('/api/agent/status', {}, 'agent status'),
        fetchWithFallback('/api/health', {}, 'health'),
        fetchWithFallback('/api/sessions', { sessions: [] }, 'sessions'),
        fetchWithFallback('/api/skills', { skills: [] }, 'skills'),
        fetchWithFallback('/api/cron/jobs', { jobs: [] }, 'cron jobs'),
        fetchWithFallback('/api/stats/cache', { hit_rate: 0 }, 'cache stats'),
        fetchWithFallback('/api/stats/timeseries?hours=24', { series: {}, labels: [] }, 'timeseries'),
        fetchWithFallback('/api/wallet/balance', {}, 'wallet'),
        fetchWithFallback('/api/breaker/status', {}, 'breaker'),
        fetchWithFallback('/api/workspace/state', { agents: [] }, 'workspace'),
        fetchWithFallback('/api/stats/costs', { costs: [] }, 'cost stats'),
        fetchWithFallback('/api/channels/status', [], 'channel status'),
        fetchWithFallback('/api/stats/throttle', {}, 'throttle stats')
      ]).then(function(arr) {
        sparklineId = 0; stackedId = 0;
        var failures = arr.filter(function(r) { return !r.ok; });
        var agent = arr[0].data, health = arr[1].data, sessions = arr[2].data, skills = arr[3].data;
        var cron = arr[4].data, cache = arr[5].data, timeseries = arr[6].data, wallet = arr[7].data, breaker = arr[8].data;
        var wsState = arr[9].data, costData = arr[10].data, channelStatuses = arr[11].data || [];
        var throttleData = arr[12].data || {};
        _cachedWorkspace = wsState;

        var state = (agent.state || 'unknown').toLowerCase();
        applySidebarIdentity(agent, health);
        if (agent.agent_id) App._activeAgentId = String(agent.agent_id);

        var sessionCount = (sessions.sessions || []).length;
        var skillList = skills.skills || [];
        var skillCount = skillList.length;
        var enabledSkills = skillList.filter(function(s) { return s.enabled; }).length;
        var jobCount = (cron.jobs || []).length;
        var hitRate = cache.hit_rate != null ? cache.hit_rate : 0;
        var balance = Number(wallet.balance) || 0;
        var costs = costData.costs || [];
        var totalCost = costs.reduce(function(s, c) { return s + (Number(c.cost) || 0); }, 0);
        var totalTokens = costs.reduce(function(s, c) { return s + (Number(c.tokens_in) || 0) + (Number(c.tokens_out) || 0); }, 0);
        var cronErrors = (cron.jobs || []).reduce(function(s, j) { return s + (j.consecutive_errors || 0); }, 0);
        var agents = wsState.agents || [];
        function agentActivityScore(a) {
          var activity = (a.activity || '').toString().toLowerCase();
          var state = (a.state || '').toString().toLowerCase();
          if (activity === 'idle' || activity === 'standby') return 0;
          if (activity === 'inference' || activity === 'working' || activity === 'tool_execution' || activity === 'tooling') return 1;
          if (activity === 'walking' || activity === 'moving' || activity === 'talking') return 0.5;
          if (state === 'running' && activity) return 0.6;
          return 0;
        }
        var agentRunning = agents.filter(function(a) { return (a.state || '').toLowerCase() === 'running'; });
        var agentActivity = {};
        agents.forEach(function(a) { agentActivity[a.name || a.id] = agentActivityScore(a); });
        var agentActiveNow = Object.keys(agentActivity).filter(function(k) { return (agentActivity[k] || 0) > 0; }).length;

        var costPerHr = totalCost > 0 ? totalCost / 24 : 0;
        var tokPerHr = totalTokens > 0 ? totalTokens / 24 : 0;
        var agentLoadVal = agents.length > 0
          ? agents.reduce(function(sum, a) { return sum + agentActivityScore(a); }, 0) / agents.length
          : 0;
        var cronSuccessVal = cronErrors > 0 ? 0.9 : (jobCount > 0 ? 1.0 : 0);
        var s = (timeseries && timeseries.series) || {};
        var buckets = (timeseries && timeseries.labels && timeseries.labels.length) ? timeseries.labels.length : 24;
        var TS = {
          costPerHour: (s.cost_per_hour || []).map(Number),
          tokensPerHour: (s.tokens_per_hour || []).map(Number),
          cacheHitRate: repeatedSeries(buckets, hitRate || 0),
          walletBalance: repeatedSeries(buckets, balance || 0),
          requestLatency: (s.latency_p50_ms || []).map(Number),
          sessionsPerHour: (s.sessions_per_hour || []).map(Number),
          memoryEntries: repeatedSeries(buckets, 0),
          cronSuccess: (s.cron_success_rate || []).map(Number),
          breakerFailures: repeatedSeries(buckets, 0),
          agentLoad: repeatedSeries(buckets, agentLoadVal),
        };
        if (TS.costPerHour.length === 0) TS.costPerHour = repeatedSeries(buckets, costPerHr);
        if (TS.tokensPerHour.length === 0) TS.tokensPerHour = repeatedSeries(buckets, tokPerHr);
        if (TS.requestLatency.length === 0) TS.requestLatency = repeatedSeries(buckets, 0);
        if (TS.sessionsPerHour.length === 0) TS.sessionsPerHour = repeatedSeries(buckets, sessionCount);
        if (TS.cronSuccess.length === 0) TS.cronSuccess = repeatedSeries(buckets, cronSuccessVal);
        TS.errorRate = TS.cronSuccess.map(function(v) { return Math.max(0, 1 - v); });
        pushFleetSnapshot(agents, agentActivity);
        TS.agentLoadByAgent = {};
        var agentCount = agents.length;
        agents.forEach(function(a) {
          var k = a.name || a.id;
          var history = (_fleetHistory.byAgent[k] || []).slice();
          if (history.length === 0) history = [agentActivity[k] || 0];
          // Normalize: each agent contributes at most 1/agentCount to the
          // stacked total so that 100% = all agents active simultaneously.
          TS.agentLoadByAgent[k] = history.map(function(v) { return v / agentCount; });
        });
        TS.agentLoadLabels = _fleetHistory.labels.slice();

        var costLast = seriesLast(TS.costPerHour), costPrev = seriesPrev(TS.costPerHour);
        var tokLast = seriesLast(TS.tokensPerHour), tokPrev = seriesPrev(TS.tokensPerHour);
        var hitLast = seriesLast(TS.cacheHitRate), hitPrev = seriesPrev(TS.cacheHitRate);
        var balLast = seriesLast(TS.walletBalance), balPrev = seriesPrev(TS.walletBalance);
        var latLast = seriesLast(TS.requestLatency), latPrev = seriesPrev(TS.requestLatency);
        var memLast = seriesLast(TS.memoryEntries);

        var cardCost = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Inference Cost (24h)</div><div class="cc-value">$' + totalCost.toFixed(2) + '</div><div class="cc-sub">$' + costLast.toFixed(4) + '/hr current</div></div><div class="cc-right">' + deltaHtml(costLast, costPrev) + '</div></div>' + renderSparkCanvas(TS.costPerHour, { color: '#c180ff' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Avg / hr</div><div class="cc-stat-value">$' + seriesAvg(TS.costPerHour).toFixed(4) + '</div></div><div class="cc-stat"><div class="cc-stat-label">Peak</div><div class="cc-stat-value">$' + seriesMax(TS.costPerHour).toFixed(4) + '</div></div><div class="cc-stat"><div class="cc-stat-label">Requests</div><div class="cc-stat-value">' + costs.length + '</div></div></div></div>';
        var cardTokens = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Token Throughput</div><div class="cc-value">' + Math.round(tokLast).toLocaleString() + '</div><div class="cc-sub">tokens/hr current</div></div><div class="cc-right">' + deltaHtml(tokLast, tokPrev) + '</div></div>' + renderSparkCanvas(TS.tokensPerHour, { color: '#8b5cf6' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Avg / hr</div><div class="cc-stat-value">' + Math.round(seriesAvg(TS.tokensPerHour)).toLocaleString() + '</div></div><div class="cc-stat"><div class="cc-stat-label">Peak</div><div class="cc-stat-value">' + Math.round(seriesMax(TS.tokensPerHour)).toLocaleString() + '</div></div><div class="cc-stat"><div class="cc-stat-label">Total (24h)</div><div class="cc-stat-value">' + totalTokens.toLocaleString() + '</div></div></div></div>';
        var cardCache = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Cache Hit Rate</div><div class="cc-value">' + (hitLast * 100).toFixed(1) + '%</div><div class="cc-sub">' + (seriesAvg(TS.cacheHitRate) * 100).toFixed(1) + '% avg over 24h</div></div><div class="cc-right">' + deltaHtml(hitLast, hitPrev) + '</div></div>' + renderSparkCanvas(TS.cacheHitRate, { color: '#22c55e' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Floor</div><div class="cc-stat-value">' + (Math.min.apply(null, TS.cacheHitRate) * 100).toFixed(1) + '%</div></div><div class="cc-stat"><div class="cc-stat-label">Ceiling</div><div class="cc-stat-value">' + (seriesMax(TS.cacheHitRate) * 100).toFixed(1) + '%</div></div></div></div>';
        var cardWallet = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Wallet Balance</div><div class="cc-value">' + balLast.toFixed(3) + ' ' + esc(wallet.currency || 'SOL') + '</div><div class="cc-sub">managed wallet</div></div><div class="cc-right">' + deltaHtml(balLast, balPrev) + '</div></div>' + renderSparkCanvas(TS.walletBalance, { color: '#f59e0b' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">24h Low</div><div class="cc-stat-value">' + Math.min.apply(null, TS.walletBalance).toFixed(3) + '</div></div><div class="cc-stat"><div class="cc-stat-label">24h High</div><div class="cc-stat-value">' + seriesMax(TS.walletBalance).toFixed(3) + '</div></div></div></div>';
        var cardLatency = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Request Latency</div><div class="cc-value">' + Math.round(latLast) + 'ms</div><div class="cc-sub">p50 current</div></div><div class="cc-right">' + deltaHtml(latPrev, latLast) + '</div></div>' + renderSparkCanvas(TS.requestLatency, { color: '#06b6d4' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Avg</div><div class="cc-stat-value">' + Math.round(seriesAvg(TS.requestLatency)) + 'ms</div></div><div class="cc-stat"><div class="cc-stat-label">p99</div><div class="cc-stat-value">' + Math.round(seriesMax(TS.requestLatency)) + 'ms</div></div></div></div>';
        var cardSessions = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Sessions</div><div class="cc-value">' + sessionCount + ' active</div></div><div class="cc-right"><span class="cc-delta flat">' + sessionCount + ' open</span></div></div>' + renderSparkCanvas(TS.sessionsPerHour, { color: '#ec4899' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Skills</div><div class="cc-stat-value">' + enabledSkills + '/' + skillCount + '</div></div><div class="cc-stat"><div class="cc-stat-label">Cron jobs</div><div class="cc-stat-value">' + jobCount + '</div></div></div></div>';
        var healthStatus = String(health.status || 'unknown').toLowerCase();
        var healthClass = healthStatus === 'ok' || healthStatus === 'healthy' ? 'success' : (healthStatus === 'warning' ? 'warning' : 'error');
        var breakerState = String(breaker.note || 'closed').toLowerCase();
        var breakerClass = breakerState.indexOf('open') >= 0 ? 'error' : (breakerState.indexOf('half') >= 0 ? 'warning' : 'success');
        var cardSystem = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">System Health</div><div class="cc-value"><span class="badge ' + healthClass + '">' + esc(health.status || 'unknown') + '</span></div><div class="cc-sub">uptime ' + formatUptime(health.uptime_seconds) + '</div></div><div class="cc-right"><span class="cc-delta up">' + (seriesLast(TS.cronSuccess) * 100).toFixed(0) + '% cron</span></div></div>' + renderSparkCanvas(TS.cronSuccess, { color: '#22c55e' }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Breaker</div><div class="cc-stat-value"><span class="badge ' + breakerClass + '" style="font-size:0.6875rem">' + esc(breaker.note || 'closed') + '</span></div></div><div class="cc-stat"><div class="cc-stat-label">Cron errors</div><div class="cc-stat-value">' + cronErrors + '</div></div></div></div>';
        var errLast = seriesLast(TS.errorRate), errPrev = seriesPrev(TS.errorRate);
        var errPeak = seriesMax(TS.errorRate);
        var errAvg = seriesAvg(TS.errorRate);
        var totalInferenceErrors = costs.filter(function(c) { return c.error; }).length;
        var errColor = errLast > 0.05 ? '#ef4444' : '#22c55e';
        var cardErrors = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Error Rate</div><div class="cc-value">' + (errLast * 100).toFixed(1) + '%</div><div class="cc-sub">' + (errAvg * 100).toFixed(1) + '% avg over 24h</div></div><div class="cc-right">' + deltaHtml(errPrev, errLast) + '</div></div>' + renderSparkCanvas(TS.errorRate, { color: errColor }) + '<div class="cc-footer"><div class="cc-stat"><div class="cc-stat-label">Peak</div><div class="cc-stat-value">' + (errPeak * 100).toFixed(1) + '%</div></div><div class="cc-stat"><div class="cc-stat-label">Inference errs</div><div class="cc-stat-value">' + totalInferenceErrors + '</div></div><div class="cc-stat"><div class="cc-stat-label">Cron errs</div><div class="cc-stat-value">' + cronErrors + '</div></div></div></div>';
        var cardFleet = '';

        if (agents.length > 0) {
          var fleetColors = {};
          var fleetLegend = agents.map(function(a) {
            var k = a.name || a.id;
            var isRunning = (a.state || '').toLowerCase() === 'running';
            var lastVal = agentActivity[k] || 0;
            var stateLabel = (a.activity || a.state || 'idle').toString().toLowerCase();
            var color = a.color || '#c180ff';
            fleetColors[k] = color;
            return '<div class="fleet-legend-item">' +
              '<div class="fleet-legend-swatch" style="background:' + color + '"></div>' +
              '<span class="fleet-legend-name">' + esc(k) + '</span>' +
              '<span class="fleet-legend-state ' + (isRunning ? 'running' : 'idle') + '">' + esc(stateLabel) + '</span>' +
              '<span class="fleet-legend-pct">' + (lastVal * 100).toFixed(0) + '%</span>' +
            '</div>';
          }).join('');
          var fleetKeys = agents.map(function(a) { return a.name || a.id; });
          var fleetChart = renderStackedArea(
            TS.agentLoadByAgent,
            fleetKeys,
            fleetColors,
            {
              height: 190,
              labels: TS.agentLoadLabels.length > 1,
              xLabels: TS.agentLoadLabels,
              yAxis: true,
              fixedMax: 1.0,
              yFormat: function(v) { return (v * 100).toFixed(0) + '%'; }
            }
          );
          cardFleet = '<div class="card composite-card"><div class="cc-header"><div class="cc-left"><div class="cc-label">Agent Fleet Activity</div><div class="cc-value">' + (seriesLast(TS.agentLoad) * 100).toFixed(0) + '% active load</div><div class="cc-sub">' + agentActiveNow + ' / ' + agents.length + ' agents active now</div></div><div class="cc-right">' + deltaHtml(seriesLast(TS.agentLoad), seriesPrev(TS.agentLoad)) + '</div></div>' + fleetChart + '<div class="fleet-legend-grid">' + fleetLegend + '</div></div>';
        }

        var attention = [];
        if (failures.length > 0) attention.push(failures.length + ' data source(s) unavailable: ' + failures.map(function(f) { return f.label; }).join(', '));
        if (cronErrors > 0) attention.push('Cron has ' + cronErrors + ' consecutive error(s)');
        if (breakerClass !== 'success') attention.push('Circuit breaker is ' + (breaker.note || 'not closed'));
        if (healthClass !== 'success') attention.push('System health reports "' + (health.status || 'unknown') + '"');

        return {
          cost: cardCost,
          tokens: cardTokens,
          cache: cardCache,
          wallet: cardWallet,
          latency: cardLatency,
          sessions: cardSessions,
          system: cardSystem,
          errors: cardErrors,
          fleet: cardFleet,
          __attention: attention,
          __failures: failures,
          __meta: { updated_at: new Date().toLocaleTimeString() },
          __channelStatuses: channelStatuses,
          __throttle: throttleData
        };
      });
};