Inbox overview
The Inbox is the one list of everything waiting on you, gathered from every project and every channel. It sits at the top of the sidebar, and clearing an item there is the same as clearing it where it lives.
What it is
The Inbox is Omniscio's cross-project triage view — one place that surfaces every item across every project that needs your attention right now, regardless of which channel it came from. It sits at the very top of the projects sidebar with an amber inbox icon and an amber count badge showing how many items are waiting. Click it (or trigger your inbox keyboard shortcut) and the middle column switches from "this project's sessions" to "everything that needs you, grouped by source."
The Inbox is not a separate folder, mailbox, or queue — it's a live aggregation. Every item in it also exists in its home location (a session in its project, an SMS thread in the SMS virtual project, a cron approval in the queue), so triaging from the Inbox is the same as triaging from each home. The Inbox just collapses them into one list so you don't have to walk every project to find the four sessions that need replies, the two SMS threads with new messages, and the cron job pending approval.
Inbox attention scoped to a real project also appears in that project's "Needs You" section. Most inbox items are either session attention (already shown in their project) or virtual-source items (SMS, Briefings, cron) with no real-project home. But some non-session items are tied to a real project — a repo's PR-merge-queue nudge, a project's context-size (doc-token) alert, a project-scoped cron-job approval, a Nighty-Tidy summary, an agent alert (for example the Auto-lander's "branches landed" notice), or a held Bug-Intake "Review" card. Those surface in both places: the global Inbox and the owning project's Needs You sidebar section (with the project's amber attention badge counting them), on desktop and mobile — so opening the project shows what needs you there, not only in the Inbox. These notice rows behave exactly like the session rows beside them: Tab / J / K (and mobile tap) reach them in the order they're shown, and clicking or keyboard-activating one opens its detail in place in the project's main panel — you stay in the project, and closing it returns you there, not to the global Inbox. One shared membership source drives the row, the amber badge, and the project-view nav so they cannot drift; see project-needs-you-attention-contract.md.
The Inbox surfaces one kind of item per entry in the INBOX_SOURCE_INTEGRATIONS registry (inbox-sources.ts; INBOX_SOURCE_COUNT derives from it, so this page does not quote a number that would drift). The main ones are: sessions that delivered something for you (a finished answer, a question, a plan to approve, a permission request, a mission, a merge-conflict handback), today's Briefings summary if it hasn't been read, unread SMS conversations, unread Telegram conversations, pending cron-job approvals, pending automation-rule approvals, CLI Pending Actions queued by external AIs (settings PATCHes, session lifecycle, project deletes, project bug-intake toggles, away-mode rule changes — see cli-pending-actions.md), recipe approvals, recipe authoring approvals, and recipe runs that failed, stalled or stopped incomplete. Sessions that hit an error (API or auth), that you stopped, that gave up recovering ("Recovery failed"), or that are waiting out a rate limit are not in the Inbox — they wait in their project's Interrupted section and do not chime (errors and stops since 2026-08-21, rate-limit waits since 2026-08-14). PRD Stack plugin attention items render as a separate group below the unified list.
While items remain, the Inbox keeps one selected for you so you're never staring at a stale empty pane: finishing the current item drops you into the next, and an item that arrives while you're sitting on "All clear" opens on its own — a session, a text, an alert. Approvals are the deliberate exception: an approval that arrives while you're idle in the inbox waits as a row instead of opening on its own, so a background agent's approval can never take over your screen — you open it when you're ready. (Opening the Inbox yourself reopens the item you were last viewing if it still exists, otherwise the newest item — approval or not (groups list oldest-first, so "newest" is the bottom row of a group) — and finishing the item before an approval still advances onto it — only a from-behind arrival is held back. See inbox-navigation-contract.md approvals-no-passive-auto-open.) If items remain but none is open (auto-select hasn't landed one yet), the reading pane prompts "Select an item" rather than falsely claiming all-clear. The "All clear" panel — a green inbox icon, the heading "All clear", and the subtext "No sessions need your attention right now" — appears only when the Inbox is genuinely empty (the count badge is 0). That's the goal state — empty inbox means triage is done.
Where to find it
At the very top of the projects sidebar, marked with an amber inbox icon and an amber badge counting what is waiting. Clicking it — or pressing your inbox keyboard shortcut — switches the middle column to the grouped list of everything needing you.
How it behaves
How to use it
- Open the Inbox. Click the amber Inbox entry at the top of the projects sidebar (above the divider that separates Inbox from your pinned and grouped projects). The icon is a mailbox glyph; the badge to its right shows the total count of pending items across every source. The middle column switches from per-project sessions to the unified inbox list, with
aria-label="Inbox, N needing attention"set for screen-reader callout. - Read the grouped list. Items are grouped by their source project — a real project's group header shows the project's color bar and icon plus the project name; virtual sources show a Lucide icon (alarm clock for CRON JOBS, smartphone for SMS, newspaper for BRIEFINGS) plus a tinted label. Every group header — real project or virtual source alike — also carries a parenthesised count of how many items sit in that group (for example Omniscio (12)), on desktop and mobile. Inside each group, items are sorted oldest-first so the longest-waiting items are easy to spot. Group order itself is frozen so the list doesn't shuffle under your finger as new items arrive — a new group is appended to the end rather than splicing into the middle — and it stays stable across visits, changing only as you clear items or new ones arrive (it resets when you restart the app). Collapse groups to scan by product. A Collapse all / Expand all toggle folds the whole Inbox down to a list of product names, and every group header carries a small arrow (chevron) that folds that one group down to just its name and count — so you can shrink the Inbox, then open one product to see only its items. On both desktop and mobile the toggle sits in the Inbox header row itself (the same row as the "Inbox" title): on desktop it's an icon-only button beside the source-filter icon, with its "Collapse all"/"Expand all" label shown in the hover tooltip; on mobile it's the labeled toggle. On desktop it's hidden while Focus Mode is armed (the list is behind the focus overlay, so there's nothing to fold). Collapsing only hides the rows; it never changes what's in the Inbox or the order you navigate. On desktop the group holding the item you're currently reading stays open so keyboard navigation never lands on a hidden row; on mobile an explicit fold fully folds (your place is remembered and reopens the moment you tap or swipe). Your collapsed/expanded choices are remembered across restarts and on your phone. Tapping a group name still jumps into that project as before — the arrow is the dedicated fold control.
- Click a row to act on it. A session row opens that session in the right pane so you can read the question, choose a plan option, grant or deny a permission, or reply. An SMS row opens the conversation viewer. A Briefings row opens today's briefing. An approval row (cron, automation, CLI Pending, recipe) opens an approval modal showing the action kind, the target, the JSON payload, and Approve / Reject buttons. All five approval kinds sit together in one "Approvals" section, and approving or rejecting one carries you to the next pending approval — never off into a project's sessions — until the queue is clear. Closing an approval (the X) without deciding doesn't lose it — the approval stays waiting in the Inbox and Omniscio drops you onto the next pending item so you can keep moving through the queue (close the last one and you land back on the list). The amber row colour signals "decision required" so you can scan and prioritize.
- Triage with keyboard. J / K walk forward and backward through the unified list across groups (the same shortcuts you use inside a project's session list). Shift+J / Shift+K extend a multi-selection so you can bulk-archive or bulk-snooze. Plain arrows clear any stale multi-selection so a follow-up bulk action operates on what you can see, not on a selection from twenty rows ago. However you move the selection — walking with J / K, or clearing items so the cursor jumps to the next one — the Inbox keeps the selected row in view: if it would land off-screen (for example above the fold after you clear an item near the top), the list scrolls just enough to reveal it, so you never lose your place. See inbox-navigation-contract.md
active-row-scrolled-into-view. - Items leave the Inbox automatically as you handle them. Reply to a session and its row disappears (the session is no longer in attention status). Read the briefing and its row clears. Approve a CLI pending action and the row vanishes. An SMS thread leaves the Inbox when you dispose of it — open it, tap Mark as read on the thread, archive it, or reply (in Omniscio, or straight from your phone, which Omniscio catches on its next background sync within ~30 seconds). Just opening a thread also takes it out of the Inbox — there is no "peek" — though the app moving its own cursor onto a thread does not count as opening it (see sms-inbox-attention-contract.md). Snoozing or archiving from inside a session — or right-clicking the Briefings row and choosing Archive or Snooze, or pressing H on it — also drops it out of the Inbox immediately. The badge count on the sidebar updates live via push events — no refresh. And a restart never leaves you a row you can't act on: an Allow/Deny tool-permission request lives inside the running agent's own process, so it cannot survive Omniscio closing. On the next launch that session moves out of the Inbox into Interrupted and — with auto-resume on — restarts and asks again with a live Allow/Deny card, instead of sitting in the Inbox forever with nothing to click. Every other kind of "needs you" (a question, a plan to approve, a finished turn) is written into the transcript, so it survives a restart and correctly stays in the Inbox. See permission-prompt-lifecycle-contract.md P17.
- Mobile uses the same view. On a mobile-width viewport (the phone web UI or a narrow window) the Inbox is just another row in the projects panel; tap it and the sessions panel slides in showing the same unified, grouped list. Swipe-left on a row archives the underlying session with an Undo toast. Clearing the item you're viewing slides you straight into the next one — or back to the list when the inbox empties — exactly as it does on desktop (one shared reconcile drives every way you clear, so a phone never gets dropped to the list instead of advancing; see inbox-navigation-contract.md
mobile-panel-follows-advance). The Inbox is also the default landing tab on mobile because triage is the primary phone use case — see mobile-remote-access.md.
Scope and limits
- Desktop only — no CLI control surface. The unified channels layer that feeds this inbox — connecting a channel, applying its config, sending a reply, marking an item read, or deleting an item (the
CHANNELS_CONNECT/CHANNELS_CONFIG_APPLY/CHANNELS_SEND/CHANNELS_MARK_READ/CHANNELS_DELETE_ITEMactions across the Slack, RSS, webhook, calendar, Drive, and Sheets adapters) — is driven through the desktop/mobile UI only. There is no localhost CLI route (the127.0.0.1:19519control server) that manages channels or sends on their behalf; these mutating actions are the classifiedmessaging-clientdesktop-only exemption in the CLI-parity manifest.
For agents
How it works
The Inbox virtual project is identified by the constant INBOX_ID = '__inbox__' exported from session-store.ts. Selecting the Inbox sets activeProjectId === INBOX_ID, which a derived isInboxSelected flag in Dashboard.tsx feeds into SessionsSidebar.tsx so the middle column flips into inbox-render mode. The sidebar entry itself is rendered in ProjectsSidebar.tsx (look for data-tour="inbox-button") — the amber count badge reads from useInboxAttentionCount().
Item aggregation lives in stores/inbox-items.ts. The useInboxItems() hook calls one adapter per source in the registry (the core ones are useSessionInboxItems, useDigestInboxItems, useSmsInboxItems, useTelegramInboxItems, useCronApprovalItems, useAutomationApprovalItems, useCliPendingApprovalItems, useRecipeApprovalItems, useRecipeAuthoringApprovalItems, useRecipeInboxItems, plus an adapter for each remaining registry entry) and concatenates them into a single UnifiedInboxItem[] array ({ integration, id, title, timestamp, projectId, inboxGroupId?, dotColor?, payloadRef }). computeUnifiedInboxGroups() groups by item.inboxGroupId ?? item.projectId, sorts items inside each group by ascending timestamp, and sorts the groups themselves by a frozen-order map so ordering doesn't shift mid-triage. The five approval stores (cron, automation, cli-pending, recipe, recipe-authoring) set inboxGroupId to the shared "Approvals" group (via the one approval-inbox-group.ts chokepoint) so every approve/reject row sits in ONE contiguous queue — resolving one advances to the next approval, never an adjacent session — while keeping its projectId for project-view Needs-You membership + sidebar badges (a cron-failure NOTICE and every non-approval row leave inboxGroupId unset and still group by projectId). Before this split, projectId did double duty (grouping AND project membership), so an approval surfaced in a project scattered out of the Approvals group and a resolve walked onto the adjacent session — see inbox-navigation-contract.md approvals-are-one-inbox-group. computeUnifiedInboxGroups is the SOLE author of that map — the sessions-only computeOrderedInboxGroups only READS it (seeding a sessions-only subset would evict virtual groups like the __alerts__ pile, the 2026-07 "alerts jump to the bottom" reflow). The map is an in-memory singleton that persists across inbox visits (setActiveProject no longer clears it — Option B) and resets on app restart. The Ask Omniscio visibility gate (filterVisibleInboxItems) hides Ask Omniscio sessions from the inbox when that feature toggle is off.
Group header rendering is centralized in inbox-helpers.ts: getIntegrationGroupName, getGroupColor, getIntegrationGroupIcon, and getIntegrationGroupIconColor map a group id (which is itself a project id, real or virtual) to its display label, vertical color bar, and icon. The header component itself (SidebarInboxContent.tsx on desktop, MobileInboxGroups.tsx on mobile) also renders a parenthesised item count next to the name — group.items.length, the exact per-group array the rows below map over, so the count can never disagree with the list. Each item row renders through UnifiedInboxRow.tsx — the dispatch shim that decides which navigation action runs when the row is clicked. The list-side "All clear" panel is wrapped in EmptyState and rendered inline in SessionsSidebar.tsx / MobileSessionsList.tsx when that rendered list is empty. The right-hand reading pane decides its inbox empty state separately via inboxMainPaneEmptyKind (inbox-main-pane.ts): it shows the "All clear" celebration only when useInboxIsEmpty() is true, and "Select an item" when items exist but none is open — never gating "All clear" on "nothing is selected" (the 2026-06-07 badge=1 / pane-says-empty bug). The badge count (useInboxAttentionCount), the rendered list (useInboxItems), and that empty signal all derive from one useVisibleInboxItems() pipeline so they can't drift; see inbox-count-list-parity-contract.md.
Group collapse/expand is a render-only accordion layered on top of the same grouping — it changes what's drawn, never the data. A persisted collapsedInboxGroups setting (an array of group ids FLIPPED FROM THEIR DEFAULT: for an ordinary group presence means folded; for the one default-folded group — the System section of Omniscio's own notices, which also always sorts last — presence means the user OPENED it, a mark that survives the section emptying so it stays open until folded again) is read through the shared inbox-collapse.ts hook, which both SidebarInboxContent.tsx (desktop) and MobileSessionsList.tsx / MobileInboxGroups.tsx (mobile) use to draw the per-group chevron, plus the Collapse-all / Expand-all control (InboxCollapseControls.tsx) — which lives in the Inbox header row on both platforms, not the scroll body: on desktop an icon-only button (InboxHeaderCollapseButton.tsx) rendered by SidebarHeader.tsx beside the source-filter (hidden while Focus Mode is armed), and on mobile the labeled toggle via MobileInboxCollapseToggle.tsx (rendered by MobileBreadcrumb.tsx). Collapsing a group only skips RENDERING its rows — it never touches the unified flatList, the frozen group order, or the nav resolver — so navigation still walks every item. Both the desktop and mobile inboxes opt OUT of the active-group auto-reveal (activeGroupId = null), so an explicit collapse folds every group — including the one holding the item you currently have open. Navigation is collapse-blind (it walks the unified flat list, not just the rendered rows), and opening an item shows it in the reading pane, so a folded active group can't strand you — the open item still shows on the right, and "sidebar order IS the navigation order" still holds (folding hides rows, never reorders them). WITH the auto-reveal, "Collapse all" (and a group's own chevron) couldn't fold the active item's section: on mobile a single-group inbox looked dead (2026-07-19), and on desktop the ALERTS section stayed open while everything else folded (2026-07-20). State persists silently through saveCollapsePreference (no toast) and rides the mobile bootstrap (collapsedInboxGroups is in WEB_BOOTSTRAP_SETTING_KEYS) so a phone remembers it across reloads. The render-only invariant and the no-active-reveal rule are locked by inbox-collapse.test.ts, the desktop fold case in SidebarInboxContent.test.tsx, and the mobile fold case in MobileSessionsList.test.tsx; see also inbox-navigation-contract.md mobile-collapse-no-active-reveal.
Entering the Inbox is handled by selectInboxOnEntry in session-store.ts: it reopens the item you were last viewing if it still exists, otherwise selects the newest item (approval or not). Auto-selection while you are already in the Inbox runs through a single chokepoint, runInboxAutoSelectIfIdle in session-navigation.ts: it only fills an empty selection — picking the newest visible item when there is no recovery anchor (groups sort oldest-first, so "newest" is the bottom row of a group, never the top), but advancing to the sidebar-ADJACENT row (never the newest) when the selection went empty because an action removed the row you were on (an approval resolved, a session ejected under you). It remembers the row you were last on — a recovery anchor — so acting on a row moves you to the neighbouring item like every other inbox action, instead of teleporting you to the newest item at the very bottom of the list (the 2026-07-04 "approving jumps to the very bottom" regression, structurally un-regressable now). See inbox-navigation-contract.md idle-auto-select-recovers-adjacent. Event-driven gates call it — the session fetch-race, a visibility-recovery subscriber, and the close-advance after you finish an approval (entering the inbox is selectInboxOnEntry's job, above). But those only observe the session + AI-manager stores, while the inbox aggregates every source in INBOX_SOURCE_INTEGRATIONS, so the first item to land from any other source (an SMS, an approval, an alert) could slip through every gate. A render-driven backstop — useInboxAutoSelectBackstop (useInboxAutoSelectBackstop.ts), mounted in Dashboard.tsx — closes that gap by re-running the same chokepoint against the EXACT useInboxItems() list the inbox renders (which subscribes to all sources), so the first item to arrive is selected regardless of which store changed. One deliberate exception protects your attention (2026-07-23): when the chokepoint fires passively — a new item landing while you sit idle, or a fetch / visibility recovery — it skips approvals, so a background agent's approval waits as a row instead of auto-opening its full-screen pane over you (the "an agent forced my attention" fix). Deliberately opening the Inbox still reopens your last item or the newest one (approval or not), and the active close-advance after you finish an approval still flows to the next one; J / K / Tab always reach approvals too. See inbox-navigation-contract.md approvals-no-passive-auto-open. The same backstop also recovers a stranded selection (2026-07-20, widened 2026-08-27): a row can leave the inbox without you acting on it — a derived card like the weekly-suggestions card is pinned to one specific summary, so a newer summary silently re-targets it and the open row leaves the list; a snooze fires; another device or agent resolves the item out from under you. The selection then points at a row that no longer exists, and that is worse than a stranded reading pane: auto-select only fires when NOTHING is selected, so one stale selection wedges the whole inbox — it reads as empty, and every item that arrives afterwards is left unselected (the reported "the inbox was empty, something appeared, and it was NOT auto-selected"). The backstop notices the selected row is gone from the live list and advances you to the next live row. Originally this covered only detail cards, which left six kinds — SMS threads, the Briefings digest, the weekly summary, drip items, scheduled messages and the PR merge queue — with nothing to recover them; it now covers every selection kind except three that another mechanism deliberately owns: a session is cleared by the attention-membership eject instead, the mobile new-session composer has no row by design, and an approval is left to its own confirm-before-close (an approval row can blink out of the pending list during a reload, and treating that blink as gone was itself a bug). See inbox-navigation-contract.md orphan-selection-reconcile. Conversely, when the active session stops needing you on its own — a backend transition, not your reply — updateSessionStatus ejects it (clears activeSessionId) so the next item can take its place rather than the panel sitting on "All clear" with the row unread. Needs You is a strict membership surface: the eject fires for any status outside ATTENTION_STATUSES, so a session that goes back to running / starting / ready / waiting leaves the Inbox exactly like one that ends or is archived — there is no alive/reviving exemption. An earlier "stay on that session" rule (2026-06-23, broadened 2026-06-27) kept a self-reviving session selected here; it was removed because it left running sessions sitting in Needs You after they had stopped needing you. You can still watch a running session — it lives under its project, which is unaffected. isActiveSessionInboxBlocked applies the same membership rule, so the reading pane and the list can never disagree about it. The eject is gated to backend transitions only (it never fires for your own replies / nudge / keep-waiting / restart, which navigate off the sender first — restart joined this family on 2026-06-30, so restarting the session you're viewing carries you to the next one instead of stranding you on it). The full gate set, that race-avoidance rule, and the all-source render backstop are locked in inbox-navigation-contract.md (active-session-auto-eject and empty-inbox-auto-select-backstop). One refinement on top of "select the newest" (2026-06-19): when you return to the Inbox after a detour (Settings, another project, a view switch), selectInboxOnEntry re-selects the exact item you were last looking at rather than jumping to the newest — it remembers your spot in memory and validates the item still exists, falling back to the newest only if that item is gone. See inbox-navigation-contract.md inbox-entry-detour-return. A second refinement (2026-07-01) governs an explicit close: closing an approval pane (the X) does NOT resolve the approval (unlike Approve/Reject, which flip the row terminal), so the row stays pending and — being the newest attention item — the auto-select would immediately re-grab it, making the X appear dead ("the same approval reopens"). closeApprovalPaneAdvancing records the just-closed approval as a one-row exclusion the chokepoint skips for that idle window, so a close ADVANCES to the next-newest item (or back to the list on mobile when nothing remains) and self-clears the moment something is selected or you leave the inbox. See inbox-navigation-contract.md explicit-close-advances-to-next.
Related
- snooze-an-inbox-item.md — every row in the Inbox (every inbox kind) is snoozable via right-click → Snooze; one unified Snooze Palette, one expiry sweep, one gate helper, counts update live via
INBOX_SNOOZE_CHANGED - cli-pending-actions.md — how external-AI requests land in the inbox as approve/reject rows
- bulk-select-sidebar.md — Shift+J/K multi-select and bulk archive/snooze across inbox groups
- snooze-a-session.md — snoozing a session removes it from the inbox until the snooze expires
- archive-a-session.md — archiving from the inbox kills the CLI process and drops the row
- mobile-remote-access.md — the mobile/phone view defaults to the Inbox as the landing tab
- daily-digest.md — the Briefings (formerly Daily Digest) row that surfaces in the Inbox once a day
Last verified 2026-10-03