docs/agent.md: fix stale 3-flyout description; drop dead buildLooseEndsList
docs/web-ui/agent.md still described a "loose-ends" and "tasks" flyout that were superseded by the todos flyout when loose-ends-v2 landed — GET /api/loose-ends and GET /api/bash-tasks are both gone server-side. Replaced with an accurate description of the todos flyout (including the mark-done bulk action from #2919), and noted that the ask->operator inline-answer binding this doc also describes is currently non-functional (its data source was the same removed endpoint) — tracked separately as #2922, not fixed here. buildLooseEndsList in app.js rendered the old loose-ends flyout and had zero call sites left; removed it. buildAnswerForm stays — reconcileAskBinds still calls it, even though that path is currently dead per #2922. Fixes #2920
This commit is contained in:
parent
77e30dd1bc
commit
94fb42f2b6
2 changed files with 42 additions and 96 deletions
|
|
@ -147,34 +147,42 @@ newlines, Tab-completes slash commands (see "Terminal-embedded
|
||||||
prompt" below).
|
prompt" below).
|
||||||
|
|
||||||
**Side panel** (slide-in from right): singleton shared with the
|
**Side panel** (slide-in from right): singleton shared with the
|
||||||
dashboard's side panel shape. Carries inbox and loose-ends flyouts
|
dashboard's side panel shape. Carries inbox and todos flyouts (opened
|
||||||
(opened via the header pills) as well as long content (file previews,
|
via the header pills) as well as long content (file previews, diffs,
|
||||||
diffs, journald logs). Inbox flyout: unread messages addressed to
|
journald logs). Inbox flyout: unread messages addressed to this agent
|
||||||
this agent (`acked_at IS NULL`, newest-first, up to 30); reply
|
(`acked_at IS NULL`, newest-first, up to 30); reply messages indented
|
||||||
messages indented with `↳ reply ·` in amber. A `✓ mark all read`
|
with `↳ reply ·` in amber. A `✓ mark all read` button appears in the
|
||||||
button appears in the flyout header when the inbox is non-empty;
|
flyout header when the inbox is non-empty; clicking it confirms then
|
||||||
clicking it confirms then POSTs cross-origin to the core
|
POSTs cross-origin to the core dashboard's `POST
|
||||||
dashboard's `POST /api/agent/{name}/mark-all-read` — all pending
|
/api/agent/{name}/mark-all-read` — all pending messages for this agent
|
||||||
messages for this agent are acked, the harness won't receive
|
are acked, the harness won't receive wake-prompts for them. A `{
|
||||||
wake-prompts for them. A `{ marked: N }` pill surfaces the count.
|
marked: N }` pill surfaces the count. After the drain the inbox list
|
||||||
After the drain the inbox list empties on reload (the filter is
|
empties on reload (the filter is `acked_at IS NULL`, so drained
|
||||||
`acked_at IS NULL`, so drained messages disappear). Loose-ends flyout: questions,
|
messages disappear). Todos flyout ("loose-ends v2"): the harness-local
|
||||||
approvals, reminders, pending inbox messages, and unread matrix
|
todos other subsystems (matrix, forge, bash) push at this agent
|
||||||
notifications pending against this agent (`GET /api/loose-ends`);
|
(`GET /api/todos`) — each row shows the producing subsystem, an
|
||||||
question rows carry an inline answer form that POSTs cross-origin to
|
optional source label, a summary, and age. A checkbox per row plus a
|
||||||
the core dashboard's `POST /api/answer-question/{id}` so the operator answers
|
select-all / select-none / `✓ mark done` bulk row above the list POSTs
|
||||||
*as operator* (see `docs/boundary.md`). Pending inbox messages (`✉ inbox
|
the checked ids to `POST /api/todos/mark-done`, which dismisses them
|
||||||
— N pending message(s)`) and unread matrix rooms appear informational
|
from the harness-local store (same effect as `cancel_loose_end(kind:
|
||||||
only (not cancellable from the flyout — drain with `recv` / `mark_read`
|
"todo")`, just from the web UI instead of the agent's own tool calls).
|
||||||
in-turn). Tasks flyout: in-flight bash
|
|
||||||
tasks (`GET /api/bash-tasks`); each row shows status (`▶ running` /
|
|
||||||
`◷ queued`), the task id, elapsed time, and a truncated one-line
|
|
||||||
command preview. Read-only — kill/inspect lives in the harness, not
|
|
||||||
the page.
|
|
||||||
|
|
||||||
**Ask → operator inline-answer binding.** When the agent emits
|
Older per-agent flyouts this doc used to describe (a "loose-ends"
|
||||||
`mcp__hyperhive__ask(to: "operator", ...)`, the tool_use renderer
|
list of questions/approvals/reminders backed by a since-removed
|
||||||
mounts an empty slot (`<div class="ask-answer-inline-slot">`)
|
`GET /api/loose-ends`, and a read-only "tasks" list of in-flight bash
|
||||||
|
tasks backed by a since-removed `GET /api/bash-tasks`) no longer
|
||||||
|
exist — todos superseded both. One open gap from that migration:
|
||||||
|
the "Ask → operator inline-answer binding" described below relies on
|
||||||
|
that removed `/api/loose-ends` endpoint and is currently non-functional
|
||||||
|
(hyperhive#2922) — the operator can still answer a pending question via
|
||||||
|
the main dashboard's own question surfacing, just not inline in this
|
||||||
|
agent's terminal.
|
||||||
|
|
||||||
|
**Ask → operator inline-answer binding** (currently non-functional,
|
||||||
|
see the todos-flyout paragraph above + hyperhive#2922 — described here
|
||||||
|
for the intended design, which is what a fix would restore). When the
|
||||||
|
agent emits `mcp__hyperhive__ask(to: "operator", ...)`, the tool_use
|
||||||
|
renderer mounts an empty slot (`<div class="ask-answer-inline-slot">`)
|
||||||
right under the `↳ ask → operator` row in the terminal scrollback
|
right under the `↳ ask → operator` row in the terminal scrollback
|
||||||
and pushes a reference into `pendingAskBinds`. The broker assigns
|
and pushes a reference into `pendingAskBinds`. The broker assigns
|
||||||
the question id asynchronously, so the slot waits — and the next
|
the question id asynchronously, so the slot waits — and the next
|
||||||
|
|
|
||||||
|
|
@ -792,75 +792,12 @@ window.marked = marked;
|
||||||
|
|
||||||
/** ask → operator inline-answer slot registry. See
|
/** ask → operator inline-answer slot registry. See
|
||||||
* docs/web-ui.md::Per-agent page (Ask → operator inline-answer
|
* docs/web-ui.md::Per-agent page (Ask → operator inline-answer
|
||||||
* binding) for the matching algorithm + resolution semantics. */
|
* binding) for the matching algorithm + resolution semantics —
|
||||||
|
* currently non-functional (its data source was the old loose-ends
|
||||||
|
* endpoint, since removed — tracked on the forge), kept for when
|
||||||
|
* that's resolved rather than ripped out speculatively. */
|
||||||
const pendingAskBinds = [];
|
const pendingAskBinds = [];
|
||||||
|
|
||||||
function buildLooseEndsList(threads) {
|
|
||||||
// Returns the <div> the side panel renders. The structural shape
|
|
||||||
// mirrors the legacy <details>-collapsible block — same CSS rules
|
|
||||||
// apply via `hive-side-panel .agent-inbox`.
|
|
||||||
const wrap = el('div', { class: 'agent-inbox' });
|
|
||||||
if (!threads.length) {
|
|
||||||
wrap.append(el('p', { class: 'side-panel-empty' },
|
|
||||||
'no loose ends — every question, approval and reminder is resolved.'));
|
|
||||||
return wrap;
|
|
||||||
}
|
|
||||||
const list = el('ul');
|
|
||||||
const fmtAge = (s) => {
|
|
||||||
if (s < 60) return s + 's';
|
|
||||||
if (s < 3600) return Math.floor(s / 60) + 'm';
|
|
||||||
if (s < 86400) return Math.floor(s / 3600) + 'h';
|
|
||||||
return Math.floor(s / 86400) + 'd';
|
|
||||||
};
|
|
||||||
for (const t of threads) {
|
|
||||||
const li = el('li');
|
|
||||||
if (t.kind === 'approval') {
|
|
||||||
li.append(
|
|
||||||
el('span', { class: 'inbox-from' }, '◇ approval #' + t.id), ' ',
|
|
||||||
el('span', { class: 'inbox-sep' }, t.agent + ' @ ' + (t.commit_ref || '').slice(0, 12)), ' ',
|
|
||||||
el('span', { class: 'inbox-ts' }, fmtAge(t.age_seconds || 0) + ' ago'),
|
|
||||||
);
|
|
||||||
if (t.description) {
|
|
||||||
li.append(el('div', { class: 'inbox-body' }, t.description));
|
|
||||||
}
|
|
||||||
} else if (t.kind === 'question') {
|
|
||||||
const target = t.target || 'operator';
|
|
||||||
li.append(
|
|
||||||
el('span', { class: 'inbox-from' }, '? #' + t.id), ' ',
|
|
||||||
el('span', { class: 'inbox-sep' }, t.asker + ' → ' + target), ' ',
|
|
||||||
el('span', { class: 'inbox-ts' }, fmtAge(t.age_seconds || 0) + ' ago'),
|
|
||||||
el('div', { class: 'inbox-body' }, t.question || ''),
|
|
||||||
buildAnswerForm(t.id),
|
|
||||||
);
|
|
||||||
} else if (t.kind === 'reminder') {
|
|
||||||
// due_at arrives as an ISO 8601 string (DateTime<Utc> on the wire).
|
|
||||||
// Parse it to unix seconds before arithmetic — doing `t.due_at - now`
|
|
||||||
// directly yields NaN because a string minus a number is NaN in JS.
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const dueAtSec = t.due_at ? Math.floor(new Date(t.due_at).getTime() / 1000) : 0;
|
|
||||||
const dueIn = dueAtSec - now;
|
|
||||||
const dueLabel = dueIn >= 0 ? 'in ' + fmtAge(dueIn) : fmtAge(-dueIn) + ' overdue';
|
|
||||||
li.append(
|
|
||||||
el('span', { class: 'inbox-from' }, '⏰ reminder #' + t.id), ' ',
|
|
||||||
el('span', { class: 'inbox-sep' }, t.owner + ' · due ' + dueLabel), ' ',
|
|
||||||
el('span', { class: 'inbox-ts' }, 'scheduled ' + fmtAge(t.age_seconds || 0) + ' ago'),
|
|
||||||
el('div', { class: 'inbox-body' }, t.message || ''),
|
|
||||||
);
|
|
||||||
} else if (t.kind === 'pending_messages') {
|
|
||||||
li.append(
|
|
||||||
el('span', { class: 'inbox-from' }, '✉ inbox'), ' ',
|
|
||||||
el('span', { class: 'inbox-sep' }, (t.count || 0) + ' pending message(s)'), ' ',
|
|
||||||
el('span', { class: 'inbox-ts' }, 'drain with recv'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
li.append(el('span', { class: 'inbox-body' }, JSON.stringify(t)));
|
|
||||||
}
|
|
||||||
list.append(li);
|
|
||||||
}
|
|
||||||
wrap.append(list);
|
|
||||||
return wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Bulk "mark done" row for the todos flyout: select all / select none
|
/** Bulk "mark done" row for the todos flyout: select all / select none
|
||||||
* + a mark-done button, disabled until at least one row is checked.
|
* + a mark-done button, disabled until at least one row is checked.
|
||||||
* POSTs the checked ids (comma-joined into one field, same shape as
|
* POSTs the checked ids (comma-joined into one field, same shape as
|
||||||
|
|
@ -1008,8 +945,9 @@ window.marked = marked;
|
||||||
// Inline "answer as operator" form for a question loose-end. POSTs to
|
// Inline "answer as operator" form for a question loose-end. POSTs to
|
||||||
// the host dashboard (core backend), never this agent's socket — the
|
// the host dashboard (core backend), never this agent's socket — the
|
||||||
// core is the only place that can stamp `operator` as the answerer.
|
// core is the only place that can stamp `operator` as the answerer.
|
||||||
// Used by both the loose-ends side panel (`buildLooseEndsList`) and
|
// Only live call site today is the in-stream ask-row binder
|
||||||
// the in-stream ask-row binder (`reconcileAskBinds`).
|
// (`reconcileAskBinds`) — see its doc comment above for why that path
|
||||||
|
// is currently non-functional.
|
||||||
function buildAnswerForm(id) {
|
function buildAnswerForm(id) {
|
||||||
const wrap = el('div', { class: 'answer-form' });
|
const wrap = el('div', { class: 'answer-form' });
|
||||||
const ta = el('textarea', { rows: '2', placeholder: 'answer as operator…' });
|
const ta = el('textarea', { rows: '2', placeholder: 'answer as operator…' });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue