agent ui: event-driven status / model / token_usage / turn_state
new LiveEvent variants on the per-agent bus —
status_changed / model_changed / token_usage_changed /
turn_state_changed — replace the per-agent web UI's
/api/state polling for the badge row.
emit sites:
- Bus::set_model → model_changed
- Bus::record_usage → token_usage_changed
- Bus::set_state → turn_state_changed
- turn::wait_for_login → status_changed("online") on creds detect
- post_login_start / post_login_cancel → status_changed("needs_login_*")
per-agent endpoints (post_set_model / post_compact / post_new_session
/ post_cancel_turn / post_login_*) now all return 200; client
drops the post-submit refetch except on login transitions, which
still need /api/state to render the OAuth form + session stream.
client adds dispatch on the four new event kinds, threads
`currentLabel` through so the composer re-enables on a live
status flip, and no longer fires refreshState() from turn_end or
postModel — the events carry the same signal faster.
closes the per-agent half of the dashboard event-channel
refactor; TODO entry dropped.
This commit is contained in:
parent
b444dac6e8
commit
39d8359c10
7 changed files with 120 additions and 22 deletions
|
|
@ -168,6 +168,10 @@
|
|||
// dispatcher to print local-only rows ('help', errors) and to clear
|
||||
// the terminal on `/clear`.
|
||||
let termAPI = null;
|
||||
// Label captured from the first /api/state cold load — used by the
|
||||
// bus-driven `status_changed` handler so it can re-enable the
|
||||
// composer without waiting for the next snapshot fetch.
|
||||
let currentLabel = '';
|
||||
|
||||
const SLASH_COMMANDS = [
|
||||
{ name: '/help', desc: 'list slash commands' },
|
||||
|
|
@ -192,9 +196,9 @@
|
|||
const text = await resp.text().catch(() => '');
|
||||
termAPI.row('turn-end-fail', '✗ /model failed: ' + resp.status
|
||||
+ (text ? ' — ' + text : ''));
|
||||
} else {
|
||||
refreshState();
|
||||
}
|
||||
// No refreshState — the harness emits `model_changed` on the
|
||||
// SSE bus and the chip handler picks it up live.
|
||||
} catch (err) {
|
||||
if (termAPI) termAPI.row('turn-end-fail', '✗ /model failed: ' + err);
|
||||
}
|
||||
|
|
@ -523,6 +527,7 @@
|
|||
if (!resp.ok) throw new Error('http ' + resp.status);
|
||||
const s = await resp.json();
|
||||
if (!headerSet) { setHeader(s.label, s.dashboard_port); headerSet = true; }
|
||||
currentLabel = s.label;
|
||||
renderTermInput(s.label, s.status === 'online');
|
||||
renderInbox(s.inbox || []);
|
||||
// Authoritative state comes from the harness via /api/state.
|
||||
|
|
@ -725,8 +730,6 @@
|
|||
openTurnsFromHistory = Math.max(0, openTurnsFromHistory - 1);
|
||||
} else {
|
||||
setBannerActive(false); setState('idle');
|
||||
// Login may have just landed (or session re-enters Online).
|
||||
refreshState();
|
||||
}
|
||||
const cls = ev.ok ? 'turn-end-ok' : 'turn-end-fail';
|
||||
api.row(cls,
|
||||
|
|
@ -738,6 +741,38 @@
|
|||
const v = Object.assign({}, ev); delete v.kind;
|
||||
renderStream(v, api);
|
||||
},
|
||||
// Bus-driven state/badges. `status_changed` may also need a
|
||||
// /api/state refresh to render the login `#status` block
|
||||
// (which carries the OAuth URL + form), so we kick the
|
||||
// existing refresh path on that transition. Online → only
|
||||
// the badge updates; no /api/state fetch needed.
|
||||
status_changed(ev, api) {
|
||||
if (api.fromHistory) return;
|
||||
renderAliveBadge(ev.status);
|
||||
renderTermInput(currentLabel, ev.status === 'online');
|
||||
// Login-flow transitions need the #status block rebuilt
|
||||
// (it carries the OAuth URL + form). The existing
|
||||
// refreshState path also re-arms the in-progress poll for
|
||||
// session output streaming. Online → only the badge moves;
|
||||
// no /api/state fetch is necessary.
|
||||
if (ev.status !== 'online' && ev.status !== lastStatus) {
|
||||
refreshState();
|
||||
} else if (ev.status === 'online' && lastStatus !== 'online') {
|
||||
// Status block stays as-is or shows the previous
|
||||
// login UI; clear it so the operator sees a clean
|
||||
// online state without a separate refetch.
|
||||
const root = $('status');
|
||||
if (root) root.innerHTML = '';
|
||||
lastStatus = 'online';
|
||||
}
|
||||
},
|
||||
model_changed(ev, api) { if (!api.fromHistory) renderModelChip(ev.model); },
|
||||
token_usage_changed(ev, api) {
|
||||
if (!api.fromHistory) renderTokenUsage(ev.usage);
|
||||
},
|
||||
turn_state_changed(ev, api) {
|
||||
if (!api.fromHistory) setStateAbs(ev.state, ev.since_unix);
|
||||
},
|
||||
},
|
||||
onBackfillDone() {
|
||||
// If the last replayed turn never closed, the banner shimmer +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue