// 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 ? `
` : '';
body.innerHTML = `
${escapeHtml(data.total_human || '0h 0m 0s')}
estimated · ${escapeHtml((data.method || '').replace('session-gap estimate ', ''))}
${keys.length}
distinct agents
${data.event_count || 0}
audit + chat + cost
${(data.first_seen || '—').slice(0, 10)}
→ ${(data.last_seen || '—').slice(0, 10)}
first → last activity
Agent Total Time Share Sessions Touches
${sorted.map(k => {
const a = agents[k];
const pct = ((a.total_seconds / max) * 100).toFixed(1);
return `
${escapeHtml(k)}
${fmtDuration(a.total_seconds)}
${a.sessions}
${a.touches}
`;
}).join('')}
${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)}
Recompute now
`;
}
}
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');
}
}