Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/web/src/components/app/docs-sections/automations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,10 @@ export function AutomationsContent() {
agent config (prompt, agent type, model, worktree, full access). It
stays out of the Templates list and the palette, and the job's own
forms are how you edit it. Job runs notify through the ordinary
per-agent channels — a job agent's <Code>done</Code>,{" "}
<Code>waiting_user</Code>, or <Code>blocked</Code> event reaches Slack
and web notifications exactly like any other agent's.
per-agent channels, with one exception — a job agent's{" "}
<Code>done</Code>, <Code>waiting_user</Code>, or <Code>blocked</Code>{" "}
event raises a browser notification like any other agent's, but is
excluded from Slack.
</P>
</Section>

Expand Down
21 changes: 13 additions & 8 deletions apps/web/src/components/app/docs-sections/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export function NotificationsContent() {
<P>
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{" "}
<strong>Settings → Notifications</strong>.
</P>

Expand All @@ -25,7 +25,8 @@ export function NotificationsContent() {
<Code>waiting_user</Code> — agent needs your input
</li>
<li>
<Code>blocked</Code> — agent hit an error it can't resolve
<Code>blocked</Code> — agent is stuck with no further approach to
try
</li>
</ul>
</Section>
Expand Down Expand Up @@ -60,10 +61,14 @@ export function NotificationsContent() {
>
Slack incoming webhook
</a>{" "}
URL and use <strong>Send Slack test</strong> to verify it. Configured
events fire to Slack whenever a browser notification isn't delivered
(no tab open, permission denied, or the event isn't in your
browser-notification list).
URL and use <strong>Send Slack test</strong> to verify it, then click{" "}
<strong>Save</strong> — the webhook and the Slack event list are only
persisted when you save, unlike the browser toggles above, which save
as soon as you flip them. Configured events fire to Slack whenever a
browser notification isn't delivered (no tab open, permission denied,
or the event isn't in your browser-notification list). Agents launched
by a job are the exception: their status events go to browser
notifications only, never to Slack.
</P>
</Section>

Expand Down Expand Up @@ -93,7 +98,7 @@ export function NotificationsContent() {
</Section>

<Section>
<H3>Focus-aware suppression</H3>
<H3 id="focus-suppression">Focus-aware suppression</H3>
<P>
Dispatch suppresses browser and Slack notifications for an agent
you're already looking at. Tabs that have an agent selected send a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
];
2 changes: 1 addition & 1 deletion apps/web/src/lib/sound-cues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/lib/tips/tips.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion docs/03-api-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand Down
2 changes: 1 addition & 1 deletion docs/17-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 7 additions & 6 deletions plugins/dispatch/skills/jobs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading