// Agent Time Monitor page — total time AI agents spent on the project. // Data comes from the backend (/api/agent-time), which derives totals // from event logs via a session-gap estimator. No Mnemoverse widget. function fmtDuration(seconds) { seconds = Math.floor(seconds || 0); const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; if (h > 0) return `${h}h ${m}m`; if (m > 0) return `${m}m ${s}s`; return `${s}s`; } function fmtHMS(seconds) { seconds = Math.floor(seconds || 0); const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; return `${h}h ${m}m ${s}s`; } // Agent accent colors, matching agent-health / known identities. function agentColor(agent) { const map = { system: 'var(--purple, #9b8cff)', hermes: 'var(--green, #37e0a6)', opencode: 'var(--accent, #6c5ce7)', gemini: 'var(--yellow, #ffcf5c)', codex: '#5b8cff', jarvis: '#ff79c6', kilocode: '#74b9ff', test_claude: '#a29bfe', test: '#7f8fa6', }; return map[agent] || 'linear-gradient(90deg, var(--accent), var(--green))'; } async function renderAgentTime() { const content = document.getElementById('pageContent'); content.innerHTML = `
`; await loadAgentTime(false); } async function loadAgentTime(recompute) { const body = document.getElementById('agentTimeBody'); try { const data = await api.getAgentTime(recompute); const agents = data.agents || {}; const keys = Object.keys(agents); const max = Math.max(1, ...keys.map(k => agents[k].total_seconds)); const sorted = keys.slice().sort((a, b) => agents[b].total_seconds - agents[a].total_seconds); const g = data.graph || {}; const graphCard = g.nodes != null ? `
Codebase Graph (graphify)
${g.nodes}
nodes
${g.edges}
edges
${g.communities != null ? g.communities : '—'}
communities
↗ Open interactive graph
` : ''; body.innerHTML = `
Total Agent Time
${escapeHtml(data.total_human || '0h 0m 0s')}
estimated · ${escapeHtml((data.method || '').replace('session-gap estimate ', ''))}
Active Agents
${keys.length}
distinct agents
Logged Events
${data.event_count || 0}
audit + chat + cost
Coverage
${(data.first_seen || '—').slice(0, 10)}
→ ${(data.last_seen || '—').slice(0, 10)}
first → last activity
Time per Agent sessions of activity (30-min gap) + 2-min tail
${sorted.map(k => { const a = agents[k]; const pct = ((a.total_seconds / max) * 100).toFixed(1); return ` `; }).join('')}
AgentTotal TimeShareSessionsTouches
${escapeHtml(k)} ${fmtDuration(a.total_seconds)}
${a.sessions} ${a.touches}
${graphCard ? `
${graphCard}
` : ''}
Computed ${escapeHtml((data.generated_at || '').replace('T', ' ').slice(0, 19))} UTC. Totals are estimates — the project logs events, not durations; see the method in the report.
`; // animate bars setTimeout(() => { document.querySelectorAll('#agentTimeBody [data-w]').forEach(el => { el.style.width = el.dataset.w; }); }, 60); } catch (err) { body.innerHTML = `
Could not load agent-time report
${escapeHtml(err.message)}
`; } } async function recomputeAgentTime() { const body = document.getElementById('agentTimeBody'); showToast('Recomputing from logs…', 'info'); try { const res = await api.recomputeAgentTime(); if (res.ok) { showToast(`Recomputed: ${res.total_human} across ${res.agents} agents`, 'success'); await loadAgentTime(false); } else { showToast('Recompute failed', 'error'); } } catch (err) { showToast(`Recompute error: ${err.message}`, 'error'); } }