Stop PWA notification pile-up and surface blocked sub-agents - #237
Merged
Merged
Conversation
Three fixes for the over-notification report, one cause each: 1. Session-scoped OS notification tag. The tag was the record id, so a session that asked for bash seven times left seven stale "Needs approval" cards in the notification center. Web Push cannot retract a shown notification; replacement via a shared tag is its only correction. Each session now holds one replaceable slot: a later ask overwrites the stale one, the parked escalation overwrites the ask it escalates, and the eventual idle overwrites whatever was left. The server computes the tag once and sends it on both the push payload and notification.recorded, so the service worker and an open tab cannot disagree. Per-record dedupe still governs sound and speech. 2. Auto-approve flags persist across restarts. The flag was memory-only, so every deploy silently flipped an auto-approved directory back to ask mode and pushed one permission ask per tool call at every configured phone until the user noticed. Flags now live in .state/auto-approve.json (AUTO_APPROVE_STATE_FILE, 0600), restored on boot with pending-ask reconciliation; corrupt state fails closed and an explicit toggle wins over the startup load. 3. Sub-agent permission asks are delivered. A child stopped on an unanswered ask is stalled work nobody else can unblock; suppressing it meant a delegated task sat frozen while the inbox swore nothing needed anyone. Permission (and its parked escalation) is now the one child event that takes the delivery path; an auto-approved directory still answers child asks silently, and a child that merely finishes stays recorded-only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
Screenshots showed macOS Notification Center piled with "Needs approval to run bash" cards from the PWA — most already answered in the app — and a new requirement: a sub-agent blocked on a permission ask must be surfaced, while a sub-agent that merely finishes must not.
Three causes, three fixes
1. One replaceable OS notification slot per session
The push/notification
tagwas the record id, so every ask in a busy session piled its own card into the notification center, and Web Push cannot retract a shown notification — replacement via a shared tag is its only correction.notificationTag()keys the tag by session (record id only for sessionless records): a later ask overwrites the stale one, the parked escalation overwrites the ask it escalates, the eventual idle overwrites whatever is left. The server computes the tag once and stamps it on both the push payload andnotification.recorded, so the service worker and an open tab can never disagree about identity. Collapsing is presentation only: the per-record dedupe still governs sound/speech.2. Auto-approve survives a restart
The flag was memory-only (decision #11 "volatile"), so
service:installat 14:01 today silently flipped this very directory back to ask mode and pushed one permission ask per tool call until it was re-toggled at 14:04 — the exact burst in the screenshots, and it would recur on every deploy. Flags now persist to.state/auto-approve.json(AUTO_APPROVE_STATE_FILE, mode 0600): restored on boot with pending-ask reconciliation (asks that arrived while the BFF was down get answered too), corrupt file fails closed to everything-off, an explicit toggle always wins over the startup load. Persisting an instruction the user already gave through the authenticated UI is not an escalation.3. A blocked sub-agent is surfaced; a finished one is not
permissionis now the one child event that takes the delivery path — a child stopped on an unanswered ask is stalled work nobody else can unblock, and suppressing it meant a delegated task sat frozen while the inbox swore nothing needed anyone. Its parked escalation follows the same policy (the lineage skip inscheduleParkedis removed). A child ask in an auto-approved directory is still suppressed asauto-permissions— it was answered before anyone was blocked. Child idle/error/question staysuppressed: "subagent", recorded-only.Coverage
notificationTagidentity / tag on push payload + recorded event / persistence round-trip incl. boot reconciliation, corrupt-file fail-closed, explicit-toggle-wins / client collapse under shared tag with per-record sound preserved.smoke.apilineage test rewritten to the new policy (asks from any lineage delivered; child idle recorded as filterable noise); mock's/test/mobile/idlegained adirectoryparam for lineage-correct fixtures;AUTO_APPROVE_STATE_FILEwired into the per-run state-file set (guard test enforces it).AGENTS.md decisions #10a, #11, #24 updated to match.
Verification
npm run typecheckclean;npm test1184 passed.Note for after merge: existing stale cards in macOS Notification Center predate the tag change and need one manual clear; new notifications collapse from then on.