Expand description
Notification Router — the side-effecting shell around policy.rs
(SPEC_OS_NOTIFICATIONS_SYSTEM_2026_09_24.md §3, §5).
Owns: settings resolution, agent-name lookup + sanitization, body redaction (§9), publishing on the mps broker, a 500 ms tick task for debounce release, and the single pending “activate on next window” slot for a click that arrived while no window was open.
One Router per broker (i.e. per AppState), kept in a small registry
keyed by the broker’s identity rather than as an AppState field: it is
created lazily by the first WS connection and outlives any one connection.
Keying by broker (not a single global) means a second AppState in the
same process — tests, or any future multi-state setup — never has its
events published onto someone else’s bus.
Structs§
- AckOutcome
- What a click asks of the launcher (the
notify.ackresponse). - Block
Reveal Event - Where
block.revealreveals a block — also theblock:revealpayload. - Click
Target - Where a toast click lands (rich-content spec §3.3 A). Resolved by srv from its own store for the notification the click acked — never taken from the toast’s launch argument (§3.4).
- Ledger
End - What the router needs from an ended turn’s ledger (
agentturn). - Notification
Activate Event notification:activate: the user clicked a notification; this window shows its block.- Notification
Retract Event notification:retract: take a shown notification down.- Pending
Activation - A click that arrived while its pane’s workspace was open in no window.
- Published
Ledger 🔒 - The fields of a published ledger the router reads, typed by the same enums srv serializes it with.
- Router
Enums§
- Internal 🔒
- srv-internal sources, fed from places that must not block (the broker’s publish path, the jekt delivery path) and drained by the router’s task.
- Turn
Transition - What the Router does with a
controllerstatusturn transition.
Constants§
- AGENT_
NAME_ 🔒MAX - BODY_
MAX 🔒 - CLOUD_
SIGNED_ OUT_ BODY - The body of the
CloudSignedOutnotification. - EVENT_
BLOCK_ REVEAL block.reveal: the named window’s renderer reveals the block itself (SPEC_REVEAL_BLOCK_ONE_PATH_2026_09_27.md§4.3). Payload:BlockRevealEvent.- EVENT_
NOTIFICATION - EVENT_
NOTIFICATION_ ACTIVATE - EVENT_
NOTIFICATION_ RETRACT - EVENT_
NOTIFICATION_ STATE - Tray snapshot (
TrayState), published on change with persist=1 so a presenter that (re)subscribes gets the current state immediately. - PENDING_
ACTIVATION_ MAX_ AGE_ MS - A click’s target is picked up by a newly opened window only this long after the click (rich-content spec §3.3 D) — never “jumps to an old block much later”.
- STOP_
GRACE_ 🔒MS - How long after a user Stop the turn-ended transition it causes is treated as “the user’s own action”, not “finished”.
- SUMMARY_
MAX 🔒 - Rich-content spec §1.2: the summary line’s cap, before the
…. - TURN_
MAP_ 🔒MAX - Cap on tracked blocks’ turn state (see
Internal::TurnStatushandling).
Statics§
- ROUTERS 🔒
Functions§
- attach_
sources 🔒 - Both hooks run on hot, lock-holding paths (the broker’s publish; jekt delivery). They only parse and enqueue — never call back into the broker or touch the store inline.
- failure_
body AgentFailure.code→ the fixed body for its toast;None= don’t notify. Onlyagent_deleted(a user action) is skipped.killedis NOT a user Stop — those go through the controllers’ kill arm and are never classified (host_spawn.rs) — it means a signal/137, most often the OOM killer: exactly the “stopped while you were away” case (ReAgent P1 on #3654). Detail/stderr never reach a toast.- get
- The router for this broker, if a connection has created it yet.
- init
- Get-or-create the router for this broker. Idempotent.
- is_
bidi_ 🔒control - key 🔒
- may_
take_ pending - Pure: may a window showing
workspace_id(None = an older frontend that doesn’t say) takependingatnow_ms? - notify_
turn_ end - Whether a finished turn is notified: never AgentMux’s own hidden turn; a turn something else started only when the user asked for those too.
- now_ms
- Milliseconds since the Unix epoch, as
i64. - parse_
ledger_ end - The turn is over by its ledger:
end: "completed"(an exit is a crash, whichagentfailurereports).Nonefor a turn still running or settling. - parse_
turn_ status (blockid, turn_active)from a serializedBlockControllerRuntimeStatus.turn_activeisskip_serializing_if = "is_false", so a turn that ENDED arrives with the field absent — absent means false (Codex P1 on #3705; treating it as “unknown” dropped every end-of-turn and “finished” never fired).- pick_
open_ 🔒window - Of
window_ids(a workspace’s windows), the one a connected frontend reports open. Two open windows on one workspace: prefer the one the user was last in. - quiet_
hours_ until notify:quiethours="HH:MM-HH:MM"in local time. Ifnowis inside the window, return when it ends (epoch ms);Noneoutside it or on a malformed/empty value. A window whose end is before its start spans midnight (“22:00-08:00”). Equal start and end means “off”.- redact_
body - §9.2 body policy: first line, sanitized, truncated, and replaced wholesale when it trips the same credential/destructive keyword list the jekt tier gate uses.
- registry 🔒
- resolve_
click_ target - Walk block → tab → workspace → windows.
Nonewhen the block or its tab is gone. BLOCKING (store reads). - sanitize_
name - Strip control + bidi-override characters and cap length — the only user-influenced text that reaches a title (§9.1).
- sanitize_
summary - Rich-content spec §1.2: the agent’s session summary as a single line — controls and bidi stripped, whitespace collapsed, capped at 90 characters. A summary that trips the jekt keyword list is dropped, not replaced: it is decoration, not something the user needs to be told is hidden.
- setting_
bool 🔒 - setting_
str 🔒 - settings_
from_ extra - Resolve the policy’s settings view from
settings.json(§8). - spawn_
internal 🔒 - spawn_
ticker 🔒 - turn_
transition - Pure:
prev= last knownturn_activefor the block (None = never seen, e.g. right after srv start — a replayed/first status is not a transition). - with_
question_ count - Rich-content spec §2.1: “ (+N more)“ after the (already truncated) first question, so the count survives truncation. No body, no suffix — the title “has a question” stands on its own.
Type Aliases§
- Registry 🔒