From 7051cde73c3fb02ce00f9902ad31da0f36a38ffe Mon Sep 17 00:00:00 2001
From: Brad Harris block the
settings pane renders, the ~3s ack timeout, the 15s focus heartbeat and
30s TTL, and every dispatch_notify parameter, limit and tool-set claim.
Fixed:
- Four surfaces claimed a job agent's status events reach Slack like any
other agent's. createNotificationRuntime skips the Slack send for any
agent named `job-...` that has a job run, so those events raise browser
notifications only. Corrected in docs-sections/automations.tsx,
docs-sections/notifications.tsx, docs/17-jobs.md, docs/03-api-spec.md
and plugins/dispatch/skills/jobs/SKILL.md.
- docs/17-jobs.md linked to docs/16-notifications.md, which doesn't exist.
- The Slack section never mentioned Save. The webhook and the Slack event
list only persist on Save, while the browser toggles above them save as
soon as they're flipped.
- `blocked` was described as "hit an error it can't resolve", which
contradicts the Status Events section and the injected launch guidance.
Aligned the docs and the settings-pane copy (EVENT_OPTIONS, CUE_INTENTS)
with the canonical wording.
- Dropped "independent" from the intro; browser and Slack are chained.
Added a focus-suppression ambient tip, since a user who doesn't know
about it reads the silence as a broken notification.
Co-Authored-By: Claude Opus 5 (1M context)
done,{" "}
- waiting_user, or blocked event reaches Slack
- and web notifications exactly like any other agent's.
+ per-agent channels, with one exception — a job agent's{" "}
+ done, waiting_user, or blocked{" "}
+ event raises a browser notification like any other agent's, but is
+ excluded from Slack.
Dispatch can notify you when agents finish, need input, or get stuck — so you don't have to watch the dashboard. Notifications are delivered - through three independent channels: native browser notifications, Slack, - and local sound cues. All three are configured in{" "} + through three channels: native browser notifications, Slack, and local + sound cues. All three are configured in{" "} Settings → Notifications.
@@ -25,7 +25,8 @@ export function NotificationsContent() {waiting_user — agent needs your input
blocked — agent hit an error it can't resolve
+ blocked — agent is stuck with no further approach to
+ try
Dispatch suppresses browser and Slack notifications for an agent you're already looking at. Tabs that have an agent selected send a diff --git a/apps/web/src/components/app/notification-settings-constants.ts b/apps/web/src/components/app/notification-settings-constants.ts index 6ab9c74f4..d84e70f21 100644 --- a/apps/web/src/components/app/notification-settings-constants.ts +++ b/apps/web/src/components/app/notification-settings-constants.ts @@ -21,6 +21,6 @@ export const EVENT_OPTIONS: Array<{ { id: "blocked", label: "Blocked", - description: "Agent hit an error or obstacle", + description: "Agent is stuck with no further approach to try", }, ]; diff --git a/apps/web/src/lib/sound-cues.ts b/apps/web/src/lib/sound-cues.ts index d4f837f6b..a938f17d2 100644 --- a/apps/web/src/lib/sound-cues.ts +++ b/apps/web/src/lib/sound-cues.ts @@ -154,7 +154,7 @@ export const CUE_INTENTS: Array<{ { intent: "blocked", label: "Blocked", - description: "Agent hit an error or obstacle.", + description: "Agent is stuck with no further approach to try.", }, { intent: "review_finished", diff --git a/apps/web/src/lib/tips/tips.ts b/apps/web/src/lib/tips/tips.ts index d7e7f5705..edd0c39b7 100644 --- a/apps/web/src/lib/tips/tips.ts +++ b/apps/web/src/lib/tips/tips.ts @@ -308,6 +308,14 @@ export const tips: Tip[] = [ since: "0.35.0", surfaces: ["ambient"], }, + { + id: "focus-suppression", + title: "Quiet While You Watch", + body: "Dispatch skips browser and Slack notifications for the agent you currently have selected in a visible, focused tab. Switch away, hide the tab, or blur the window and alerts for it resume right away.", + docsSection: "notifications#focus-suppression", + since: "0.11.12", + surfaces: ["ambient"], + }, { id: "status-correction", title: "Status Correction", diff --git a/docs/03-api-spec.md b/docs/03-api-spec.md index 2a89e5356..d51a383c3 100644 --- a/docs/03-api-spec.md +++ b/docs/03-api-spec.md @@ -358,7 +358,7 @@ All fields are optional — the request updates only the fields it contains. } ``` -`notifyEvents` and `webNotifyEvents` are arrays of event-type strings (`done`, `waiting_user`, `blocked`). When a notable agent event fires, Dispatch first attempts an in-app notification via the SSE event stream; if no browser client acks within ~3s it falls back to the Slack webhook (provided the event is enabled there). +`notifyEvents` and `webNotifyEvents` are arrays of event-type strings (`done`, `waiting_user`, `blocked`). When a notable agent event fires, Dispatch first attempts an in-app notification via the SSE event stream; if no browser client acks within ~3s it falls back to the Slack webhook (provided the event is enabled there). Agents belonging to a job run are excluded from that Slack fallback — their status events reach browser notifications only. ### `POST /notifications/ack` diff --git a/docs/17-jobs.md b/docs/17-jobs.md index 13b7b3ad0..4dd9d590f 100644 --- a/docs/17-jobs.md +++ b/docs/17-jobs.md @@ -140,7 +140,7 @@ Report size limits: 1 MB total, 100 tasks, 500 logs per task, 10 KB summary and **None of it fires today.** The channel lists come from `job.notify`, and no code path writes that column: it is absent from `createJob`'s insert, from `JobConfigUpdate`, from the routes' Zod schemas, and from the MCP `create_job`/`update_job` tools. `buildRunConfig` falls back to three empty arrays, so `getNotifyChannels` always returns none and the notifier returns before sending. Treat this as a data model waiting on a write path, not a shipped feature. -What does notify is the ordinary per-agent path in [docs/16-notifications.md](16-notifications.md): job agents are not excluded from it, so their `done`, `waiting_user`, and `blocked` events reach Slack and web notifications like any other agent's. Agents can also call `dispatch_notify` directly, which is in `JOB_TOOLS`. +What does notify is the ordinary per-agent path, and only half of it: a job agent's `done`, `waiting_user`, and `blocked` events raise browser notifications like any other agent's, but `createNotificationRuntime` skips the Slack send for any agent whose name starts with `job-` and has a job run. So a job run's status events never reach Slack through that path either. Agents can call `dispatch_notify` directly, which is in `JOB_TOOLS`, and that path has no job exclusion. ### API diff --git a/plugins/dispatch/skills/jobs/SKILL.md b/plugins/dispatch/skills/jobs/SKILL.md index 7de66e10e..9234f05ce 100644 --- a/plugins/dispatch/skills/jobs/SKILL.md +++ b/plugins/dispatch/skills/jobs/SKILL.md @@ -123,9 +123,10 @@ There is no per-job notification config to wire. The `notify` column exists in the data model, but nothing writes it — not the routes, not `create_job`, not `update_job` — so the per-job Slack path never fires. -Job agents are not excluded from the ordinary per-agent notifications, so a run's -`done`, `waiting_user`, and `blocked` events already reach whatever Slack and web -notifications the user has configured. That is the coverage you get for free — -which makes emitting an honest terminal `dispatch_event` the thing that actually -determines whether a failure is visible tonight. Call `dispatch_notify` directly -when a run needs to say something the status event cannot carry. +The ordinary per-agent path covers a job run only halfway: its `done`, +`waiting_user`, and `blocked` events raise the browser notifications the user has +configured, but the Slack send is skipped for any agent named `job-…` that has a +job run. So an honest terminal `dispatch_event` is what makes a failure visible +in the app, and nothing you emit as a status event will reach Slack. Call +`dispatch_notify` directly when a run needs to say something in Slack, or +anything the status event cannot carry.