From 029bf2be046ea976e2623b2670f08bff35a1d497 Mon Sep 17 00:00:00 2001 From: Dennis V <2119348+dzianisv@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:33:27 +0000 Subject: [PATCH] fix(notifications): use user-friendly title and dedup keys for permission/question notifications - Change permission notification title from `req.permission || 'Permission requested'` to the user-friendly 'Agent needs approval'; permission type + patterns now appear in the body (e.g. 'bash: echo hello') for context. - Add `dedupeKey: `perm-${req.id}`` and `dedupeKey: `question-${req.id}`` (60 s cooldown) to both events so a SSE reconnect after disconnect() clears state can't fire a second notification for the same pending request. - Fix stale CUA-test comment that claimed 'Agent needs approval' did not exist; fallback assertion already matched correct title; update the comment to reflect the real events.ts behavior. Closes #39 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android-cua-smoke.py | 10 +++++----- src/stores/events.ts | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/scripts/android-cua-smoke.py b/scripts/android-cua-smoke.py index 88369cf..7436dd6 100755 --- a/scripts/android-cua-smoke.py +++ b/scripts/android-cua-smoke.py @@ -1140,11 +1140,11 @@ def run_scenario_backgrounded_permission_notification(opencode_url: str, model: print(f" [api] WARNING: could not send permission-triggering message: {exc}") # Phase 4: DETERMINISTIC — poll notification drawer for permission notification. - # The actual notification fired by events.ts has title `req.permission || - # "Permission requested"` and body `req.patterns` or "A tool needs your approval" - # — there is NO "Agent needs approval" string. The only token guaranteed in every - # dumpsys record is our package name, so gate on APP_PACKAGE (deterministic) and - # treat the human-readable copy as a secondary signal. + # events.ts fires notify({ title: "Agent needs approval", body: ": ", + # category: "permissions", sessionId }) — only when app is backgrounded (AppState check + # in notifications.ts send()). The only token guaranteed in every dumpsys record is our + # package name, so gate on APP_PACKAGE (deterministic); fall back to the notification + # title/body as secondary signals. print(" [notify] polling notification drawer for permission notification...") appeared = check_notification_drawer(APP_PACKAGE, timeout=15) if not appeared: diff --git a/src/stores/events.ts b/src/stores/events.ts index cbab02e..8a26428 100644 --- a/src/stores/events.ts +++ b/src/stores/events.ts @@ -269,9 +269,18 @@ export const useEvents = create((set, get) => ({ })) notify({ category: "permissions", - title: req.permission || "Permission requested", - body: sanitizeBody(req.patterns?.join(", "), "A tool needs your approval"), + title: "Agent needs approval", + body: sanitizeBody( + req.permission + ? req.patterns?.length + ? `${req.permission}: ${req.patterns.join(", ")}` + : req.permission + : req.patterns?.join(", "), + "A tool needs your approval", + ), sessionId: req.sessionID, + dedupeKey: `perm-${req.id}`, + dedupeCooldownMs: 60_000, }) break } @@ -305,6 +314,8 @@ export const useEvents = create((set, get) => ({ title: req.questions?.[0]?.header || "Input needed", body: sanitizeBody(req.questions?.[0]?.question, "The assistant has a question"), sessionId: req.sessionID, + dedupeKey: `question-${req.id}`, + dedupeCooldownMs: 60_000, }) break }