SIGN IN SIGN UP

feat: an attention inbox for refusals and stalls (#255)

A boundary refusal or a stalled run was recorded and then waited for somebody
to happen to look — at the right channel, or at the audit page only an
administrator has. The trail knew; nobody was told.

The inbox is a view over the trail, not a second record of it. Refusals
(computer.action_refused, mcp.call_rejected) and stalls (agent.stream_stalled)
are already written transactionally by the gateway and the stall guard, so
deriving the inbox from those rows means it cannot miss one: there is no dual
write to drift, and nothing new runs on the action path. The only state it owns
is the resolution — who marked a row handled, and when — in a table beside the
append-only trail rather than in it, ids by value with no foreign keys for the
trail's own documented reason.

GET /api/attention composes recent rows minus resolutions, then scopes per item
by the same canUseBot the roster and the computer use; an administrator sees
everything the way they see every Bot. Not under /api/admin: the audit page is
the administrator looking back, the inbox is the working person being told now.

POST /api/attention/:eventId/resolve marks one handled for everyone, with
attribution. First writer wins by unique index rather than check-then-write,
and the second presser is read back who got there first. Only a row of the
three attention kinds resolves; anything else answers the same 404, so the
endpoint cannot be used to probe what the trail holds.

Which Bot a row is about is not where it looks: a tool rejection's target is
the TOOL — targetType "mcp_tool", targetId the ref — and its Bot travels only
in the payload. Reading targetId unconditionally called a refusal's Bot
"google-drive/search_files", which canUseBot correctly denies, which hid every
tool rejection from exactly the person it was for. botOf reads targetId only
for computer and agent rows, and a row that cannot name its Bot is dropped
rather than shown to everybody.

In the app: an Attention page listing what is open with Resolve on each row,
and a sidebar entry with a count badge drawn only when nonzero.

Co-authored-by: David McKay <davidmckayv@users.noreply.github.com>
A
anygivenfriday committed
e8aa34451f73ef2719c22cc557be369d9ea70afb
Parent: 3c1a067
Committed by GitHub <noreply@github.com> on 8/26/2026, 4:29:24 PM