agent/terminal: inline answer form under ask → operator rows (#666)

When an agent calls mcp__hyperhive__ask with to==operator (the
default), the rich tool-use renderer now mounts an empty
ask-answer-inline-slot inside the expanded ask row and enqueues a
loose-ends refresh. A new reconcileAskBinds() walks waiting slots
on every loose-ends update, matches each against pending
operator-bound questions by question text, and injects the same
inline answer form the side panel uses (buildAnswerForm → POST to
the host dashboard answer-question endpoint). When a question
resolves, the form gets replaced by a struck-through [answered ✓]
tag so the scrollback reflects the closed state.

Lets the operator respond to agent questions inline in the live
terminal without context-switching to the loose-ends side panel or
the dashboard Y3R C4LL tab.

Refreshes loose-ends both on tool_use render (best-effort, may
miss the question before the broker persists it) and on the
matching tool_result (the right moment — MCP has just returned the
assigned id). Slots are pruned on /clear and defensively filtered
for isConnected on each reconcile.

closes #666
This commit is contained in:
iris 2026-05-30 21:40:04 +02:00 committed by Mara
commit e056e82d6d
3 changed files with 194 additions and 1 deletions

View file

@ -552,6 +552,67 @@ pre.diff {
.agent-inbox .answer-form button:disabled { opacity: 0.5; cursor: default; }
.agent-inbox .answer-status { color: var(--muted); align-self: center; }
/* #666: inline answer slot mounted under each `ask operator` row
in the live terminal stream. Mirrors the side-panel `.answer-form`
look-and-feel same textarea, same send button so the operator
doesn't have to context-switch between "answering in the panel" and
"answering inline". Empty slot collapses to nothing (no margin) so
pre-bind rows stay tidy; populated slot gets a thin top divider to
separate the question body from the form. Resolved tag (the
struck-through `[answered ]`) replaces the form once the
question's been answered. */
.live .ask-answer-inline-slot:empty { display: none; }
.live .ask-answer-inline-slot {
margin-top: 0.5em;
padding-top: 0.5em;
border-top: 1px dashed var(--border);
}
.live .ask-answer-inline-slot .answer-form {
display: flex;
gap: 0.4em;
align-items: flex-start;
}
.live .ask-answer-inline-slot .answer-form textarea {
flex: 1;
font-family: inherit;
font-size: inherit;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 3px;
padding: 0.3em;
resize: vertical;
}
.live .ask-answer-inline-slot .answer-form button {
font-family: inherit;
font-size: inherit;
background: var(--bg-elev);
color: var(--fg);
border: 1px solid var(--border);
border-radius: 3px;
padding: 0.3em 0.7em;
cursor: pointer;
white-space: nowrap;
}
.live .ask-answer-inline-slot .answer-form button:hover:not(:disabled) {
border-color: var(--purple);
color: var(--purple);
}
.live .ask-answer-inline-slot .answer-form button:disabled {
opacity: 0.5;
cursor: default;
}
.live .ask-answer-inline-slot .answer-status {
color: var(--muted);
align-self: center;
}
.live .ask-answer-inline-slot .ask-answered-tag {
color: var(--muted);
font-style: italic;
text-decoration: line-through;
text-decoration-color: var(--muted);
}
/* #559: "mark all read" header row sits above the recent-messages
list in the inbox side-panel flyout. Same look as the answer-form
button (mauve hover, bg-elev background) so they read as part of