agent page: dashboard back-link + last-turn timing chip

title bar grows a '↑ DASHB04RD' link next to the rebuild button —
opens the host dashboard in a new tab so the operator can pivot
between agents without losing the live tail. uses the dashboardPort
already plumbed via /api/state.

state row picks up a 'last turn 12.3s' chip that fills in when
state transitions away from thinking. format: ms / s.s / m s.
hidden until the first turn completes.
This commit is contained in:
müde 2026-05-15 20:27:09 +02:00
parent ee5b85716d
commit bd7d2d4860
4 changed files with 55 additions and 4 deletions

View file

@ -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);