Add ready, discard, merge-problem and workflow notifications

This commit is contained in:
Ian Gulliver
2026-10-19 19:35:23 -07:00
parent 31ee494c41
commit 88d1146778
7 changed files with 276 additions and 215 deletions
+6 -2
View File
@@ -1215,13 +1215,17 @@ components:
NotificationPrefs: NotificationPrefs:
type: object type: object
description: Which new questions, finished turns, errors and rate limits notify this user in their open byre pages. description: Which task events notify this user in the macOS app.
required: [questions, reports, errors, rate_limits] required: [questions, reports, errors, rate_limits, ready, discard, merge_problems, workflows]
properties: properties:
questions: { type: boolean, description: Questions. On by default. } questions: { type: boolean, description: Questions. On by default. }
reports: { type: boolean, description: "Task turns that end with a summary (turn.ended with summary: true)." } 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. } errors: { type: boolean, description: session.error events on tasks. }
rate_limits: { type: boolean, description: session.rate_limited 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: UserUpdate:
type: object type: object
+1 -1
View File
@@ -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. `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: <error>` 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: <error>` 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. The app logs through `os.Logger` with subsystem `run.fc.byre.mac`: `log show --last 10m --predicate 'subsystem == "run.fc.byre.mac"'` prints it.
+4 -4
View File
@@ -342,11 +342,11 @@ An agent error (a turn that ends in error without a human interrupt, a process t
## Notifications ## 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. - **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 · <task title>` 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-<seq>` for a turn summary or `error-<seq>` for an error or rate limit, so a repeat replaces the earlier one. - **Content.** The title is `byre · <task title>` 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-<seq>` for a turn summary, `workflow-<seq>` for a finished workflow, `error-<seq>` for an error or rate limit, and `task-<task id>` 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`), and a `summary.read` event removes that task's `turn-<seq>` notifications up to its `seq` (`clearTurns`). - **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-<seq>` 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-<task id>` 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. - **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"). 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").
+221 -208
View File
@@ -998,19 +998,31 @@ type MessageCreate struct {
Text string `json:"text"` 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 { type NotificationPrefs struct {
// Discard task.discard_suggested events.
Discard bool `json:"discard"`
// Errors session.error events on tasks. // Errors session.error events on tasks.
Errors bool `json:"errors"` 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 Questions. On by default.
Questions bool `json:"questions"` Questions bool `json:"questions"`
// RateLimits session.rate_limited events on tasks. // RateLimits session.rate_limited events on tasks.
RateLimits bool `json:"rate_limits"` 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 Task turns that end with a summary (turn.ended with summary: true).
Reports bool `json:"reports"` Reports bool `json:"reports"`
// Workflows task.workflow_finished events.
Workflows bool `json:"workflows"`
} }
// PaneFocus defines model for PaneFocus. // PaneFocus defines model for PaneFocus.
@@ -1502,7 +1514,7 @@ type User struct {
GitName string `json:"git_name"` GitName string `json:"git_name"`
Login string `json:"login"` 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"` NotificationPrefs NotificationPrefs `json:"notification_prefs"`
// ReviewModel The model that reviews this user's tasks // ReviewModel The model that reviews this user's tasks
@@ -4095,212 +4107,213 @@ var swaggerSpec = []string{
"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",
"cJ/fq1rP+/f/cwA=", "XlGCwAGyzzgwMBL4Py9/+jHqcgB1tU8eckChvxDBii+ePu15yGUYFSVnwqzeTIY4Vsq51vax5Rhp+ChE",
"+XQ5rXdoxt5SzYP7/F7Vet6//58DAA==",
} }
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+31
View File
@@ -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 @MainActor
func notifyTurn(_ store: Store, _ ev: ByreEvent) { 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 { guard let me = store.me, me.notificationPrefs.reports, ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId, !taskId.isEmpty else {
+4
View File
@@ -8,6 +8,10 @@ typealias NotificationPrefs = Components.Schemas.NotificationPrefs
private let kinds: [(key: WritableKeyPath<NotificationPrefs, Bool>, label: String)] = [ private let kinds: [(key: WritableKeyPath<NotificationPrefs, Bool>, label: String)] = [
(\.questions, "Questions"), (\.questions, "Questions"),
(\.reports, "Turn summaries"), (\.reports, "Turn summaries"),
(\.ready, "Ready to merge"),
(\.discard, "Suggests discard"),
(\.mergeProblems, "Merge problems"),
(\.workflows, "Workflow finished"),
(\.errors, "Session errors"), (\.errors, "Session errors"),
(\.rateLimits, "Rate limits"), (\.rateLimits, "Rate limits"),
] ]
+9
View File
@@ -624,6 +624,10 @@ final class Store {
try await self.loadProjects() 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": 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 { if let projectId = ev.projectId {
spawn("load tasks") { spawn("load tasks") {
try await self.loadTasks(projectId) try await self.loadTasks(projectId)
@@ -640,6 +644,9 @@ final class Store {
} }
} }
case "inbox.created": case "inbox.created":
if let taskId = ev.taskId {
clearTask(self, taskId)
}
notifyInbox(self, try! decodePayload(ev.payload, as: InboxItem.self)) notifyInbox(self, try! decodePayload(ev.payload, as: InboxItem.self))
spawn("load inbox") { spawn("load inbox") {
try await self.loadInbox() try await self.loadInbox()
@@ -656,6 +663,8 @@ final class Store {
} }
case "session.error", "session.rate_limited": case "session.error", "session.rate_limited":
notifyError(self, ev) notifyError(self, ev)
case "task.merge_stopped", "task.merge_after_review_dropped", "task.workflow_finished":
notifyTask(self, ev)
case "turn.ended": case "turn.ended":
notifyTurn(self, ev) notifyTurn(self, ev)
if ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId { if ev.payload.value["summary"] as? Bool == true, let taskId = ev.taskId {