feat(agent-ui): show turn start/end times + duration on the agent terminal
The per-agent terminal marks turn boundaries (◆ TURN ← / ✓ turn) but shows no time. Append a wall-clock HH:MM:SS to both the turn-start and turn-end rows, plus the elapsed duration on turn-end, rendered as dim metadata so the boundary glyph stays the focus. This is the frontend half of the feature; it reads a per-event `ts` (unix seconds) off the turn_start / turn_end events. The read is guarded on a numeric `ts`, so until the harness surfaces per-event timestamps the rows render exactly as before (inert-until-capture). Once the backend attaches `ts` to the history rows + live SSE frame, the times light up automatically for both live and scrollback, with no further frontend change.
This commit is contained in:
parent
50e0ef9b46
commit
e89ca90956
2 changed files with 39 additions and 1 deletions
|
|
@ -756,6 +756,16 @@ window.marked = marked;
|
|||
const h = Math.floor(m / 60);
|
||||
return h + 'h ' + (m % 60) + 'm';
|
||||
}
|
||||
// Wall-clock HH:MM:SS (UTC, matching the inbox timestamps on this page)
|
||||
// from a unix-seconds value. Used to label turn-start / turn-end rows
|
||||
// when the event carries a `ts` (see the turn renderers below).
|
||||
function fmtClock(sec) {
|
||||
return new Date(sec * 1000).toISOString().slice(11, 19);
|
||||
}
|
||||
// Unix-seconds stamp of the most recent open turn-start, so the
|
||||
// matching turn-end can show a duration. Turns are sequential, so a
|
||||
// single slot is enough (history replays chronologically too).
|
||||
let pendingTurnStartTs = null;
|
||||
const STATE_TOOLTIPS = {
|
||||
loading: 'harness not yet contacted',
|
||||
offline: 'harness unreachable or claude not logged in',
|
||||
|
|
@ -1697,6 +1707,16 @@ window.marked = marked;
|
|||
if (api.fromHistory) openTurnsFromHistory += 1;
|
||||
else { setBannerActive(true); setState('thinking'); }
|
||||
const block = api.row('turn-start', '◆ TURN ← ' + ev.from);
|
||||
// Turn start time. Guarded on a numeric `ts` (unix seconds)
|
||||
// so the row degrades to its old text-only form until the
|
||||
// backend surfaces per-event timestamps.
|
||||
if (typeof ev.ts === 'number') {
|
||||
pendingTurnStartTs = ev.ts;
|
||||
const t = document.createElement('span');
|
||||
t.className = 'turn-time';
|
||||
t.textContent = '· ' + fmtClock(ev.ts);
|
||||
block.appendChild(t);
|
||||
}
|
||||
if (ev.unread > 0) {
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'unread-badge';
|
||||
|
|
@ -1717,9 +1737,23 @@ window.marked = marked;
|
|||
refreshLooseEnds();
|
||||
}
|
||||
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
|
||||
api.row(cls,
|
||||
const row = api.row(cls,
|
||||
(ev.ok ? '✓' : '✗') + ' turn ' + (ev.ok ? 'ok' : 'fail')
|
||||
+ (ev.note ? ' — ' + ev.note : ''));
|
||||
// Turn end time + duration since the paired turn-start.
|
||||
// Same `ts` guard as turn_start; duration only when we saw the
|
||||
// matching start's stamp.
|
||||
if (typeof ev.ts === 'number') {
|
||||
const t = document.createElement('span');
|
||||
t.className = 'turn-time';
|
||||
let label = '· ' + fmtClock(ev.ts);
|
||||
if (pendingTurnStartTs != null && ev.ts >= pendingTurnStartTs) {
|
||||
label += ' · ' + fmtAge((ev.ts - pendingTurnStartTs) * 1000);
|
||||
}
|
||||
t.textContent = label;
|
||||
row.appendChild(t);
|
||||
}
|
||||
pendingTurnStartTs = null;
|
||||
},
|
||||
note(ev, api) {
|
||||
const t = String(ev.text || '');
|
||||
|
|
|
|||
|
|
@ -100,6 +100,10 @@
|
|||
.live .row .md, .live .row > details { text-indent: 0; }
|
||||
.live .turn-end-ok { color: var(--green); border-left-color: var(--green); }
|
||||
.live .turn-end-fail { color: var(--red); border-left-color: var(--red); }
|
||||
/* Wall-clock time (+ duration on turn-end) appended to the turn-start /
|
||||
turn-end rows. Dim + smaller so the boundary glyph stays the focus and
|
||||
the timestamp reads as metadata. */
|
||||
.live .turn-time { color: var(--muted); font-size: 0.85em; margin-left: 0.5em; }
|
||||
.live .text { color: var(--fg); }
|
||||
.live .thinking { color: var(--muted); font-style: italic; }
|
||||
.live .tool-use { color: var(--cyan); }
|
||||
|
|
|
|||
Loading…
Reference in a new issue