diff options
| author | Adam Malczewski <[email protected]> | 2026-06-01 10:12:11 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-01 10:12:11 +0900 |
| commit | 9c93086c0d4acaa1ed753488b12f72c2ca86a22c (patch) | |
| tree | e74597029db76d50ea6e48c2bb9bcc62a84fd079 /packages/frontend/src/lib/components | |
| parent | 41857893e0a3af7afb7b716a2274dc4aec29e61c (diff) | |
| download | dispatch-9c93086c0d4acaa1ed753488b12f72c2ca86a22c.tar.gz dispatch-9c93086c0d4acaa1ed753488b12f72c2ca86a22c.zip | |
feat(notifications): add notifySubagents toggle to suppress subagent turn pings
A parent agent that spawns 8 subagents was producing 9 "Turn complete"
notifications per round — almost always noise. New `notifySubagents`
config flag (defaults to false) gates `turn-completed` and `turn-error`
from any tab with a `parentTabId`. The flag is intentionally NOT applied
to `permission-required` — a subagent's permission prompt still needs a
human tap to proceed, so suppressing it would silently hang the
subagent. `agent-spawned` is already top-level-only by construction.
Wiring:
- core/notifications/types.ts: NtfyConfig.notifySubagents: boolean
- core/notifications/config.ts: defaults to false; normalize() tolerates
missing / wrong-typed values and falls back to false
- core/notifications/dispatcher.ts: new optional TabParentLookup option
(getTabParentId). When notifySubagents=false AND the lookup returns a
non-empty parent id string, turn-completed/turn-error are dropped.
Lookup failures (no lookup configured, throws, returns undefined) fall
back to "treat as top-level" so legitimate top-level events are never
silently dropped when the DB is briefly unreadable.
- api/app.ts: wires getTabParentId via core's getTab(id)?.parentTabId
- frontend SettingsPanel.svelte: "Include subagent tabs" checkbox with
an explanatory hint that permission prompts still fire
Tests (+9):
- 3 in config.test.ts: default-false, explicit-true, wrong-typed fallback
- 6 in dispatcher.test.ts: suppression of turn-completed/turn-error from
subagents, no suppression when flag is true, permission-required not
gated, graceful fallback when lookup is missing/throws/returns undefined
Live ntfy.sh round-trip re-verified (status: 200).
Diffstat (limited to 'packages/frontend/src/lib/components')
| -rw-r--r-- | packages/frontend/src/lib/components/SettingsPanel.svelte | 17 |
1 files changed, 17 insertions, 0 deletions
diff --git a/packages/frontend/src/lib/components/SettingsPanel.svelte b/packages/frontend/src/lib/components/SettingsPanel.svelte index 8031500..08e86ac 100644 --- a/packages/frontend/src/lib/components/SettingsPanel.svelte +++ b/packages/frontend/src/lib/components/SettingsPanel.svelte @@ -36,6 +36,7 @@ interface NtfyConfigView { authToken: string; hasAuthToken?: boolean; events: Record<NotificationEventType, boolean>; + notifySubagents: boolean; } const NTFY_EVENT_LABELS: Record<NotificationEventType, string> = { @@ -56,6 +57,7 @@ const DEFAULT_NTFY: NtfyConfigView = { "permission-required": true, "agent-spawned": false, }, + notifySubagents: false, }; let ntfy = $state<NtfyConfigView>({ ...DEFAULT_NTFY, events: { ...DEFAULT_NTFY.events } }); @@ -162,6 +164,7 @@ async function saveNtfy(): Promise<void> { enabled: ntfy.enabled, topicUrl: ntfy.topicUrl, events: ntfy.events, + notifySubagents: ntfy.notifySubagents, }; if (ntfyAuthTokenInput !== "") payload.authToken = ntfyAuthTokenInput; const res = await fetch(`${apiBase}/notifications`, { @@ -469,6 +472,20 @@ $effect(() => { {/each} </div> + <div class="flex flex-col gap-1 mt-1"> + <label class="flex items-center gap-2 cursor-pointer"> + <input + type="checkbox" + class="checkbox checkbox-sm rounded-sm" + bind:checked={ntfy.notifySubagents} + /> + <span class="text-xs text-base-content/70">Include subagent tabs</span> + </label> + <span class="text-[10px] text-base-content/40 pl-6"> + Off (default): turn-completed/turn-error from subagents are suppressed. Permission prompts still fire so subagents don't silently hang. + </span> + </div> + <div class="flex gap-1 mt-1"> <button type="button" |
