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