From 10c6c84f74587049ef8b1c782978ea455298d7f9 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sun, 18 Oct 2026 00:57:51 -0700 Subject: [PATCH] Save a default model per user, set from Settings --- api/openapi.yaml | 26 +- docs/operations.md | 8 +- internal/api/api.gen.go | 517 ++++++++++++++++--------------- internal/server/notifications.go | 21 ++ internal/store/notifications.go | 14 + internal/store/store.go | 6 +- mac/Sources/Byre/Composer.swift | 3 +- mac/Sources/Byre/Settings.swift | 41 +++ mac/Sources/Byre/Store.swift | 10 +- mac/Sources/Byre/Switcher.swift | 6 +- 10 files changed, 395 insertions(+), 257 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index e6194e0..541be85 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -44,6 +44,23 @@ paths: schema: { $ref: "#/components/schemas/User" } "400": { $ref: "#/components/responses/Error" } + /api/me/default-model: + put: + operationId: updateDefaultModel + description: Sets the model the calling user's app starts new tasks on. + requestBody: + required: true + content: + application/json: + schema: { $ref: "#/components/schemas/DefaultModel" } + responses: + "200": + description: The updated user. + content: + application/json: + schema: { $ref: "#/components/schemas/User" } + "400": { $ref: "#/components/responses/Error" } + /api/me/lead/sessions: get: operationId: listLeadSessions @@ -1072,13 +1089,20 @@ components: User: type: object - required: [login, display_name, git_name, git_email, notification_prefs] + required: [login, display_name, git_name, git_email, notification_prefs, default_model] properties: login: { type: string } display_name: { type: string } git_name: { type: string } git_email: { type: string } notification_prefs: { $ref: "#/components/schemas/NotificationPrefs" } + default_model: { type: string, description: The model the app starts new tasks on. } + + DefaultModel: + type: object + required: [model] + properties: + model: { type: string } NotificationPrefs: type: object diff --git a/docs/operations.md b/docs/operations.md index e0da471..3fd6e03 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -171,6 +171,8 @@ curl -H 'Tailscale-User-Login: you@example.com' -H 'Tailscale-User-Name: You' ht `POST /api/tasks/{id}/sessions` starts a Claude session in the task's worktree and sends the prompt as its first turn; `POST /api/sessions/{id}/messages` starts later turns and `POST /api/sessions/{id}/interrupt` ends the current one. A message sent while a task session's turn is running, from the app, the lead's `send_message`, a parent's `message_subtask` or byre's review findings, is queued (`Manager.Message` in `internal/agents/queue.go`) in `task_inputs`, keyed by task so it survives a handoff to a new session, and recorded, in the same transaction, as a `user.queued` event (its `text`, `images` and `task_inputs` `id`, the sender as actor), and the request returns the running session with 202. `DELETE /api/tasks/{id}/queue/{input id}` takes a queued message back (`Manager.Withdraw`): under the task's queue lock (`queueLockKey`), which both hand-over paths (`deliverQueued`, `takeQueuedMidTurn`) hold from reading the queue until the message is sent or taken, whichever session of the task is delivering, it deletes the row and records a `user.unqueued` event with its `id`, or answers 409 once the message has been handed over (the row is gone). A model it carries is applied to the session at once and takes effect from the next turn. The agent gets queued messages after its next tool call, the same way as inbox answers (see "Inbox"): each is recorded as its own `user.message` with the sender as actor and `mid_turn: true`, and the agent reads them, headed " sent you this message while you were working", in the hook's `additionalContext` (Codex: `turn/steer`). Messages are handed over oldest first, stopping at the first one with images, since mid-turn delivery carries only text; none are handed over during a handoff turn. Whatever is still queued when the turn ends goes out one message per turn, ahead of held inbox answers, from `Deliver`; startup does the same for idle sessions. Closing the task deletes its queued messages. `sqlite3 byre.db 'SELECT * FROM task_inputs'` lists them. A task has at most one session that isn't `completed`. A message may carry `model`; when it differs from the session's, byre writes it to `agent_sessions.model`, records a `session.model` event (shown as a note), and sends the live CLI a `set_model` control request before the message, so the switch applies from that turn and survives a resume (`--model`) or compaction. The composer's model menu sends `model` only when the choice differs from the session's model, or from the `session.context` model when the session has none. The session's commits use the git identity of the user who created it, so that user must have set one with `PUT /api/me`. +`users.default_model` is the model the app starts new work on: ⌘K's Start and New task (whose composer then offers it first), the Project tab's and TODO links' `start`, suggestion links' `start` and `discuss` (when there is no live lead session), and the composer of a task or lead with no live session. It is returned as `default_model` on `GET /api/me`, set with `PUT /api/me/default-model` from the Settings tab's Default model menu, and records `user.updated`, so every open app reloads it. The server itself never reads it: the lead's `create_task` and `start_todo` default to the lead's own model, and `create_subtask` to the parent session's. + Images: `POST /api/blobs` takes a raw image body (type sniffed from the bytes, PNG, JPEG, GIF or WebP only, at most 5 MiB; `ImageTypes` and `MaxBlobBytes` in `internal/store/blob.go`) and returns its id; `GET /api/blobs/{id}` serves it. A message or session prompt may carry `images`, a list of blob ids (unknown ids are a 400); a message may then have empty text. byre reads each blob when the turn is sent and passes it before the text: to Claude as a base64 `image` content block (`claude.Session.Send`), to Codex as an `image` input with a `data:` URL (`codex.Session.Send`). The `user.message` event records the ids as `images`. In the composer, pasting or dropping an image adds a thumbnail with a × to remove it; images upload when the message is sent, and the transcript shows them on the user's message, each linking to the full image. Compaction is vendor-agnostic: `POST /api/sessions/{id}/compact` (the transcript's **Compact** button, always shown and enabled only while the task has an idle session) sends the agent `handoffPrompt` (`internal/agents/compact.go`) as a `byre` message, asking for a handoff covering the goal, decisions and why, current state, dead ends, open questions and files. That turn files no inbox summary and isn't bounced for questions. When it ends normally, its `turn.ended` carries `handoff: true`, the session is completed, and a new session on the same task starts with `handoffPreamble` plus the handoff as its first message (actor `byre`; its `user.message` carries `continuation: true`). The request's optional `model` (the button's long-press menu, `compact_task`'s `model`) picks the new session's model, on either vendor; without it the new session keeps the old one's. The old session still writes the handoff, so this can't get past a vendor that is rate-limited. The chosen model is held in memory (`Manager.handoffModel`) until the handoff turn ends. The context meter tracks the new session. The request's `user.message` carries `handoff: true`, and from it the transcript puts the request and everything the agent does in that turn into one collapsed note reading "writing a handoff…", without streaming the agent's text; when the turn ends its summary becomes the `turn.ended` note, or "handoff not written" if the turn ended without one. The transcript never draws the new session's first message (`continuation: true`), so that note, which holds the handoff, is the only marker of the switch. If byre restarts during the handoff turn, the continuation is lost and the old session simply goes idle. @@ -216,7 +218,7 @@ Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and **Drafts.** Unsent composer text (task and lead) and unsent inbox answers (checked choices and Other/Reply text) are kept in memory by `Drafts` (`mac/Sources/Byre/Drafts.swift`) and saved to a SwiftData `DraftRecord` per server and login (`docs/macos.md`, "Drafts"), one JSON object with `composer` (keyed by `message:` or `message:lead`, each `{text, images}`), `checked` and `texts` (both keyed by inbox item id). Writes are debounced (400 ms) and flushed at once when a composer or answer box loses focus, on send and on quit. A composer draft is cleared only once its send succeeds, and an inbox answer only once its response is accepted; each send in flight is tracked on its own and the stored draft holds the text of every unconfirmed send (oldest first, then the composer's current text, separated by blank lines) and counts its images, so a relaunch mid-send brings back everything not yet confirmed. A failed send's text goes back into the composer ahead of whatever is there now (text typed since, or earlier failures), separated by a blank line, rather than replacing it. The composer follows its `Drafts` text as it changes, so text put back from outside it (a withdrawn queued message, a draft synced in from another Mac) shows at once. Drafts for tasks that are no longer active and for inbox items no longer open are pruned whenever the task list or inbox reloads. A record that doesn't decode is logged and ignored, as is a write that fails; drafts then live only in memory. Pending images are not stored, only their count: after a relaunch the composer shows how many need re-attaching, until dismissed or the next successful send. -The task switcher (⌘K, or the project ▸ task picker centred in the right pane's top row) lists active tasks with their task state and open inbox item count (the Project tab shows the same state), live while it is open. Tasks with inbox items come first; within each group, the task least recently visited comes first (never-visited tasks before all others, the right pane's own task after all others, ties in the server's order: projects by name, tasks newest first), so ⌘K Enter repeated cycles through the tasks. Visit times are SwiftData `VisitRecord`s synced through iCloud (`docs/macos.md`, "Persistence"), stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Text before a first colon that starts at least one project's name (ignoring case, no spaces) picks those projects instead: `by: fix` lists only their tasks whose titles contain "fix", their project rows that match, and one row per project creating the task titled "fix" there. When no project name starts with it, the whole text is the title, so "Fix: login" stays as typed. A create row shows `models[0]` from `mac/Sources/Byre/Composer.swift`, the model the composer offers first for the new task's first message; the task itself starts with no session. Below each create row, a "Start in …" row creates the same task and starts its agent on `models[0]` with the title as its first message, through `POST /api/projects/{id}/tasks` with `prompt` set to the title. Picking a task points the right pane at it on the Inbox tab when it has open inbox items, otherwise on the Transcript tab, scrolled to its latest summary when that is unread (see below and "Lead agent"). +The task switcher (⌘K, or the project ▸ task picker centred in the right pane's top row) lists active tasks with their task state and open inbox item count (the Project tab shows the same state), live while it is open. Tasks with inbox items come first; within each group, the task least recently visited comes first (never-visited tasks before all others, the right pane's own task after all others, ties in the server's order: projects by name, tasks newest first), so ⌘K Enter repeated cycles through the tasks. Visit times are SwiftData `VisitRecord`s synced through iCloud (`docs/macos.md`, "Persistence"), stamped when a task is selected and again when it is left. After the tasks come the projects, one row each; picking one points the right pane at the project with no task, on the Project tab. Typing filters both and adds a last row that creates a task with the typed text as its title, in the right pane's project (the first project when there is none); Enter picks the highlighted row. Text before a first colon that starts at least one project's name (ignoring case, no spaces) picks those projects instead: `by: fix` lists only their tasks whose titles contain "fix", their project rows that match, and one row per project creating the task titled "fix" there. When no project name starts with it, the whole text is the title, so "Fix: login" stays as typed. A create row shows the user's default model (see "Agent sessions"), the model the composer offers first for the new task's first message; the task itself starts with no session. Below each create row, a "Start in …" row creates the same task and starts its agent on the default model with the title as its first message, through `POST /api/projects/{id}/tasks` with `prompt` set to the title. Picking a task points the right pane at it on the Inbox tab when it has open inbox items, otherwise on the Transcript tab, scrolled to its latest summary when that is unread (see below and "Lead agent"). **Task state.** The switcher and the Project tab show one label per task in place of its raw session state, the first that applies (`taskState` in `mac/Sources/Byre/Store.swift`), with a dot and a tooltip saying why: **Needs your answer** (an open `question` inbox item), **Merging** (`merging` or `merging_into_parent`), **Merge held** (`merge_queued_at`), **Merge after review** (`merge_after_review`), **Error** (an open `session_error` or `rate_limited` inbox item), **Working** (the task's `state` is `running`, `review_pending`, `reviewing` or `discarding`; see "Task state in the store"), **Idle** (anything else, including no session). The Project tab, in its task list and TODO items, draws a spinner in place of the dot for **Working** and **Merging**. The label follows the tasks and inbox state the app already holds, so it needs no extra fetch. @@ -234,7 +236,7 @@ Each user has one long-running lead agent, shown in the left pane. It is not tie - **Signals.** Every published event goes through `Manager.Observe`. These become signals in `lead_inputs` for every user who has a lead session: `inbox.created` (a question with its choices and recommendation, an error or rate limit), `inbox.updated` when a human answered a question or error (a question's signal also carries its text and the agent's recommendation, so the lead sees where the human departed from it), `task.created` (naming the TODO item it was started from, or a subtask's parent, if any), `task.merge_started` (" is merging, started by ."), `task.merged`, `task.discarded`, `turn.ended` with `summary: true` (" finished a turn:" with its `result` cut to 1500 characters), `task.workflow_stage` (" started stage "" of workflow "".") and `task.workflow_finished` (" finished workflow "":" with its output cut to 1500 characters), `review.started` (" started a review of snapshot ."), `review.finished` (" review of snapshot :" with the review's summary, `change` and findings, or why it failed or was skipped; `reviewLine` in `internal/agents/review.go`), `user.message` on a task from a human (see "Pane lock"), and `task.state` (" state: → .", or " state: ." for a new task; see "Task state in the store"). The store hands `Observe` all of one transaction's events together, and they become one signal, its `task.state` lines first, so a turn's summary always arrives with the state its turn end left the task in. The human switching the right pane is not a signal. A signal caused by the lead itself (actor ` (lead)`) is skipped, as is the update from an answer it gave. A task's `task.created` event carries the actor that created it, separately from its `created_by` login: tasks the lead creates with `create_task` or `start_todo` keep the human's login as `created_by` but have actor ` (lead)`. Whenever the lead is idle, byre sends the oldest held message from the human as its own turn, or else every held signal as one `byre` message headed "Signals since your last turn (n)", followed by "Tasks now:" with the `list_tasks` text and a line saying what the lead's right pane shows (task or project, and selected tab) and whether the human is working in it (`leadStateText` in `internal/agents/lead.go`). If collecting that fails, for example because a project's repository is gone, the batch is still sent, ending instead with "The task list and right pane state are unavailable:" and the error, which is also logged as `lead state`. Its guidance tells it not to respond to the human's own actions, and to make its final message exactly `NO_REPLY` when a batch needs nothing from it. A transcript (`quietReply` in `mac/Sources/Byre/TranscriptBuilder.swift`) never draws an `agent.message` whose last line is `NO_REPLY` (the model sometimes writes a sentence before it), and hides the live partial message while its text is still a prefix of it, so an empty one never takes up space. A lead turn that byre starts without a handoff (a signal batch or a handoff continuation) is held (`ctx.held`): its `byre` message is never drawn, the turn isn't opened, so the scanner stays off, its partial message stays hidden, and its tool calls, subagents and thinking are built into a held row list instead of the transcript. The turn is released, inserting those rows where the turn belongs, when it draws anything else: a real message, a note, or its own `turn.ended` when it ends without `NO_REPLY`. When it ends in `NO_REPLY` while still held, the held rows are dropped, so nothing of the turn ever appears; a turn that draws a real message anywhere in it keeps all of it, even when a later message is `NO_REPLY`. - **Tools.** `list_tasks` (each project's active tasks with their state, each subtask noting its parent, then a line listing the titles of its `TODO.md` items on the default branch that no active task was started from), `read_transcript` (a task's events in brief, the last `limit` or those after `after_seq`; closed tasks too), `search_closed_tasks` (see "Closing a task"), `read_inbox`, `read_status` (the `GET /api/admin/status` body as JSON, built by the same `Server.status`, which the server hands `agents.New`), `answer_question` (an `option`, `other` or `dismiss` response recorded as the user, through the same checks as `POST /api/inbox/responses`), `send_message` (starts the task's agent if it has none; while it is mid-turn the message is queued through `Manager.Message` like the human's, see "Agent sessions", and the tool result says it was queued), `create_task` and `start_todo` (each with an optional `workflow` to freeze and run), `create_project` (`name` and `repo_path`, created as the user by the same `Server.createProject` as `POST /api/projects`, which the server hands `agents.New`, so it publishes the same `project.created` event, and returned as JSON), `interrupt`, `compact_task`, `take_snapshot` (see "Snapshots"), `merge_task` (optional `target`: `main`, or `parent` for a subtask, see "Subtasks"), `discard_task`, `show`, `compact_self`, the note tools (see "Notes") and the workflow tools (see "Workflows"). The lead also has Claude Code's own tools and reads worktrees by path. Messages it sends task agents have actor ` (lead)`, so they don't make the user a participant; tasks, merges, discards and answers are done as the user. Its guidance says to answer a question only when the answer is clear or the human gave it, and to merge or discard only when the human asks. - **Focus.** `users.focus` holds what the user's right pane shows: `project_id`, `task_id`, `tabs` (the tabs put first, in order) and `tab` (the selected one). With a `task_id`, `project_id` is always that task's project, filled in by the server; with only a `project_id`, the pane shows that project with no task, which only the Project tab has anything for. `GET /api/me/focus` reads it; `PUT /api/me/focus` and the lead's `show` set it, refusing an unknown tab, a task that isn't active or an unknown project, and record a `focus.updated` event owned by the user. The app applies the focus at startup and on every `focus.updated` it didn't cause itself; the human's own changes (⌘K, the right pane's task picker, clicking a tab, a task link, a notification) are sent with `PUT /api/me/focus`. -- **Pane lock.** A `show` of what the pane already shows (the same task, or the same project with no task, on the same selected tab; `sameView`) tells the lead so and applies only its `tabs` order, whether or not the human is in the pane, so the human's selected tab stays selected. Otherwise, while the human is working in the right pane, the lead's `show` is refused with an error telling it to say in chat what it would have shown, unless its selected tab is `project` (the fallback the lead shows when nothing needs the human, so the human being there doesn't hold it) or the pane is finished (`Manager.paneFinished`): its task is merged, discarded or merging (`merging_by` or `finalizing` set), or its selected tab is `inbox` and the task has no open items, or the human is waiting on the task's agent (`Manager.paneWaiting`): the selected tab is `transcript`, the task's implementer session is running, the app reports no unsent text in the pane, and either the `user.message` that started the running turn came from the user after the pane last moved, or the pane last moved 10 seconds ago or more (`watchHold`). The pane's last move is the last `PUT /api/me/focus` or `show` that set it (`Manager.paneMoved`, in memory, so a restart counts as long ago). For the user's own message, unsent text reported before the turn started doesn't count, since the app's report of the composer emptying on send can arrive after the message does. A `user.message` on a task from a human (not `byre`, an agent or a lead) signals every lead " sent a message; its agent is working on it.", and the sender's own lead, when its pane shows that task and is waiting that way, also gets "Your right pane is free: the human is waiting on 's agent." Each `PUT /api/me/focus` from the user starts a 10-second timer (`endWatchHold`); when it fires, if the pane hasn't moved since, the app still reports the human in it, the user hasn't messaged the task since the move (that already freed it, above), and the pane is waiting that way, the lead gets the signal "Your right pane is free: the human has watched 's agent work for 10 seconds without typing." The lead's own `show` starts no timer. When an `inbox.updated`, `task.merge_started`, `task.merged` or `task.discarded` event leaves a user's focused task finished that way, that user's signal for it gets a line "Your right pane is free: has nothing left in its inbox." (or "is merging."/"is merged."/"is discarded."), even when the event itself makes no signal, such as a dismissed error; `inbox.updated` on a task that is no longer active adds none. The lead's guidance tells it to show the next thing that needs the human on that line, or, when nothing does, the project's Project tab with a short status in chat: one line per active task, then up to three TODO items it would start next, each with a reason, starting one only when the human picks it. It writes each item as a link to `#/todo//`; Markdown views draw that (`todo` in `mac/Sources/Byre/MarkdownView.swift`) as the title followed by `start` and `discuss` actions, as accent link text so the line stays selectable, that call `POST /api/projects/{id}/todo/tasks` with the first model, as the Project tab's do, or, once an active task was started from that item, the task's colour, dot and state, linking to it. When the lead suggests new work that isn't in TODO.md, its guidance tells it to write a link to `#/suggest///`, drawn by the same code with `start` and `discuss` buttons: `start` calls `POST /api/projects/{id}/tasks` with the title, the brief as `prompt` and the first model, which checks the user's git identity, creates the task and starts its agent in one request, with no lead turn in between; `discuss` sends the lead `POST /api/me/lead/messages` asking to discuss the suggestion, quoting its title and brief (with the first model only when the user has no live lead session). Once an active task in that project has the suggestion's title, it shows that task's state the same way. A chat view refills them whenever its transcript grows or tasks or sessions change. When that status says nothing new since its last one, it replies `NO_REPLY` instead. The app sends `PUT /api/me/pane-focus` with `focused` and `drafting` whenever either changes (`PaneFocus` in `mac/Sources/Byre/PaneFocus.swift`): `focused` is true while the window is key, the app is active and the keyboard focus or the last click is inside the right pane, so a click anywhere in it counts, not only on inputs and buttons; `drafting` while any text field in the right pane other than the terminal holds non-blank text. It sends false on deactivating, Change Server…, closing the window and quit, and resends true when its event stream reconnects. byre keeps both flags and the time of the last report in memory per user (`Manager.paneFocus`, `Manager.paneReported`), so they start false after a restart until the app resends them. +- **Pane lock.** A `show` of what the pane already shows (the same task, or the same project with no task, on the same selected tab; `sameView`) tells the lead so and applies only its `tabs` order, whether or not the human is in the pane, so the human's selected tab stays selected. Otherwise, while the human is working in the right pane, the lead's `show` is refused with an error telling it to say in chat what it would have shown, unless its selected tab is `project` (the fallback the lead shows when nothing needs the human, so the human being there doesn't hold it) or the pane is finished (`Manager.paneFinished`): its task is merged, discarded or merging (`merging_by` or `finalizing` set), or its selected tab is `inbox` and the task has no open items, or the human is waiting on the task's agent (`Manager.paneWaiting`): the selected tab is `transcript`, the task's implementer session is running, the app reports no unsent text in the pane, and either the `user.message` that started the running turn came from the user after the pane last moved, or the pane last moved 10 seconds ago or more (`watchHold`). The pane's last move is the last `PUT /api/me/focus` or `show` that set it (`Manager.paneMoved`, in memory, so a restart counts as long ago). For the user's own message, unsent text reported before the turn started doesn't count, since the app's report of the composer emptying on send can arrive after the message does. A `user.message` on a task from a human (not `byre`, an agent or a lead) signals every lead " sent a message; its agent is working on it.", and the sender's own lead, when its pane shows that task and is waiting that way, also gets "Your right pane is free: the human is waiting on 's agent." Each `PUT /api/me/focus` from the user starts a 10-second timer (`endWatchHold`); when it fires, if the pane hasn't moved since, the app still reports the human in it, the user hasn't messaged the task since the move (that already freed it, above), and the pane is waiting that way, the lead gets the signal "Your right pane is free: the human has watched 's agent work for 10 seconds without typing." The lead's own `show` starts no timer. When an `inbox.updated`, `task.merge_started`, `task.merged` or `task.discarded` event leaves a user's focused task finished that way, that user's signal for it gets a line "Your right pane is free: has nothing left in its inbox." (or "is merging."/"is merged."/"is discarded."), even when the event itself makes no signal, such as a dismissed error; `inbox.updated` on a task that is no longer active adds none. The lead's guidance tells it to show the next thing that needs the human on that line, or, when nothing does, the project's Project tab with a short status in chat: one line per active task, then up to three TODO items it would start next, each with a reason, starting one only when the human picks it. It writes each item as a link to `#/todo//`; Markdown views draw that (`todo` in `mac/Sources/Byre/MarkdownView.swift`) as the title followed by `start` and `discuss` actions, as accent link text so the line stays selectable, that call `POST /api/projects/{id}/todo/tasks` with the user's default model, as the Project tab's do, or, once an active task was started from that item, the task's colour, dot and state, linking to it. When the lead suggests new work that isn't in TODO.md, its guidance tells it to write a link to `#/suggest///`, drawn by the same code with `start` and `discuss` buttons: `start` calls `POST /api/projects/{id}/tasks` with the title, the brief as `prompt` and the user's default model, which checks the user's git identity, creates the task and starts its agent in one request, with no lead turn in between; `discuss` sends the lead `POST /api/me/lead/messages` asking to discuss the suggestion, quoting its title and brief (with the user's default model only when the user has no live lead session). Once an active task in that project has the suggestion's title, it shows that task's state the same way. A chat view refills them whenever its transcript grows or tasks or sessions change. When that status says nothing new since its last one, it replies `NO_REPLY` instead. The app sends `PUT /api/me/pane-focus` with `focused` and `drafting` whenever either changes (`PaneFocus` in `mac/Sources/Byre/PaneFocus.swift`): `focused` is true while the window is key, the app is active and the keyboard focus or the last click is inside the right pane, so a click anywhere in it counts, not only on inputs and buttons; `drafting` while any text field in the right pane other than the terminal holds non-blank text. It sends false on deactivating, Change Server…, closing the window and quit, and resends true when its event stream reconnects. byre keeps both flags and the time of the last report in memory per user (`Manager.paneFocus`, `Manager.paneReported`), so they start false after a restart until the app resends them. - **Move hold.** Each `PUT /api/me/focus` from the user (switching task or tab, by any means: the switcher, a tab, a link, a notification, or the app's own switch to Inbox when a question arrives) records the time in `Manager.humanMoved`, and for 10 seconds after it (`moveHold`) every `show` that would change the view is refused, whatever the pane lock above would allow, with an error giving how long ago the move was. A `show` of what the pane already shows still applies its tab order. A refusal also marks the user in `Manager.moveRefused`, which each move by the user clears. When the 10 seconds pass without another move by the user (`endMoveHold`, on a timer), byre clears both and, only if a `show` was refused since that move, sends the lead the signal "The move hold is over: …"; its guidance tells it to show what it held back if the pane's other rules allow, or reply `NO_REPLY`. Because only a refusal arranges the signal, the lead's system prompt and the `show` tool's description tell it to call `show` at once whenever the user asks to see something or it wants to show something, never holding back in expectation of a refusal. It is in memory, so a restart clears it. - **Start hold.** Start on a TODO item in the Project tab leaves the pane where it is. When the user starts one themselves (`StartTodo` with the user as actor, not the lead's `start_todo`), byre records the time in `Manager.startedTodo` for them, and for 10 seconds after it (`startHold`), while the pane's selected tab is `project`, every `show` that would change the view is refused, focused or not, with an error telling the lead the human is likely to start more. A refusal also marks the user in `Manager.startRefused`. The user's own `PUT /api/me/focus` (switching task or tab) or a message to the lead clears both, and another Start restarts the hold and clears the mark. The refusal and the `show` tool's description both tell the lead the hold lasts 10 seconds. When the 10 seconds pass without it being cleared or restarted (`endStartHold`, on a timer), byre clears both and, only if a `show` was refused since that Start and the pane's selected tab is still `project`, sends the lead the signal "The start hold is over: …" so it can show what it held back; its guidance tells it to reply `NO_REPLY` when that no longer matters. It is in memory, so a restart clears it. - **Compaction.** The pane's **Compact** button asks the lead for a handoff (`leadHandoffPrompt`). The lead can't see its own context size, so the first time one of its turns ends (other than a handoff or a `compact_self` turn) with the latest `session.context` `used` at half of its `window` or more, byre holds a signal for it giving the percentage and asking it to call `compact_self` at the next logical point (`warnLeadContext` in `internal/agents/lead.go`); it is sent once per live process, so a byre restart can send it again once the window is known. The lead calls `compact_self`; when that turn ends normally, byre sends the same request. The handoff turn ends the session, and a new lead session starts from `leadHandoffPreamble` plus the handoff; held input follows once that turn ends. @@ -282,7 +284,7 @@ The running macOS app posts a notification for each new inbox item, raised by th - **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`). - **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. +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"). The Inbox tab shows one item at a time, the task's oldest, filling the tab without a card frame; the next appears when it is answered. Each item is answered on its own; there is no batch submission. A pick-one question's choices are clickable blocks, and clicking one sends that answer at once. A choice's block (or a pick-any choice's checkbox label) holds the whole list item: its first line, which is the choice's text, and everything under it, such as an example's paragraph or code block. A pick-any question's choices are checkboxes, sent with the Submit button at the bottom of the card. Below the choices, an "Other…" block in the same style opens the only text field. Under it, every question has one submit row: a "Let the agent decide" button at its left, away from the choices so it isn't hit by accident, and the Submit button at its right. Let the agent decide sends at once an `other` answer reading "Use your judgment for this decision and continue within the existing task instructions." (`delegateText` in `mac/Sources/Byre/Inbox.swift`), which shows in the card's answer list like any other; the `ask_human` description tells agents that this answer settles only that decision and never grants an authorization the owner must give explicitly. ArrowDown and ArrowUp move between the choice blocks, the checkboxes and the Other block. A question has exactly one Submit button, enabled once there is something to send: on a pick-one question it sends the Other text as `other`; on a pick-any question it sends the checked choices with the Other text alongside (one `option` response), or the Other text alone as `other` when nothing is checked. An error has a "Reply…" block (`other`) and a Dismiss button. The Other and Reply text boxes send with the Submit button under them or on Enter (Shift+Enter for a newline), like the message box, and fold back into their block when left empty with no images. Pasting or dropping an image into them adds a thumbnail, as in the composer (both keep pending images in `Store.images`, an `ImageDrafts`); images upload when the answer is submitted and are put back if the submission fails. Pending answer images are not kept across a relaunch. Clicking a pick-one choice sends no images. diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 40bfd92..14cb65f 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -736,6 +736,11 @@ type ConnectedClient struct { Login string `json:"login"` } +// DefaultModel defines model for DefaultModel. +type DefaultModel struct { + Model string `json:"model"` +} + // DirEntry defines model for DirEntry. type DirEntry struct { // Ignored git ignores the entry: .gitignore and the other exclude files match it or a directory above it. @@ -1343,10 +1348,12 @@ type Unmerged struct { // User defines model for User. type User struct { - DisplayName string `json:"display_name"` - GitEmail string `json:"git_email"` - GitName string `json:"git_name"` - Login string `json:"login"` + // DefaultModel The model the app starts new tasks on. + DefaultModel string `json:"default_model"` + DisplayName string `json:"display_name"` + GitEmail string `json:"git_email"` + GitName string `json:"git_name"` + Login string `json:"login"` // NotificationPrefs Which new inbox items and finished turns notify this user in their open byre pages. NotificationPrefs NotificationPrefs `json:"notification_prefs"` @@ -1509,6 +1516,9 @@ type RespondInboxJSONRequestBody = InboxRespond // UpdateMeJSONRequestBody defines body for UpdateMe for application/json ContentType. type UpdateMeJSONRequestBody = UserUpdate +// UpdateDefaultModelJSONRequestBody defines body for UpdateDefaultModel for application/json ContentType. +type UpdateDefaultModelJSONRequestBody = DefaultModel + // SetFocusJSONRequestBody defines body for SetFocus for application/json ContentType. type SetFocusJSONRequestBody = Focus @@ -1587,6 +1597,9 @@ type ServerInterface interface { // (PUT /api/me) UpdateMe(w http.ResponseWriter, r *http.Request) + // (PUT /api/me/default-model) + UpdateDefaultModel(w http.ResponseWriter, r *http.Request) + // (GET /api/me/focus) GetFocus(w http.ResponseWriter, r *http.Request) @@ -2077,6 +2090,20 @@ func (siw *ServerInterfaceWrapper) UpdateMe(w http.ResponseWriter, r *http.Reque handler.ServeHTTP(w, r) } +// UpdateDefaultModel operation middleware +func (siw *ServerInterfaceWrapper) UpdateDefaultModel(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateDefaultModel(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // GetFocus operation middleware func (siw *ServerInterfaceWrapper) GetFocus(w http.ResponseWriter, r *http.Request) { @@ -3644,6 +3671,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/me", wrapper.GetMe) m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/me", wrapper.UpdateMe) m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/me/notifications", wrapper.UpdateNotificationPrefs) + m.HandleFunc(http.MethodPut+" "+options.BaseURL+"/api/me/default-model", wrapper.UpdateDefaultModel) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/me/lead/sessions", wrapper.ListLeadSessions) m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/me/lead/events", wrapper.ListLeadEvents) m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/me/lead/messages", wrapper.MessageLead) @@ -3713,246 +3741,247 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H // const string: with thousands of chunks the chained `+` fold is several // times slower for the Go compiler than parsing a slice literal. var swaggerSpec = []string{ - "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", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/server/notifications.go b/internal/server/notifications.go index c16ecb4..2ba6848 100644 --- a/internal/server/notifications.go +++ b/internal/server/notifications.go @@ -1,7 +1,9 @@ package server import ( + "errors" "net/http" + "strings" "g.fc.run/misc/byre/internal/api" ) @@ -19,3 +21,22 @@ func (s *Server) UpdateNotificationPrefs(w http.ResponseWriter, r *http.Request) } writeJSON(w, http.StatusOK, u) } + +func (s *Server) UpdateDefaultModel(w http.ResponseWriter, r *http.Request) { + req := api.DefaultModel{} + if !readJSON(w, r, &req) { + return + } + + if strings.TrimSpace(req.Model) == "" { + writeError(w, http.StatusBadRequest, errors.New("model is required")) + return + } + + u, err := s.store.UpdateDefaultModel(r.Context(), actor(r), req.Model) + if err != nil { + writeStoreError(w, err) + return + } + writeJSON(w, http.StatusOK, u) +} diff --git a/internal/store/notifications.go b/internal/store/notifications.go index 755beb9..57cd5bc 100644 --- a/internal/store/notifications.go +++ b/internal/store/notifications.go @@ -25,3 +25,17 @@ func (s *Store) UpdateNotificationPrefs(ctx context.Context, login string, prefs } return s.GetUser(ctx, login) } + +func (s *Store) UpdateDefaultModel(ctx context.Context, login, model string) (api.User, error) { + err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + _, err := tx.ExecContext(ctx, `UPDATE users SET default_model = ? WHERE login = ?`, model, login) + if err != nil { + return err + } + return emit(api.Event{Kind: "user.updated", Actor: login, Payload: map[string]any{"login": login}}) + }) + if err != nil { + return api.User{}, err + } + return s.GetUser(ctx, login) +} diff --git a/internal/store/store.go b/internal/store/store.go index b38c2bf..1f5c26d 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -474,6 +474,8 @@ DELETE FROM inbox_responses WHERE item_id IN (SELECT id FROM inbox_items WHERE k DELETE FROM inbox_items WHERE kind = 'client_run'; ALTER TABLE inbox_responses DROP COLUMN client; ALTER TABLE inbox_responses DROP COLUMN exit_code; +`}, {"add-user-default-model", ` +ALTER TABLE users ADD COLUMN default_model TEXT NOT NULL DEFAULT 'claude-opus-5-5'; `}} const ( @@ -674,8 +676,8 @@ func (s *Store) EnsureUser(ctx context.Context, login, displayName string) (api. func (s *Store) GetUser(ctx context.Context, login string) (api.User, error) { u := api.User{} prefs := "" - err := s.db.QueryRowContext(ctx, `SELECT login, display_name, git_name, git_email, notification_prefs FROM users WHERE login = ?`, login). - Scan(&u.Login, &u.DisplayName, &u.GitName, &u.GitEmail, &prefs) + err := s.db.QueryRowContext(ctx, `SELECT login, display_name, git_name, git_email, notification_prefs, default_model FROM users WHERE login = ?`, login). + Scan(&u.Login, &u.DisplayName, &u.GitName, &u.GitEmail, &prefs, &u.DefaultModel) if errors.Is(err, sql.ErrNoRows) { return u, ErrNotFound } diff --git a/mac/Sources/Byre/Composer.swift b/mac/Sources/Byre/Composer.swift index 37d189f..ce76d29 100644 --- a/mac/Sources/Byre/Composer.swift +++ b/mac/Sources/Byre/Composer.swift @@ -291,7 +291,8 @@ struct ComposerView: View { let offered = live.map { s in models.filter { vendorOf($0) == s.vendor.rawValue } } ?? models let options = !current.isEmpty && !offered.contains(current) ? [current] + offered : offered let modelKey = "message:\(live?.id ?? key)" - return ModelState(live: live, current: current, options: options, modelKey: modelKey, selected: pick ?? modelChoices[modelKey] ?? (current.isEmpty ? options[0] : current)) + let fresh = live == nil ? store.defaultModel : options[0] + return ModelState(live: live, current: current, options: options, modelKey: modelKey, selected: pick ?? modelChoices[modelKey] ?? (current.isEmpty ? fresh : current)) } var body: some View { diff --git a/mac/Sources/Byre/Settings.swift b/mac/Sources/Byre/Settings.swift index 61dfac4..42e8645 100644 --- a/mac/Sources/Byre/Settings.swift +++ b/mac/Sources/Byre/Settings.swift @@ -25,6 +25,11 @@ struct SettingsView: View { heading("Notifications") DeviceNotifications() PrefsBox() + heading("Default model") + Text("New tasks you start from byre run on this model.") + .foregroundStyle(Palette.muted) + .fixedSize(horizontal: false, vertical: true) + DefaultModelMenu() heading("What your lead has learned") Text("Your lead notes your priorities and preferences as it works with you, and starts every session with them.") .foregroundStyle(Palette.muted) @@ -132,6 +137,42 @@ private struct PrefsBox: View { } } +private struct DefaultModelMenu: View { + @Environment(Store.self) private var store + @Environment(Overlays.self) private var overlays + + var body: some View { + Menu { + ForEach(models, id: \.self) { m in + Button(m) { + Task { + do { + store.me = try await store.api.client.updateDefaultModel(body: .json(.init(model: m))).ok.body.json + } catch { + overlays.showAlert(errorText(error)) + } + } + } + } + } label: { + HStack(spacing: 8) { + Text(store.defaultModel) + Image(systemName: "chevron.up.chevron.down") + .font(.system(size: 9, weight: .semibold)) + } + .foregroundStyle(Palette.text) + .modifier(FieldStyle()) + .contentShape(Rectangle()) + } + .menuStyle(.button) + .buttonStyle(.plain) + .menuIndicator(.hidden) + .fixedSize() + .help("Model for new tasks") + .padding(.top, 8) + } +} + private struct LeadNotes: View { @Environment(Store.self) private var store @Environment(Overlays.self) private var overlays diff --git a/mac/Sources/Byre/Store.swift b/mac/Sources/Byre/Store.swift index 6b17ca5..5c87b45 100644 --- a/mac/Sources/Byre/Store.swift +++ b/mac/Sources/Byre/Store.swift @@ -92,7 +92,7 @@ final class Store { todoBusy[key] = discuss ? "discuss" : "start" Task { do { - _ = try await api.client.startTodoItem(path: .init(projectId: projectId), body: .json(.init(title: title, model: models[0], discuss: discuss))).created + _ = try await api.client.startTodoItem(path: .init(projectId: projectId), body: .json(.init(title: title, model: defaultModel, discuss: discuss))).created } catch { overlays.showAlert(errorText(error)) } @@ -108,7 +108,7 @@ final class Store { todoBusy[key] = "start" Task { do { - _ = try await api.client.createTask(path: .init(projectId: projectId), body: .json(.init(title: title, prompt: brief, model: models[0]))).created + _ = try await api.client.createTask(path: .init(projectId: projectId), body: .json(.init(title: title, prompt: brief, model: defaultModel))).created } catch { overlays.showAlert(errorText(error)) } @@ -126,7 +126,7 @@ final class Store { let live = lead != nil && lead!.state != .completed Task { do { - _ = try await api.client.messageLead(body: .json(.init(text: "Let's discuss your suggestion \"\(title)\" before starting it:\n\n\(brief)", model: live ? nil : models[0]))).accepted + _ = try await api.client.messageLead(body: .json(.init(text: "Let's discuss your suggestion \"\(title)\" before starting it:\n\n\(brief)", model: live ? nil : defaultModel))).accepted } catch { overlays.showAlert(errorText(error)) } @@ -208,6 +208,10 @@ final class Store { focusTab(.reviews) } + var defaultModel: String { + me!.defaultModel + } + func loadMe() async throws { me = try await api.client.getMe().ok.body.json } diff --git a/mac/Sources/Byre/Switcher.swift b/mac/Sources/Byre/Switcher.swift index 6a376f0..1104870 100644 --- a/mac/Sources/Byre/Switcher.swift +++ b/mac/Sources/Byre/Switcher.swift @@ -234,7 +234,7 @@ final class SwitcherModel { case .newTask(let p, let title): create(p.id, .init(title: title)) case .startTask(let p, let title): - create(p.id, .init(title: title, prompt: title, model: models[0])) + create(p.id, .init(title: title, prompt: title, model: store.defaultModel)) case .startWorkflow(let p, let title): pickWorkflow(p, title) case .workflow(let p, let title, let w): @@ -442,7 +442,7 @@ private struct SwitcherRowView: View { .lineLimit(1) .truncationMode(.tail) .frame(maxWidth: .infinity, alignment: .leading) - state(models[0]) + state(store.defaultModel) .help("Model for the first message") case .startTask(let p, let title): Text(created([("Start in ", nil), (p.name, Palette.muted), (": \(title)", nil)])) @@ -450,7 +450,7 @@ private struct SwitcherRowView: View { .lineLimit(1) .truncationMode(.tail) .frame(maxWidth: .infinity, alignment: .leading) - state(models[0]) + state(store.defaultModel) .help("The title is the agent's first message") case .startWorkflow(let p, let title): Text(created([("Start workflow… in ", nil), (p.name, Palette.muted), (": \(title)", nil)]))