Inbox alerts (how an agent gets your attention) (part 4)
How an alert card behaves once it is in your inbox — where it sits in the list, the badges it carries, and what acting on one does to the rest.
What it is
The continuation of part 1, covering the card inside the inbox rather than the card itself.
Where to find it
This is the behavior of the shared inbox list, so it applies wherever an alert appears: the inbox, a project Needs You section, or the Alerts screen.
How it behaves
Inbox behaviour
projectId decides WHICH part of the inbox a card lands in, so it decides whether the operator sees it. It has three states, and null is not the same as leaving it out: a real id files the card under that project; an explicit null raises it GLOBALLY, in the shared Alerts pile the operator scans; omitting it derives the raising session's project. So a card raised from inside a project-bound session and left unscoped lands in that project's own group, mixed among its sessions — which is how a genuine question for the human can sit unread. An agent asking for a decision should send projectId: null explicitly. The card keeps its source session either way, so being global costs no provenance. Honest limit: the Alerts pile is a pile, not an alarm — global makes the ask reachable, not guaranteed to be read.
Alerts surface as amber-dot rows in the unified inbox (integration alert, dotColor: 'bg-amber-400'). When an alert has a projectId that is a real user project, it also surfaces in that project's Needs You attention section (mirroring how doc-token-alert and cron-approval rows work). There it orders by its timestamp, interleaved with the session rows — it is NOT appended after them, so an old alert sits where its age belongs instead of being pinned to the bottom. Every Needs-You row (session or notice) orders by the same key via the one orderNeedsYouItems rule, and the rendered sidebar order equals the keyboard/swipe/next-selection order; see project-needs-you-attention-contract.md (render-order-equals-nav-order / advance-can-land-on-a-notice) and the ordering-parity axis in frontend-inbox-row-contract.md.
Tasks reminders get their own "Tasks" section. A Tasks reminder — raised when a task's due date arrives or a snoozed task comes back — is an ordinary agent alert, but instead of landing in the shared Alerts pile it surfaces in its own "Tasks" inbox section (its own header + task-list icon), so task reminders don't get mixed in with system alerts. Its rows behave like any alert (archive, snooze, its own Dismiss all), and clicking the section header opens the Tasks view. Tasks is a Lab / in-development feature, so this section only appears when Tasks is enabled.
Team Chat messages get their own "Team Chat" section. A team-chat new-message notice is an ordinary agent alert, but instead of landing in the shared Alerts pile it surfaces in its own "Team Chat" inbox section (its own header + chat icon) — the SAME section the connection-request rows already use, so messages and connection requests sit together. Its message rows behave like any alert (archive, snooze, their own Dismiss all — which clears message notices only, never the connection-request approvals in the same section), and clicking the section header opens the Team Chat panel. Team Chat is an in-development feature, so this section only appears when Team Chat is enabled.
A built-in channel's notices file under that channel's own inbox section. The notices raised by the built-in channel pollers — Calendar ("starting soon" reminders, "calendar updated" changes, and a "calendar event alerts stopped" health notice), Gmail ("Gmail is disconnected"), Drive ("Google Drive needs reconnecting"), RSS ("an RSS feed stopped updating"), and Webhooks ("a webhook source is failing") — pass a virtualProjectPath naming their channel's virtual project (e.g. __rss__ / __calendar__). So they group under that channel's inbox section, merging with that channel's other rows (an RSS broken-feed notice sits alongside your RSS feeds), instead of the generic Alerts bucket. resolveProjectId honours virtualProjectPath via the pure isChannelVirtualProject membership check and stores the sentinel folder_path verbatim; it takes precedence over projectId/session resolution and, touching no database, can never fail and swallow the notice. The one-click Reconnect buttons and self-clear-on-recovery behaviour are unchanged (they key on dedupKey, not the project). Calendar and Drive get their inbox-section name + icon + colour from src/renderer/src/features/dashboard/inbox-helpers.ts (they are seeded from CHANNEL_VIRTUAL_PROJECTS, not the integration registry). Calendar goes one step further: the Calendar hub's Calendar tab leads with a "For You" band showing those same rows — the calendar's own live inbox items (starting-soon reminders, event added/changed notices, the day's agenda card, and the alerts-stopped health notice) — read from the one shared inbox list, so opening or dismissing a row there does exactly what it does in the inbox and the row clears on both surfaces together. The band renders nothing when the calendar has nothing waiting, and it hides whenever the Calendar inbox toggle (inboxShowCalendar) hides those rows.
Omniscio's own notices file under your Omniscio project. A notice about Omniscio's own machinery — a Dev Pipeline card (a branch that would not land, a worktree that would not delete, a gate that died, a test baseline that regressed, a workspace that needs you) or an app-internal one (the disk guardian, a background-metadata storm, a cloud pointer that stranded, the folded "background machinery" rollup) — now files under your Omniscio project (the checkout Omniscio runs from, e.g. a row named Omniscio) instead of the generic Alerts pile. The classifier is the dedup key: an exact list plus prefix families for the build → gate → land → workspace lifecycle, unioned with the existing about-Omniscio class. It is the LAST step of the grouping fallback, so anything already carrying a channel, an explicit project or a raising session's project is untouched — a real project always wins. On a machine with no Omniscio checkout the step returns nothing and these notices stay in the pile exactly as before.
This changes where you find those cards, and one thing about how you clear them: a card filed under a project appears in that project's Needs You attention row and is dismissed one at a time — project-scoped alerts deliberately have no "Dismiss all" (that button lives on the unscoped buckets). What did NOT change: the alerts themselves, their dedup keys, their one-click actions, their re-raise cadence and their archive behaviour are all identical. Cards that were already in your pile move on their next re-raise, so the pile empties progressively rather than all at once.
Where a card appears also follows WHO raised it (owner ruling, 2026-09-30). A card Omniscio's own monitors raise is a real alert and appears everywhere an alert can: the inbox, its project's Needs You when it names one, and the Agent Alerts screen. A card an agent session raises — a crew lead's routed update, any session's POST /alert card, a one-line wait-check card, an email-arrival notice — is a message, and the Agent Alerts screen deliberately does not list it; it keeps its home in the inbox (and in its project's hub) so the conversation around it stays where it belongs. The split is the same predicate that paints the row's colour, and the screen applies it to its dismissed section too, so clearing a card one click later cannot put it back. See Inbox alerts — the lifecycle.
Standard inbox actions apply:
- Archive (
E/ middle-click / mobile X / the detail-viewer Archive button) — removes the row from the inbox and optimistically removes it from the store slice; rolls back on IPC failure. Ctrl+Z undoes it: the store'sarchiveInboxItemarms an undo (once the archive succeeds) that restores the alert viaALERT_UNARCHIVE, so undo works from every archive surface, not just the keyboard. The restore is keyed on the card's IDENTITY (dedup_key), not its row: if the condition re-fired between the dismiss and the undo, a fresh card for it is already in the inbox, so the undo reports success and leaves that live card alone rather than trying to restore a second copy of the same alert. - Snooze — routes through the universal
inbox_snoozestable (kind'alert'); the selector gates onisInboxItemSnoozed('alert', id). The server list queries do NOT filter snoozes, so every alert list applies that gate itself — the inbox inalertInboxSelectItems, the Agent Alerts screen inhubVisibleAlerts, and the sidebar badge by counting the filtered list. A snoozed alert therefore disappears from all three until it wakes; before this, the Agent Alerts screen alone kept showing it, which made snoozing look like it had done nothing. - Team-chat notices are the one kind the Agent Alerts screen does not list (owner, 2026-09-30 — a DM notice sat there as a bare sender's name among system alerts).
hubVisibleAlertsdrops all four notice prefixes: a chat message is a conversation, not a standing condition an agent needs looked at, and it is the one kind with no actions on it. Nothing is lost — a notice keeps the inbox (alertInboxSelectItemsis untouched), the channel's unread badge, the OS toast, the phone push and the read-clears-it rule. Both gates live in the same selector, so the screen, the sidebar badge and the nav list can never disagree. - Dismiss all — a section with 2+ alerts shows "Dismiss all N" in its header, on desktop and phone — the alerts twin of the Approvals "Approve all" / "Reject all" dropdown, which covers every pending approval (cron, automation, CLI Pending, recipe, recipe-authoring) bar a few CLI kinds decided one at a time. One confirm archives the section through the per-row Archive, the open row last so the cursor moves once. Hidden below 2; on the unscoped Alerts bucket and the Tasks reminders section, each clearing only its own. A project-scoped alert in its Needs You is dismissed singly.
- Enter runs the card's one primary action — while you are reading a card, a bare
Enterfires its primary button (Update nowon a tool-update card,Restart now,Resume sessions, …) exactly as clicking it does, so an action that asks you to confirm first still opens that confirmation. It fires only while the card is what holds the keyboard — if focus is on the inbox list, in a field, on a button, or anywhere else,Enterkeeps whatever meaning it has there. A heldEnterdoes not repeat the action, and no modifier combination counts. - Auto-recovery — when you archive an alert (or the one you're reading is removed out from under you — archived from another session, the CLI, a dedup collapse, or a background sweep), the inbox advances to the next item in the list, whatever it is — the next notification or the next session that needs you — rather than stranding you. There is no per-integration filter in that advance: the inbox is one ordered list and "next" is simply the next row (the sidebar order is the nav order). On a heavily loaded machine a worker session can briefly still look like it needs you just after it has gone back to running; if the cursor lands on such a stale session for a moment, the general auto-eject + idle auto-select move you straight on, so the old "blink → empty inbox" can't strand you. That advance only fires for an alert you were already viewing when the list last refreshed — so opening an alert never bounces you back to the list, even if you click it (e.g. going from a session row to a just-arrived alert) while a background refresh is mid-flight: at worst you briefly see Loading alert… until it appears. A refresh that began before your click took its snapshot before the alert was selected, so it can't be trusted to declare the alert "gone"; a later refresh resolves it. The reading pane (
AlertInboxViewer) also renders nothing — never a stale "Select an alert to view" message — when no alert is selected, so the panel's close transition can't flash a misleading empty pane (the inbox layout owns the "Select an item" / "All clear" empty state).
Related
- Inbox alerts (part 1) — what an alert is and how you answer one.
- Inbox alerts — authoring them — the developer-facing route an agent posts one through.
- Inbox alerts — the lifecycle — what happens on the Alerts screen and over an alert's life.
Last verified 2026-10-03