web-ui: expose per-agent paused status, add pause/resume to the agent page's own overflow menu
hive-agent's own web_ui module never exposed the agent's own paused status to its own /api/state — the dashboard's cross-container view knew it, but a per-agent page had no way to know it's paused. Added StateSnapshot.paused (a direct stat of the same harness-local pause marker hive-c0re's Coordinator::is_paused checks). The per-agent page's ⋯ overflow menu now has a pause/resume item that POSTs to hive-c0re's existing /api/pause/<name> / /api/resume/<name> — the same endpoints the dashboard's <hive-agent-menu> already uses, same cross-origin form-submit pattern the existing rebuild-container item uses. The item's label tracks state.paused on every /api/state refresh so a pause/resume triggered from the dashboard while this page is open doesn't leave a stale action showing.
This commit is contained in:
parent
765bea2022
commit
5bd085fbac
3 changed files with 85 additions and 2 deletions
|
|
@ -67,14 +67,23 @@ through. Three flex columns:
|
|||
(`GET /api/todos`, refreshed on cold load + every `turn_end`).
|
||||
- **Overflow button** (`⋯`): always visible. Opens a frosted popover
|
||||
(`#overflow-menu`, positioned outside the header to escape any
|
||||
stacking context) with four management rows followed by a model
|
||||
stacking context) with five management rows followed by a model
|
||||
quick-picker section: `↑ dashboard` (link), `↻ rebuild container`
|
||||
(POST confirm, same action as the dashboard R3BU1LD button),
|
||||
`↻ new claude session` (POST confirm → `POST /api/new-session`;
|
||||
next turn drops `--continue`), `🔓 logout` (POST confirm → `POST
|
||||
/api/logout`; SIGINTs any in-flight turn, wipes OAuth credential
|
||||
files, flips the agent to `needs_login` — session history
|
||||
preserved). All destructive actions require one extra click to
|
||||
preserved), and `⏸ pause agent` / `▶ resume agent` (POST confirm →
|
||||
hive-c0re's `/api/pause/<name>` / `/api/resume/<name>` — the same
|
||||
endpoints the dashboard's own `<hive-agent-menu>` uses, since
|
||||
pausing is a hive-c0re-owned write this unprivileged process can't
|
||||
make directly). The label + target endpoint track `state.paused`
|
||||
(this agent's own `/api/state`, a direct stat of the harness's
|
||||
local pause marker — see `docs/persistence.md`), refreshed on every
|
||||
snapshot so a pause/resume triggered from the *dashboard* while
|
||||
this page is open doesn't leave the menu item showing the wrong
|
||||
action. All destructive actions require one extra click to
|
||||
acknowledge — rare ops shouldn't live in the primary state strip.
|
||||
Below a separator, a **model quick-picker** section labelled
|
||||
`model` renders one button per model in the operator-configured
|
||||
|
|
|
|||
|
|
@ -231,6 +231,41 @@ window.marked = marked;
|
|||
});
|
||||
menu.append(logoutBtn);
|
||||
|
||||
// ⏸ pause / ▶ resume — this page has no pause state of its own to
|
||||
// hold or mutate; it POSTs to the same hive-c0re endpoints
|
||||
// (`/api/pause/<name>` / `/api/resume/<name>`) the dashboard's own
|
||||
// `<hive-agent-menu>` already uses, and only needs `state.paused`
|
||||
// (this agent's own `/api/state`, added alongside this menu item)
|
||||
// to know which of the two to show. See `renderPausedChip`, called
|
||||
// from `refreshState` on every snapshot so the label tracks reality
|
||||
// even when the pause/resume actually happened from the dashboard.
|
||||
const pauseBtn = el('button', {
|
||||
type: 'button',
|
||||
class: 'overflow-item overflow-item-pause',
|
||||
role: 'menuitem',
|
||||
id: 'pause-btn',
|
||||
},
|
||||
el('span', { class: 'overflow-item-icon', 'aria-hidden': 'true' }, '⏸'),
|
||||
el('span', { id: 'pause-btn-label' }, 'pause agent'),
|
||||
);
|
||||
pauseBtn.addEventListener('click', async () => {
|
||||
const paused = pauseBtn.dataset.paused === 'true';
|
||||
const verb = paused ? 'resume' : 'pause';
|
||||
const message = paused
|
||||
? `resume ${label}? the turn loop restarts and drains queued messages.`
|
||||
: `pause ${label}? parks the turn loop — inbox messages queue unacked.`;
|
||||
if (!(await themedConfirm({
|
||||
message, danger: true, confirmLabel: paused ? '▶ resume' : '⏸ pause',
|
||||
}))) return;
|
||||
closeOverflowMenu();
|
||||
const f = document.createElement('form');
|
||||
f.method = 'POST';
|
||||
f.action = `${dashUrl}api/${verb}/${label}`;
|
||||
document.body.appendChild(f);
|
||||
f.submit();
|
||||
});
|
||||
menu.append(pauseBtn);
|
||||
|
||||
// ─── model quick-picker ────────────────────────────────────────
|
||||
// One-click shortcuts for each model in `availableModels` (seeded
|
||||
// from `state.available_models` / `HIVE_AVAILABLE_MODELS` nix option).
|
||||
|
|
@ -493,6 +528,12 @@ window.marked = marked;
|
|||
let effortPickerBtns = [];
|
||||
let availableEfforts = [];
|
||||
|
||||
// Pause/resume toggle in the overflow menu — see `renderPausedChip`.
|
||||
// Tracked so subsequent /api/state refreshes can flip the existing
|
||||
// button's label without rebuilding the whole overflow menu (same
|
||||
// reason `currentModel`/`currentEffort` are tracked above).
|
||||
let currentPaused = false;
|
||||
|
||||
const SLASH_COMMANDS = [
|
||||
{ name: '/help', desc: 'list slash commands' },
|
||||
{ name: '/clear', desc: 'wipe the terminal panel (local-only)' },
|
||||
|
|
@ -1056,6 +1097,25 @@ window.marked = marked;
|
|||
btn.setAttribute('aria-pressed', String(isActive));
|
||||
}
|
||||
}
|
||||
|
||||
// Flips the overflow menu's pause/resume item to match the backend's
|
||||
// reported `state.paused` (harness-local marker stat, cheap to refresh
|
||||
// on every /api/state poll) — without this, an operator who pauses from
|
||||
// the *dashboard* while this page is open would still see a stale
|
||||
// "pause agent" item here, offering the wrong action.
|
||||
function renderPausedChip(paused) {
|
||||
currentPaused = !!paused;
|
||||
const btn = $('pause-btn');
|
||||
if (!btn) return;
|
||||
btn.dataset.paused = String(currentPaused);
|
||||
const icon = btn.querySelector('.overflow-item-icon');
|
||||
const label_ = $('pause-btn-label');
|
||||
if (icon) icon.textContent = currentPaused ? '▶' : '⏸';
|
||||
if (label_) label_.textContent = currentPaused ? 'resume agent' : 'pause agent';
|
||||
btn.title = currentPaused
|
||||
? 'resume this agent — the turn loop restarts and drains queued messages'
|
||||
: 'pause this agent — parks the turn loop, inbox messages queue unacked';
|
||||
}
|
||||
// Token badges — two separate chips:
|
||||
// ctx · N last inference's prompt size = current context window
|
||||
// utilisation (what to watch for compaction decisions)
|
||||
|
|
@ -1196,6 +1256,7 @@ window.marked = marked;
|
|||
renderAliveBadge(s.status);
|
||||
renderModelChip(s.model);
|
||||
renderEffortChip(s.effort);
|
||||
renderPausedChip(s.paused);
|
||||
renderTokenUsage({ ctx: s.ctx_usage, cost: s.cost_usage });
|
||||
// Todos pill: cold-load populate; turn_end refreshes via renderTodos.
|
||||
refreshTodos();
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ pub(super) async fn api_state(State(state): State<AppState>) -> axum::Json<State
|
|||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
paused: crate::paths::paused_marker().exists(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +186,18 @@ pub(super) struct StateSnapshot {
|
|||
/// [`crate::harness_state::EFFORT_LEVELS`], not operator-configurable like
|
||||
/// `available_models`. The frontend renders one button per entry.
|
||||
available_efforts: Vec<String>,
|
||||
/// Whether this agent's turn loop is currently parked (the harness
|
||||
/// keeps serving this page + its MCP daemons but drives no turns).
|
||||
/// Same on-disk marker hive-c0re's `Coordinator::is_paused` checks
|
||||
/// (`crate::paths::paused_marker`) — read directly here rather than
|
||||
/// asking hive-c0re over the socket, since the harness already has
|
||||
/// the file locally. hive-c0re owns the actual pause/resume *writes*
|
||||
/// (via hive-priv, this process runs unprivileged) — the per-agent
|
||||
/// page's own `⋯` menu POSTs to hive-c0re's existing
|
||||
/// `/api/pause/<name>` / `/api/resume/<name>` (same endpoints
|
||||
/// `<hive-agent-menu>` on the dashboard already uses), this field
|
||||
/// only tells the frontend which of the two to show.
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue