From 88d1146778a6a49d26f506a2051445de4e3ea8ee Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Mon, 19 Oct 2026 19:35:23 -0700 Subject: [PATCH] Add ready, discard, merge-problem and workflow notifications --- api/openapi.yaml | 8 +- docs/macos.md | 2 +- docs/operations.md | 8 +- internal/api/api.gen.go | 429 ++++++++++++++------------- mac/Sources/Byre/Notifications.swift | 31 ++ mac/Sources/Byre/Settings.swift | 4 + mac/Sources/Byre/Store.swift | 9 + 7 files changed, 276 insertions(+), 215 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index b07cb6d..264206a 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1215,13 +1215,17 @@ components: NotificationPrefs: type: object - description: Which new questions, finished turns, errors and rate limits notify this user in their open byre pages. - required: [questions, reports, errors, rate_limits] + description: Which task events notify this user in the macOS app. + required: [questions, reports, errors, rate_limits, ready, discard, merge_problems, workflows] properties: questions: { type: boolean, description: Questions. On by default. } reports: { type: boolean, description: "Task turns that end with a summary (turn.ended with summary: true)." } errors: { type: boolean, description: session.error events on tasks. } rate_limits: { type: boolean, description: session.rate_limited events on tasks. } + ready: { type: boolean, description: task.ready events. } + discard: { type: boolean, description: task.discard_suggested events. } + merge_problems: { type: boolean, description: task.merge_stopped and task.merge_after_review_dropped events. } + workflows: { type: boolean, description: task.workflow_finished events. } UserUpdate: type: object diff --git a/docs/macos.md b/docs/macos.md index 31a7953..226e02e 100644 --- a/docs/macos.md +++ b/docs/macos.md @@ -74,7 +74,7 @@ API calls go through the generated `Client` over `URLSessionTransport`. A middle `EventStream` is a hand-written SSE client over `URLSession.bytes(for:)`. It splits lines itself, because `AsyncBytes.lines` drops the blank lines that end SSE events, and parses off the main actor, handing each frame to it. It handles `id`, `event: ping`, `event: ready` and `data`: it tracks the last seq, reconnects from it after a stale threshold (40 s, checked every 5 s) and a retry delay (2 s), when the network comes back (`NWPathMonitor`), and when the app becomes active with a stale stream. An event whose data doesn't decode is logged and skipped. There are two streams, global (after the bootstrap `last_event_seq`) and lead (`?lead=true`, after the last event of the lead's first page), and only the global one drives the connection dot. -Startup: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: ` and the whole startup is tried again every 2 s until it succeeds, so a server restarting (after an update replaces its binary, for one) heals by itself; Forget Server leaves that screen. `handleEvent` calls `notifyInbox` on `inbox.created` and `notifyTurn` on `turn.ended`. +Startup: `GET /api/admin/status`, then me, projects, inbox, lead sessions and summary reads in parallel, then drafts, focus, the lead stream, the window, the global stream. A failure before the window shows replaces it with `byre failed to start: ` and the whole startup is tried again every 2 s until it succeeds, so a server restarting (after an update replaces its binary, for one) heals by itself; Forget Server leaves that screen. `handleEvent` calls `notifyInbox` on `inbox.created`, `notifyTurn` on `turn.ended`, and `notifyTask` on the task events listed under "Notifications" in `docs/operations.md`. The app logs through `os.Logger` with subsystem `run.fc.byre.mac`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it. diff --git a/docs/operations.md b/docs/operations.md index 766a3d8..b8ade38 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -342,11 +342,11 @@ An agent error (a turn that ends in error without a human interrupt, a process t ## Notifications -The running macOS app posts a notification for each new question, error and rate limit, raised by the app itself from the `inbox.created`, `session.error` and `session.rate_limited` events on its SSE stream (`notifyInbox` and `notifyError` in `mac/Sources/Byre/Notifications.swift`, through `UNUserNotificationCenter`). With the app not running, nothing notifies; closing its window leaves it running. +The running macOS app posts notifications for events on its SSE stream, raised by the app itself (`notifyInbox`, `notifyError`, `notifyTurn` and `notifyTask` in `mac/Sources/Byre/Notifications.swift`, through `UNUserNotificationCenter`). With the app not running, nothing notifies; closing its window leaves it running. -- **Preferences.** `users.notification_prefs` holds what notifies the user, returned as `notification_prefs` on `GET /api/me` and set with `PUT /api/me/notifications`: `questions` (on by default), `reports` (task turns that end with `summary: true` on `turn.ended`, labelled "Turn summaries"), `errors` (`session.error`) and `rate_limits` (`session.rate_limited`). The app checks them against each event as it arrives. -- **Content.** The title is `byre · ` and the body the question (or the turn summary's first line, the error, or "Rate limited"), cut at 200 characters. The notification's identifier is the question's ID, `turn-` for a turn summary or `error-` for an error or rate limit, so a repeat replaces the earlier one. -- **Clearing.** A delivered notification leaves Notification Center once it is dealt with anywhere, on this Mac, another one or by the lead: an `inbox.updated` event whose item is no longer `open` removes the item's notification (`clearInbox`), and a `summary.read` event removes that task's `turn-` notifications up to its `seq` (`clearTurns`). +- **Preferences.** `users.notification_prefs` holds what notifies the user, returned as `notification_prefs` on `GET /api/me` and set with `PUT /api/me/notifications`: `questions` (`inbox.created`, on by default), `reports` (task turns that end with `summary: true` on `turn.ended`, labelled "Turn summaries"), `ready` (`task.ready`), `discard` (`task.discard_suggested`), `merge_problems` (`task.merge_stopped` and `task.merge_after_review_dropped`), `workflows` (`task.workflow_finished`), `errors` (`session.error`) and `rate_limits` (`session.rate_limited`). A key missing from the stored JSON reads as off. The app checks them against each event as it arrives. +- **Content.** The title is `byre · ` and the body the first line of the question, turn summary, ready-to-merge overview, discard or merge-problem reason, workflow output, or error (or "Rate limited"), cut at 200 characters. The notification's identifier is the question's ID, `turn-` for a turn summary, `workflow-` for a finished workflow, `error-` for an error or rate limit, and `task-` for ready to merge, suggests discard and merge problems, so each task shows at most one of those three at a time. +- **Clearing.** A delivered notification leaves Notification Center once it is dealt with anywhere, on this Mac, another one or by the lead: an `inbox.updated` event whose item is no longer `open` removes the item's notification (`clearInbox`), a `summary.read` event removes that task's `turn-` notifications up to its `seq` (`clearTurns`), and `task.merged`, `task.discarded`, `snapshot.created` (unless its `trigger` is `answer`) and `inbox.created` remove the task's `task-` notification (`clearTask`); the last two are the events that clear the task's ready state on the server. - **Clicking** a notification activates the app, shows its window and points the right pane at the item's task. The left pane's **Settings** tab asks macOS for notification permission and edits the preferences, and also holds the Default model menu (see "Agent sessions") and the two Review models menus (see "Reviews"). diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 6a31680..bf47bee 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -998,19 +998,31 @@ type MessageCreate struct { Text string `json:"text"` } -// NotificationPrefs Which new questions, finished turns, errors and rate limits notify this user in their open byre pages. +// NotificationPrefs Which task events notify this user in the macOS app. type NotificationPrefs struct { + // Discard task.discard_suggested events. + Discard bool `json:"discard"` + // Errors session.error events on tasks. Errors bool `json:"errors"` + // MergeProblems task.merge_stopped and task.merge_after_review_dropped events. + MergeProblems bool `json:"merge_problems"` + // Questions Questions. On by default. Questions bool `json:"questions"` // RateLimits session.rate_limited events on tasks. RateLimits bool `json:"rate_limits"` + // Ready task.ready events. + Ready bool `json:"ready"` + // Reports Task turns that end with a summary (turn.ended with summary: true). Reports bool `json:"reports"` + + // Workflows task.workflow_finished events. + Workflows bool `json:"workflows"` } // PaneFocus defines model for PaneFocus. @@ -1502,7 +1514,7 @@ type User struct { GitName string `json:"git_name"` Login string `json:"login"` - // NotificationPrefs Which new questions, finished turns, errors and rate limits notify this user in their open byre pages. + // NotificationPrefs Which task events notify this user in the macOS app. NotificationPrefs NotificationPrefs `json:"notification_prefs"` // ReviewModel The model that reviews this user's tasks @@ -4095,212 +4107,213 @@ var swaggerSpec = []string{ "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", - "cJ/fq1rP+/f/cwA=", + "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", + "+XQ5rXdoxt5SzYP7/F7Vet6//58DAA==", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/mac/Sources/Byre/Notifications.swift b/mac/Sources/Byre/Notifications.swift index d901df5..7b15e8c 100644 --- a/mac/Sources/Byre/Notifications.swift +++ b/mac/Sources/Byre/Notifications.swift @@ -118,6 +118,37 @@ func clearTurns(_ taskId: String, through seq: Int64) { } } +@MainActor +func notifyTask(_ store: Store, _ ev: ByreEvent) { + guard let me = store.me, let taskId = ev.taskId, !taskId.isEmpty else { + return + } + let prefs = me.notificationPrefs + let origin = store.api.origin.absoluteString + switch ev.kind { + case "task.ready" where prefs.ready: + show(store, taskId, "task-\(taskId)@\(origin)", "Ready to merge: \(str(ev.payload.value, "overview") ?? "")") + case "task.discard_suggested" where prefs.discard: + show(store, taskId, "task-\(taskId)@\(origin)", "Suggests discard: \(str(ev.payload.value, "reason") ?? "")") + case "task.merge_stopped" where prefs.mergeProblems: + show(store, taskId, "task-\(taskId)@\(origin)", "Merge stopped: \(str(ev.payload.value, "reason") ?? "")") + case "task.merge_after_review_dropped" where prefs.mergeProblems: + show(store, taskId, "task-\(taskId)@\(origin)", "Didn't merge after the review: \(str(ev.payload.value, "reason") ?? "")") + case "task.workflow_finished" where prefs.workflows: + show(store, taskId, "workflow-\(ev.seq)@\(origin)", "Finished \(str(ev.payload.value, "workflow") ?? ""): \(str(ev.payload.value, "output") ?? "")") + default: + break + } +} + +@MainActor +func clearTask(_ store: Store, _ taskId: String) { + let id = "task-\(taskId)@\(store.api.origin.absoluteString)" + enqueue { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [id]) + } +} + @MainActor func notifyTurn(_ store: Store, _ ev: ByreEvent) { guard let me = store.me, me.notificationPrefs.reports, ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId, !taskId.isEmpty else { diff --git a/mac/Sources/Byre/Settings.swift b/mac/Sources/Byre/Settings.swift index c3852dd..7b7e3ab 100644 --- a/mac/Sources/Byre/Settings.swift +++ b/mac/Sources/Byre/Settings.swift @@ -8,6 +8,10 @@ typealias NotificationPrefs = Components.Schemas.NotificationPrefs private let kinds: [(key: WritableKeyPath, label: String)] = [ (\.questions, "Questions"), (\.reports, "Turn summaries"), + (\.ready, "Ready to merge"), + (\.discard, "Suggests discard"), + (\.mergeProblems, "Merge problems"), + (\.workflows, "Workflow finished"), (\.errors, "Session errors"), (\.rateLimits, "Rate limits"), ] diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 275b25e..1cd0ca4 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -624,6 +624,10 @@ final class Store { try await self.loadProjects() } case "task.created", "task.merge_title", "task.merge_queued", "task.merge_unqueued", "task.merge_after_review", "task.merge_after_review_ended", "task.merge_after_review_cancelled", "task.parent_merge_started", "task.parent_merge_stopped", "task.merge_started", "task.merge_ended", "task.discard_started", "task.merged", "task.discarded", "task.state", "task.ready", "task.discard_suggested", "snapshot.created": + if let taskId = ev.taskId, ["task.merged", "task.discarded"].contains(ev.kind) || (ev.kind == "snapshot.created" && str(ev.payload.value, "trigger") != "answer") { + clearTask(self, taskId) + } + notifyTask(self, ev) if let projectId = ev.projectId { spawn("load tasks") { try await self.loadTasks(projectId) @@ -640,6 +644,9 @@ final class Store { } } case "inbox.created": + if let taskId = ev.taskId { + clearTask(self, taskId) + } notifyInbox(self, try! decodePayload(ev.payload, as: InboxItem.self)) spawn("load inbox") { try await self.loadInbox() @@ -656,6 +663,8 @@ final class Store { } case "session.error", "session.rate_limited": notifyError(self, ev) + case "task.merge_stopped", "task.merge_after_review_dropped", "task.workflow_finished": + notifyTask(self, ev) case "turn.ended": notifyTurn(self, ev) if ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId {