diff --git a/TODO.md b/TODO.md index 58013ad..20bb3da 100644 --- a/TODO.md +++ b/TODO.md @@ -27,8 +27,12 @@ Pick anything from here when relevant. Cross-cutting design notes live in ## UI / UX -- **Per-agent UI substance.** Show last N inbox messages, last turn timing, - link back to dashboard. +- **Per-agent inbox view.** Show the last N messages addressed to + this agent on its page (the per-agent equivalent of the + dashboard's operator inbox). Needs a new wire request from agent + โ host (host has the broker; agent doesn't); reuse the broker's + `recent_for` query. Last-turn timing + dashboard back-link + already shipped. - **State badge: compacting + napping states.** Idle/thinking already ship (driven from SSE turn_start/turn_end). Add `compacting ๐ฆ` and `napping ๐ด` once the `/compact` trigger and `nap` tool exist โ diff --git a/hive-ag3nt/assets/agent.css b/hive-ag3nt/assets/agent.css index 5e6b0c6..3624f19 100644 --- a/hive-ag3nt/assets/agent.css +++ b/hive-ag3nt/assets/agent.css @@ -130,6 +130,26 @@ pre.diff { align-items: center; gap: 0.6em; } +.last-turn { + color: var(--muted); + font-size: 0.8em; + letter-spacing: 0.05em; +} +.btn-dashlink { + color: var(--cyan); + border: 1px solid var(--cyan); + padding: 0.15em 0.6em; + font-size: 0.55em; + font-family: inherit; + text-decoration: none; + letter-spacing: 0.1em; + margin-left: 0.6em; + vertical-align: middle; +} +.btn-dashlink:hover { + background: rgba(137, 220, 235, 0.1); + box-shadow: 0 0 10px -2px currentColor; +} .btn-cancel-turn { font-family: inherit; font-size: 0.8em; diff --git a/hive-ag3nt/assets/app.js b/hive-ag3nt/assets/app.js index 12a5404..f56bf7f 100644 --- a/hive-ag3nt/assets/app.js +++ b/hive-ag3nt/assets/app.js @@ -65,16 +65,25 @@ `โโโโโโโ ${label} โโโโโโโ hyperhive ag3nt โโโโโโโ`; const title = $('title'); title.textContent = `โ ${label} โ `; + // โ DASHB04RD โ back-link to the host dashboard. Opens in a new + // tab to keep the agent page anchored where the operator is. + const dashUrl = `${location.protocol}//${location.hostname}:${dashboardPort}/`; + title.append( + el('a', { + href: dashUrl, target: '_blank', rel: 'noopener', + class: 'btn-dashlink', title: 'host dashboard', + }, 'โ DASHB04RD'), + ' ', + ); const btn = el('a', { href: '#', class: 'btn-rebuild', id: 'rebuild-btn', }, 'โป R3BU1LD'); btn.addEventListener('click', (e) => { e.preventDefault(); if (!confirm(`rebuild ${label}? container will hot-reload.`)) return; - const url = `${location.protocol}//${location.hostname}:${dashboardPort}/rebuild/${label}`; const f = document.createElement('form'); f.method = 'POST'; - f.action = url; + f.action = `${dashUrl}rebuild/${label}`; document.body.appendChild(f); f.submit(); }); @@ -310,6 +319,13 @@ } function setState(next) { if (next === stateName) return; + // Capture the just-ending state's duration when leaving 'thinking' + // so the operator can eyeball turn length without scrolling the + // terminal back. + if (stateName === 'thinking' && next !== 'thinking') { + const elapsedMs = Date.now() - stateSince; + renderLastTurn(elapsedMs); + } stateName = next; stateSince = Date.now(); const badge = $('state-badge'); @@ -321,6 +337,16 @@ } renderStateBadge(); } + function renderLastTurn(ms) { + const el_ = $('last-turn'); + if (!el_) return; + let s = ''; + if (ms < 1000) s = ms + 'ms'; + else if (ms < 60_000) s = (ms / 1000).toFixed(1) + 's'; + else s = Math.floor(ms / 60_000) + 'm ' + Math.floor((ms / 1000) % 60) + 's'; + el_.textContent = 'ยท last turn ' + s; + el_.hidden = false; + } function startStateTicker() { if (stateTickTimer) return; stateTickTimer = setInterval(renderStateBadge, 1000); diff --git a/hive-ag3nt/assets/index.html b/hive-ag3nt/assets/index.html index 397fd32..5d22c30 100644 --- a/hive-ag3nt/assets/index.html +++ b/hive-ag3nt/assets/index.html @@ -15,6 +15,7 @@