diff --git a/api/openapi.yaml b/api/openapi.yaml index d39cbf6..995e020 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1012,7 +1012,7 @@ components: Task: type: object - required: [id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, reviewing, merging, discarding, merging_into_parent, summary_seq] + required: [id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, state, merging, discarding, merging_into_parent, summary_seq] properties: id: { type: string } project_id: { type: string } @@ -1038,7 +1038,11 @@ components: workflow: { type: string, description: "The name of the workflow the task froze at creation (getTaskWorkflow has its copy); absent for a task without one." } workflow_run: { type: string, description: "With workflow: the id every task of one run of the workflow shares." } workflow_stage: { type: string, description: "With workflow: the name of the stage this task runs." } - reviewing: { type: boolean, description: "Whether a review of one of the task's snapshots is running." } + state: + type: string + description: "What the task is doing, kept by the store in the same transaction as every change to it (a task.state event carries each change): merged or discarded once closed, else discarding, merging, the state of its agent's latest live session when that isn't idle, reviewing while a review runs, review_pending between a turn's end and its snapshot's review starting or being skipped, idle, or no_agent." + enum: [running, idle, review_pending, reviewing, merging, discarding, merged, discarded, no_agent] + x-enum-varnames: [TaskStateRunning, TaskStateIdle, TaskStateReviewPending, TaskStateReviewing, TaskStateMerging, TaskStateDiscarding, TaskStateMerged, TaskStateDiscarded, TaskStateNoAgent] merging: { type: boolean, description: "Whether a Merge was started and its turn hasn't ended yet." } discarding: { type: boolean, description: "Whether a Discard was started and the task isn't closed yet: its summary turn is waiting for the running turn, running, or done and the task is being removed." } parent_task_id: { type: string, description: "The task whose agent created this one as its subtask; absent for other tasks." } diff --git a/docs/operations.md b/docs/operations.md index a8e2492..1e848e7 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -55,6 +55,14 @@ Agents find closed tasks with the `search_closed_tasks` MCP tool, which task age A Task's `merging` is true while `merging_by` is set, and `discarding` while the task is active with `discard_state` set. Clearing `merging_by` emits `task.merge_ended` (not drawn); that, `task.merge_started`, `task.discard_started` and `task.discarded` reload the project's tasks in the UI, so the task switcher and the Project tab show the task as **Merging**, or **Working** while discarding (see "Task state" under the task switcher). +### Task state in the store + +A Task's `state` (the `tasks.state` column) is what the task is doing, owned by the store: `merged` or `discarded` once closed, else `discarding`, `merging`, the state of the agent's latest non-completed implementer session when it isn't `idle` (`running`), `reviewing` while a review is `running`, `review_pending` while `tasks.review_requests` is above zero, `idle`, or `no_agent` when the task has no live session (`taskStateExpr` in `internal/store/store.go`, earlier cases winning). Every write transaction (`Store.write`), after its own changes, recomputes the state of each task its events name and, when it changed, updates the column and emits `task.state` (`from`, `to`; actor that of the task's last event in the transaction) in the same transaction, so the state and the events that changed it commit and publish together, in seq order. The store publishes each transaction's events in one call (`onEvent` takes the list), which the lead's signals rely on. + +`review_requests` covers the gap between a turn's end and its review starting. `turnEnded` emits `turn.ended` inside `EndTurn`'s transaction, which also sets the session idle and, when the turn's snapshot will be reviewed (no discard or merge pending), adds one to `review_requests`, so the task goes from `running` to `review_pending` in the transaction that publishes the turn's summary. The snapshot is requested after that transaction. Each review request ends exactly once with `EndReviewRequest`, which subtracts one: after `afterSnapshot` returns (by then the review row is `running`, so the task is `reviewing`, or it was skipped or never started, so it is `idle`), or when the request is dropped because a newer one replaced it or the task's snapshots were closed. On startup `recoverReviews` zeroes every task's `review_requests` (`ClearReviewRequests`), since the requests in flight died with the process. + +The UI's labels (see "Task state" under the task switcher) read `state` for **Working**; `task.state` reloads the project's tasks. `sqlite3 byre.db "SELECT id, title, state, review_requests FROM tasks WHERE status = 'active'"` shows them. + `POST /api/tasks/{id}/merge-in` (the transcript's **Sync from main** button, labelled with the default branch) brings the default branch into the task branch through the agent: it sends `mergeInPrompt` (`internal/agents/mergein.go`) as a `byre` message to the task's session, or starts a session with it when the task has none. The prompt has the agent run `git merge --no-ff --no-commit`, resolve conflicts, regenerate generated files rather than hand-merging them, check the build and tests, and leave the merge uncommitted; byre's end-of-turn commit concludes it as a merge commit. If resolving the conflicts took substantive changes rather than combining both sides as written, the prompt tells the agent to stop there and end its turn describing them; the turn's snapshot then gets its review like any other (see "Reviews"). byre doesn't inspect the merge itself: whether a change is substantive, and whether to stop, is the agent's call. On a Merge turn the prompt adds that byre squashes and pushes when the turn ends, and tells the agent to call the `stop_merge` MCP tool first if it stops (see below). The prompt ends by showing the task's current merge title and asking the agent to set a better one with `set_merge_title` if it no longer describes all the work on the branch. It is refused (409) while a session is `running`. A task's `merge_title` is the title of the squash commit Merge makes. It is the task title until the agent calls the `set_merge_title` MCP tool (`internal/agents/mergein.go`), which it may do until a merge is confirmed: one line of at most 72 characters (`maxMergeTitle`, checked by `cleanMergeTitle`), stored in `tasks.merge_title` (empty there means "use the title"), recorded as a `task.merge_title` event that the transcript shows as a note. Malformed titles are rejected with a corrective message, and so is a call once the task is closed or while `merging_by` or `merge_queued_by` is set. The Merge confirm dialog shows the title in a text field the human can edit (Enter confirms), lists the co-authors, and sends the field's value as `merge_title` in the body of `POST /api/tasks/{id}/merge`. Merge validates it like the tool does (400 when malformed) and, when it differs from the stored title, stores it with the human as the event's actor before starting or holding the merge. @@ -173,7 +181,7 @@ Messages byre itself sends an agent (actor `byre`: the restart notice, Sync and 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 in this browser comes first (never-visited tasks before all others, ties in the server's order: projects by name, tasks newest first). Visit times are kept in the browser's localStorage under `byre.visits`, 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 `web/src/task.ts`, the model the composer offers first for the new task's first message; the task itself starts with no session. 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 `web/src/state.ts`), 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`), **Error** (an open `session_error` or `rate_limited` inbox item), **Working** (the session is `running`, a review is running, or the task is discarding), **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, sessions and inbox state the page already holds, so it needs no extra fetch. +**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 `web/src/state.ts`), 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`), **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 page already holds, so it needs no extra fetch. **Unread summaries.** A Task's `summary_seq` is the seq of its latest `turn.ended` event with `summary: true` (0 for none), read through the partial index `events_summary`, so the page knows of unread summaries without loading transcripts; a `turn.ended` with `summary: true` on the global stream raises it in the page. The switcher's and the Project tab's task rows show a **new summary** state label while `summary_seq` is above the seq the user has read (`unreadSummary` in `web/src/state.ts`). Picking such a row (in the switcher, only when the task has no open inbox items) points the right pane at the task's Transcript and scrolls it to that turn's final message (`state.jump`, applied by the transcript once the event is drawn). Read state is per user on the server: `summary_reads` holds one highest-read seq per user and task. `GET /api/me/summary-reads` returns the caller's, for active tasks, as a map from task id to seq, and `PUT /api/tasks/{taskId}/summary-read` with `{seq}` raises it, emitting `summary.read` (`task_id`, `seq`) owned by the user only when it went up, so every page the user has open clears the label. A summary counts as read only when the summary turn's end line (`.turn-end[data-seq]`) is inside a mounted transcript's visible area while the page is visible (an `IntersectionObserver` in `mountChat`), so selecting the task on another tab leaves it unread. `sqlite3 byre.db 'SELECT * FROM summary_reads'` shows the read seqs. @@ -185,8 +193,8 @@ Each user has one long-running lead agent, shown in the left pane. It is not tie - **Sessions.** A lead session is an `agent_sessions` row with `owner` set to the user's login, `role` `lead` and no `task_id`. Its events carry `owner` and no project or task. It runs in `leads/` with `leadGuidance` (passed and re-attached the same way as a task agent's guidance) and the lead tools instead of the task tools. It has no Stop-hook bounce, no snapshots, no end-of-turn commit and no turn summary; a turn that ends in error records the error on its `session.state` event, which the pane shows as a note. - **Messages.** `POST /api/me/lead/messages` sends the caller's message to their lead, starting a lead session when there is none. While the lead is mid-turn the message is held in `lead_inputs`; the lead's PostToolUse hook (Codex: `turn/steer` after each completed tool item) hands held messages over after its next tool call, like a task's queued messages (see "Agent sessions"), and any still held become its next turn. Signals are never handed over mid-turn. The composer stays enabled. `GET /api/me/lead/sessions` lists the caller's lead sessions; interrupt and compact use the usual session endpoints. -- **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.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`), and `user.message` on a task from a human (see "Pane lock"). 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 `web/src/task.ts`) 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; the partial message starts hidden and is first shown by its 100ms render, 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, and the turn isn't opened, so the scanner stays off, and its partial message stays hidden, until the lead draws something else, such as a tool call, thinking or a real message. When such a turn ends in `NO_REPLY`, everything it drew is removed with it (tool calls, thinking), so the transcript keeps nothing of it; a turn that ends in a real message keeps all of it. -- **Tools.** `list_tasks` (each project's active tasks, 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`, `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; refused while it is mid-turn), `create_task` and `start_todo` (each with an optional `workflow` to freeze and run), `interrupt`, `compact_task`, `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. +- **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 `web/src/task.ts`) 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; the partial message starts hidden and is first shown by its 100ms render, 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, and the turn isn't opened, so the scanner stays off, and its partial message stays hidden, until the lead draws something else, such as a tool call, thinking or a real message. When such a turn ends in `NO_REPLY`, everything it drew is removed with it (tool calls, thinking), so the transcript keeps nothing of it; a turn that ends in a real message keeps all of it. +- **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`, `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; refused while it is mid-turn), `create_task` and `start_todo` (each with an optional `workflow` to freeze and run), `interrupt`, `compact_task`, `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 page applies the focus on load 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`. The URL follows the focus: `#/task/`, `#/project/`, or `#/` for neither. - **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` 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 page 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 page'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 page 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 (`web/src/todolink.ts`) as the title followed by small accent-coloured `start · discuss` text links (borderless buttons) 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. 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 page sends `PUT /api/me/pane-focus` with `focused` and `drafting` whenever either changes: `focused` is true while the browser window has focus and the focused element is inside the right pane, and `drafting` while any textarea in the right pane (other than xterm's helper) holds non-blank text, checked on focus changes, `input` and `submit`. The pane itself is focusable (`tabIndex` -1), so a click anywhere in it counts, not only on inputs and buttons; a re-render that removes the focused element hands focus back to the pane rather than counting as leaving. The page sends false on `pagehide` (with `keepalive`) 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 page resends them. - **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. The user's own `PUT /api/me/focus` (switching task or tab) or a message to the lead clears it. 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 by another Start (`endStartHold`, on a timer), byre clears it and, if 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 nothing was. It is in memory, so a restart clears it. @@ -291,7 +299,7 @@ Every new snapshot of a task gets a review: a separate agent session on the vend - **Screenshots.** Reviewer sessions also get the Playwright MCP server (`playwrightMCP` in `internal/agents/review.go`: `npx @playwright/mcp` at a pinned version, headless Chrome, isolated profile, absolute file paths), passed to Claude as a `stdio` entry in `--mcp-config` and to Codex as `mcp_servers` in the thread's `config`. Its automatically named files go to `worktrees//review--media`, removed with the checkout. Each `images` entry is `{path, caption}`, the path absolute or relative to the checkout; at `submit_review` byre reads every one, refuses the whole submission (nothing recorded) for a missing file or one that isn't one of `store.ImageTypes` within `store.MaxBlobBytes`, then stores each as a blob and replaces its `path` with `blob`, since the files are deleted with the checkout. They run without the Stop-hook bounce, mid-turn delivery, end-of-turn commit, snapshots or turn summaries, and their `user.message` events carry no `head`. When the reviewer's turn ends byre completes its session, kills its process and removes the worktree; a review still `running` then fails with why (no submission, with the final message; an error; an interrupt). A process that exits mid-turn fails the review the same way. Merging or discarding the task fails its running reviews ("the task was closed") and ends their reviewers. On startup, a running reviewer resumes like any session, and a running review whose reviewer isn't `running` fails. - **Questions.** A question's `reviews` is the review of the first snapshot the task took after the question was filed, the one at the end of the turn that asked it, computed when the item is read; the Inbox tab shows it under the question once it starts. - **Events.** `review.started`, `review.finished` (state `done`, `failed` or `skipped`; a skipped review emits only `review.finished`) and `review.finding` (a finding's state set, with the user as actor) carry the whole review, with the task's ids and no `agent_session_id`. A reviewer's own events carry its session id and the task's ids: the transcript skips every event of a session whose `session.created` has `role` `reviewer`, and draws each `review.finished` as a note with its outcome; the lead's `read_transcript` leaves the reviewer's events out and shows each `review.finished` with its summary and findings (`reviewLine`). `GET /api/tasks/{taskId}/sessions` and everything that looks for the task's agent (`ListSessions`) see only implementer sessions. -- **UI.** The **Reviews** tab (`web/src/reviews.ts`) shows one review at a time, picked by snapshot (see "Snapshots" for the picker): its snapshot, state or severity counts, error, summary, screenshots and findings. The `change` field is not shown; it goes to the lead and into notes. A finding with `path` and `line` shows, under its title, the lines `line` to `end_line` plus 3 either side from that file's diff for the snapshot's step (`changes?snapshot=&step=true`, the diff the reviewer was given), with new-file line numbers and the finding's own lines marked; it shows nothing when those lines aren't in the diff, and "diff not available" with the error once the task is closed. Screenshots link to the full image. Each open finding has **fix** and **ignore** buttons (also on review cards in the inbox). **fix** sends the finding's severity, title, location and detail to the task's agent as a message from the user: through `POST /api/sessions/{id}/messages` to its latest live session (queued like any message while the agent is mid-turn, see "Agent sessions"), or starting a session when it has none; the view stays where it is, so several findings can be sent in a row. Then it, or **ignore** on its own, records the finding's state (`fixed` or `ignored`) with `PUT /api/reviews/{reviewId}/findings/{index}`, the index being the finding's position in `result.findings`; the review's `finding_states` (the `reviews.finding_states` column, a JSON object from index to state) holds them, and the buttons give way to "fix sent" or "ignored". Only a `done` review's findings take a state, and there is no way to clear one. The tab loads when it opens, when the task changes, and on each `review.*` event in the task's stream. The tab follows the latest `review.*` event of the highest-numbered snapshot: while that review runs, the tab label (and its entry in the More menu) carries a spinner in place of any earlier review's count; once it finishes with open findings (those with no state), the tab (or the More menu holding it) is highlighted with a badge of their count. Each snapshot row's **review** button carries a mark over its top-right corner from that snapshot's latest `review.*` event: a spinner while the review runs, a grey ● when it was skipped, a green ✓ when it finished with no open findings, and, when it has open findings, the button highlighted with their count in a badge. A failed review leaves the button unmarked. A Task's `reviewing` is true while any of its reviews is `running`, and the global stream's `review.*` events reload that project's tasks; the task switcher and the Project tab show such a task as **Working** (see "Task state"). `GET /api/tasks/{taskId}/reviews` lists a task's reviews oldest first. `sqlite3 byre.db "SELECT id, task_id, snapshot_n, vendor, state, error FROM reviews ORDER BY created_at DESC LIMIT 20"` shows recent ones. +- **UI.** The **Reviews** tab (`web/src/reviews.ts`) shows one review at a time, picked by snapshot (see "Snapshots" for the picker): its snapshot, state or severity counts, error, summary, screenshots and findings. The `change` field is not shown; it goes to the lead and into notes. A finding with `path` and `line` shows, under its title, the lines `line` to `end_line` plus 3 either side from that file's diff for the snapshot's step (`changes?snapshot=&step=true`, the diff the reviewer was given), with new-file line numbers and the finding's own lines marked; it shows nothing when those lines aren't in the diff, and "diff not available" with the error once the task is closed. Screenshots link to the full image. Each open finding has **fix** and **ignore** buttons (also on review cards in the inbox). **fix** sends the finding's severity, title, location and detail to the task's agent as a message from the user: through `POST /api/sessions/{id}/messages` to its latest live session (queued like any message while the agent is mid-turn, see "Agent sessions"), or starting a session when it has none; the view stays where it is, so several findings can be sent in a row. Then it, or **ignore** on its own, records the finding's state (`fixed` or `ignored`) with `PUT /api/reviews/{reviewId}/findings/{index}`, the index being the finding's position in `result.findings`; the review's `finding_states` (the `reviews.finding_states` column, a JSON object from index to state) holds them, and the buttons give way to "fix sent" or "ignored". Only a `done` review's findings take a state, and there is no way to clear one. The tab loads when it opens, when the task changes, and on each `review.*` event in the task's stream. The tab follows the latest `review.*` event of the highest-numbered snapshot: while that review runs, the tab label (and its entry in the More menu) carries a spinner in place of any earlier review's count; once it finishes with open findings (those with no state), the tab (or the More menu holding it) is highlighted with a badge of their count. Each snapshot row's **review** button carries a mark over its top-right corner from that snapshot's latest `review.*` event: a spinner while the review runs, a grey ● when it was skipped, a green ✓ when it finished with no open findings, and, when it has open findings, the button highlighted with their count in a badge. A failed review leaves the button unmarked. While any of a task's reviews is `running` and its agent is idle, the task's state is `reviewing` (see "Task state in the store"), which the task switcher and the Project tab show as **Working** (see "Task state"). `GET /api/tasks/{taskId}/reviews` lists a task's reviews oldest first. `sqlite3 byre.db "SELECT id, task_id, snapshot_n, vendor, state, error FROM reviews ORDER BY created_at DESC LIMIT 20"` shows recent ones. ## Workflows diff --git a/internal/agents/lead.go b/internal/agents/lead.go index 9ea7cd1..a5b807e 100644 --- a/internal/agents/lead.go +++ b/internal/agents/lead.go @@ -15,7 +15,7 @@ import ( ) const leadGuidance = `You are the human's lead agent in byre, shown in the left pane of byre's web UI. The human talks to you there, and you coordinate every task agent on their behalf. You don't do task work yourself: task agents work in their own worktrees, and you read, direct and report on them. -- Signals: byre sends you, as messages from byre, what happens across tasks: questions and errors that task agents file, turns they finish with their final message, questions the human answered, tasks created, starting to merge, merged or discarded. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately. +- Signals: byre sends you, as messages from byre, what happens across tasks: questions and errors that task agents file, turns they finish with their final message, questions the human answered, tasks created, reviews started and finished, starting to merge, merged or discarded, and every change of a task's state, as " state: → ." listed first among what happened at the same moment. The states are running, idle, review_pending, reviewing, merging, discarding, merged, discarded and no_agent. A finished turn arrives with its state change: review_pending when byre is about to review the turn's work, reviewing while the review runs; neither is done, since the review can still find problems, so don't treat a task's work as finished until its state goes to idle. Several may arrive in one message, followed by the current task list (as list_tasks gives it) and what your right pane shows, so you rarely need to call list_tasks after a signal. The human doesn't see signals. Messages from the human arrive separately. - Don't respond to what the human just did. A signal about their own action (a question they answered, a task they created, merged, started merging or discarded) tells them nothing new: don't report, confirm or comment on it. Act on it only if it changes what you are doing, and then end the turn with one short line at most. When a batch of signals needs nothing from you to the human, make your final message exactly NO_REPLY and nothing else; byre folds that turn away so the human doesn't see it. - "Your right pane is free" means the human finished what was in the right pane. Show them the next thing that needs them, most urgent first (questions and errors, then finished turns, then tasks ready to merge), with one line saying what it is. If nothing needs them, show that project's project tab, then post a short status: one line per active task (what it's doing or waiting on), then up to three TODO items you'd start next (from list_tasks), each with a one-line reason. Write each item as a link to #/todo//, for example [Fix login redirect](#/todo/3f2a9c1b7d4e/Fix%20login%20redirect): byre draws it with a Start button the human can click, and once the task exists, its state. Start one with start_todo only when the human picks it. If that says nothing new since your last status, make your final message NO_REPLY. - Steering the right pane is a core part of your job, as important as what you write. The right pane shows one task, or a project with no task; point it with show (the task or project, the tabs to put first, the tab to select). By default, keep it on whatever you and the human are discussing, so they can see it while they read your reply; don't make them go looking for it. Call show in the same turn you bring something up: @@ -315,20 +315,27 @@ func (m *Manager) warnLeadContext(ctx context.Context, l *live) error { return m.store.AddLeadInput(ctx, store.LeadInput{Owner: l.owner, Kind: store.LeadSignal, Actor: "byre", Text: text}) } -func (m *Manager) Observe(ev api.Event) { - switch ev.Kind { - case "inbox.created", "inbox.updated", "task.created", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.finished": - case "user.message": - if ev.TaskId == nil || !humanActor(ev.Actor) { - return +func (m *Manager) Observe(events []api.Event) { + batch := []api.Event{} + for _, ev := range events { + switch ev.Kind { + case "inbox.created", "inbox.updated", "task.created", "task.merge_started", "task.merged", "task.discarded", "turn.ended", "task.workflow_stage", "task.workflow_finished", "review.started", "review.finished", "task.state": + case "user.message": + if ev.TaskId == nil || !humanActor(ev.Actor) { + continue + } + default: + continue } - default: + batch = append(batch, ev) + } + if len(batch) == 0 { return } select { - case m.signals <- ev: + case m.signals <- batch: default: - slog.Warn("lead signal dropped", "kind", ev.Kind, "seq", ev.Seq) + slog.Warn("lead signal dropped", "kind", batch[0].Kind, "seq", batch[0].Seq) } } @@ -337,39 +344,56 @@ func (m *Manager) signalLoop() { select { case <-m.watchStop: return - case ev := <-m.signals: - err := m.signal(context.Background(), ev) + case batch := <-m.signals: + err := m.signal(context.Background(), batch) if err != nil { - slog.Error("signal lead", "kind", ev.Kind, "seq", ev.Seq, "err", err) + slog.Error("signal lead", "kind", batch[0].Kind, "seq", batch[0].Seq, "err", err) } } } } -func (m *Manager) signal(ctx context.Context, ev api.Event) error { +func (m *Manager) signal(ctx context.Context, batch []api.Event) error { owners, err := m.store.LeadOwners(ctx) if err != nil { return err } - text, err := m.signalText(ctx, ev) - if err != nil { - return err - } - - for _, owner := range owners { - if ev.Actor == leadActor(owner) { - continue - } - freed, err := m.freedText(ctx, owner, ev) + texts := []string{} + for _, ev := range batch { + text, err := m.signalText(ctx, ev) if err != nil { return err } - lines := strings.TrimSpace(text + "\n" + freed) - if lines == "" { + texts = append(texts, text) + } + + for _, owner := range owners { + states := []string{} + lines := []string{} + for i, ev := range batch { + if ev.Actor == leadActor(owner) { + continue + } + freed, err := m.freedText(ctx, owner, ev) + if err != nil { + return err + } + text := strings.TrimSpace(texts[i] + "\n" + freed) + if text == "" { + continue + } + if ev.Kind == "task.state" { + states = append(states, text) + continue + } + lines = append(lines, text) + } + text := strings.Join(append(states, lines...), "\n") + if text == "" { continue } - err = m.store.AddLeadInput(ctx, store.LeadInput{Owner: owner, Kind: store.LeadSignal, Actor: "byre", Text: lines}) + err = m.store.AddLeadInput(ctx, store.LeadInput{Owner: owner, Kind: store.LeadSignal, Actor: "byre", Text: text}) if err != nil { return err } @@ -486,14 +510,23 @@ func (m *Manager) signalText(ctx context.Context, ev api.Event) (string, error) case "task.workflow_finished": return fmt.Sprintf("%s finished workflow %q:\n%s", m.taskLabel(ctx, deref(ev.TaskId)), ev.Payload["workflow"], clip(fmt.Sprint(ev.Payload["output"]), 1500)), nil - case "review.finished": + case "review.started", "review.finished": r := api.Review{} err := remarshal(ev.Payload, &r) if err != nil { return "", err } + if ev.Kind == "review.started" { + return fmt.Sprintf("%s started a review of snapshot %d.", m.taskLabel(ctx, r.TaskId), r.SnapshotN), nil + } return fmt.Sprintf("%s %s", m.taskLabel(ctx, r.TaskId), reviewLine(r)), nil + case "task.state": + if ev.Payload["from"] == "" { + return fmt.Sprintf("%s state: %v.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Payload["to"]), nil + } + return fmt.Sprintf("%s state: %v → %v.", m.taskLabel(ctx, deref(ev.TaskId)), ev.Payload["from"], ev.Payload["to"]), nil + case "user.message": return fmt.Sprintf("%s sent %s a message; its agent is working on it.", ev.Actor, m.taskLabel(ctx, deref(ev.TaskId))), nil diff --git a/internal/agents/leadtools.go b/internal/agents/leadtools.go index 80492b6..d217679 100644 --- a/internal/agents/leadtools.go +++ b/internal/agents/leadtools.go @@ -710,16 +710,13 @@ func (m *Manager) listTasksText(ctx context.Context) (string, error) { if t.TodoItem != nil { started[*t.TodoItem] = true } - agent := "no agent" + agent := string(t.State) sess, found, err := m.liveSession(ctx, t.Id) if err != nil { return "", err } - if found { - agent = fmt.Sprintf("agent %s", sess.State) - if sess.Model != "" { - agent += " on " + sess.Model - } + if found && sess.Model != "" { + agent += "; agent on " + sess.Model } open := 0 for _, i := range items { diff --git a/internal/agents/manager.go b/internal/agents/manager.go index a16432d..83fa31f 100644 --- a/internal/agents/manager.go +++ b/internal/agents/manager.go @@ -51,7 +51,7 @@ type Manager struct { deliverMu sync.Mutex leadMu sync.Mutex - signals chan api.Event + signals chan []api.Event snapMu sync.Mutex snaps map[string]*snapState @@ -114,7 +114,7 @@ func New(st *store.Store, worktreeRoot, leadRoot, codexHome string) *Manager { paneMoved: map[string]time.Time{}, startedTodo: map[string]time.Time{}, finishing: map[string]bool{}, - signals: make(chan api.Event, 1024), + signals: make(chan []api.Event, 1024), snaps: map[string]*snapState{}, mergeHolders: map[string]string{}, bad: newBadLines(), @@ -310,7 +310,7 @@ func (m *Manager) Recover(ctx context.Context) error { if derr != nil { return derr } - next, serr := m.store.EndTurn(ctx, sess.Id, "byre", map[string]any{"error": err.Error()}) + next, serr := m.store.EndTurn(ctx, sess.Id, "byre", map[string]any{"error": err.Error()}, nil, false) if serr != nil { return serr } @@ -660,7 +660,7 @@ func (m *Manager) pump(l *live) { slog.Error("drop stage output", "session", l.id, "err", err) } } - next, err := m.store.EndTurn(ctx, l.id, agentActor, map[string]any{"error": "agent process exited mid-turn"}) + next, err := m.store.EndTurn(ctx, l.id, agentActor, map[string]any{"error": "agent process exited mid-turn"}, nil, false) if err != nil { slog.Error("record session exit", "session", l.id, "err", err) } @@ -848,18 +848,10 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error { case finished && !compacting && !asked: payload["summary"] = true } - err = m.emit(ctx, l, agentActor, "turn.ended", payload) - if err != nil { - return err - } discard, err := m.store.GetDiscard(ctx, l.taskID) if err != nil { return err } - go m.closeMergedSubtasks(l.taskID, l.dir) - if payload["summary"] == true && discard.State == store.DiscardNone { - go m.reportToParent(l.taskID, res.result) - } mergingBy := "" if finished && !handoff { mergingBy, err = m.store.MergingBy(ctx, l.taskID) @@ -867,8 +859,11 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error { return err } } - if discard.State == store.DiscardNone { - m.snapshotSoon(l, api.SnapshotTriggerTurnEnd, 0, mergingBy == "") + snapshot := discard.State == store.DiscardNone + review := snapshot && mergingBy == "" + go m.closeMergedSubtasks(l.taskID, l.dir) + if payload["summary"] == true && snapshot { + go m.reportToParent(l.taskID, res.result) } if res.window > 0 { @@ -900,10 +895,14 @@ func (m *Manager) turnEnded(ctx context.Context, l *live, res endResult) error { } } - next, err := m.store.EndTurn(ctx, l.id, agentActor, nil) + turn := m.event(l, agentActor, "turn.ended", payload) + next, err := m.store.EndTurn(ctx, l.id, agentActor, nil, &turn, review) if err != nil { return err } + if snapshot { + m.snapshotSoon(l, api.SnapshotTriggerTurnEnd, 0, review) + } switch next { case store.DiscardClosing: diff --git a/internal/agents/review.go b/internal/agents/review.go index 3e5cbbd..b46ed4f 100644 --- a/internal/agents/review.go +++ b/internal/agents/review.go @@ -471,6 +471,10 @@ func (m *Manager) cancelReviews(ctx context.Context, task api.Task, project api. } func (m *Manager) recoverReviews(ctx context.Context) error { + err := m.store.ClearReviewRequests(ctx) + if err != nil { + return err + } running, err := m.store.RunningReviews(ctx) if err != nil { return err diff --git a/internal/agents/snapshots.go b/internal/agents/snapshots.go index 049f46c..2397ccf 100644 --- a/internal/agents/snapshots.go +++ b/internal/agents/snapshots.go @@ -43,10 +43,12 @@ func (m *Manager) snapshotSoon(l *live, trigger api.SnapshotTrigger, seq int64, st := m.snapStateLocked(l.taskID) if st.closed { + m.dropRequest(l.taskID, &snapRequest{review: review}) return } req := &snapRequest{trigger: trigger, seq: seq, review: review} if st.running { + m.dropRequest(l.taskID, st.pending) st.pending = req return } @@ -66,8 +68,13 @@ func (m *Manager) runSnapshots(l *live, st *snapState, req *snapRequest) { slog.Error("take snapshot", "task", l.taskID, "trigger", req.trigger, "err", err) } if req.review { - go m.afterSnapshot(snap) + go func() { + m.afterSnapshot(snap) + m.endReviewRequest(l.taskID) + }() } + } else { + m.dropRequest(l.taskID, req) } st.mu.Unlock() @@ -75,6 +82,7 @@ func (m *Manager) runSnapshots(l *live, st *snapState, req *snapRequest) { req = st.pending st.pending = nil if req == nil || st.closed { + m.dropRequest(l.taskID, req) st.running = false m.snapMu.Unlock() return @@ -83,6 +91,19 @@ func (m *Manager) runSnapshots(l *live, st *snapState, req *snapRequest) { } } +func (m *Manager) dropRequest(taskID string, req *snapRequest) { + if req != nil && req.review { + m.endReviewRequest(taskID) + } +} + +func (m *Manager) endReviewRequest(taskID string) { + err := m.store.EndReviewRequest(context.Background(), taskID) + if err != nil { + slog.Error("end review request", "task", taskID, "err", err) + } +} + func (m *Manager) takeSnapshot(ctx context.Context, l *live, trigger api.SnapshotTrigger, seq int64) (*api.Snapshot, error) { if seq == 0 { var err error diff --git a/internal/api/api.gen.go b/internal/api/api.gen.go index 1f62026..61e0db1 100644 --- a/internal/api/api.gen.go +++ b/internal/api/api.gen.go @@ -366,6 +366,45 @@ func (e StageAttemptState) Valid() bool { } } +// Defines values for TaskState. +const ( + TaskStateDiscarded TaskState = "discarded" + TaskStateDiscarding TaskState = "discarding" + TaskStateIdle TaskState = "idle" + TaskStateMerged TaskState = "merged" + TaskStateMerging TaskState = "merging" + TaskStateNoAgent TaskState = "no_agent" + TaskStateReviewPending TaskState = "review_pending" + TaskStateReviewing TaskState = "reviewing" + TaskStateRunning TaskState = "running" +) + +// Valid indicates whether the value is a known member of the TaskState enum. +func (e TaskState) Valid() bool { + switch e { + case TaskStateDiscarded: + return true + case TaskStateDiscarding: + return true + case TaskStateIdle: + return true + case TaskStateMerged: + return true + case TaskStateMerging: + return true + case TaskStateNoAgent: + return true + case TaskStateReviewPending: + return true + case TaskStateReviewing: + return true + case TaskStateRunning: + return true + default: + return false + } +} + // Defines values for TaskStatus. const ( TaskStatusActive TaskStatus = "active" @@ -1054,9 +1093,9 @@ type Task struct { PortBase int `json:"port_base"` ProjectId string `json:"project_id"` - // Reviewing Whether a review of one of the task's snapshots is running. - Reviewing bool `json:"reviewing"` - Status TaskStatus `json:"status"` + // State What the task is doing, kept by the store in the same transaction as every change to it (a task.state event carries each change): merged or discarded once closed, else discarding, merging, the state of its agent's latest live session when that isn't idle, reviewing while a review runs, review_pending between a turn's end and its snapshot's review starting or being skipped, idle, or no_agent. + State TaskState `json:"state"` + Status TaskStatus `json:"status"` // SummarySeq The seq of the task's latest turn.ended event with summary: true; 0 when it has none. SummarySeq int64 `json:"summary_seq"` @@ -1076,6 +1115,9 @@ type Task struct { WorktreePath string `json:"worktree_path"` } +// TaskState What the task is doing, kept by the store in the same transaction as every change to it (a task.state event carries each change): merged or discarded once closed, else discarding, merging, the state of its agent's latest live session when that isn't idle, reviewing while a review runs, review_pending between a turn's end and its snapshot's review starting or being skipped, idle, or no_agent. +type TaskState string + // TaskStatus defines model for Task.Status. type TaskStatus string @@ -3391,118 +3433,121 @@ var swaggerSpec = []string{ "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", } // decodeSpec returns the embedded OpenAPI spec as raw JSON bytes, diff --git a/internal/server/server.go b/internal/server/server.go index 83659e5..a2e462b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -79,12 +79,16 @@ func New(dataDir string, portFirst int, spa fs.FS) (*Server, error) { return s, nil } -func (s *Server) publish(ev api.Event) { - s.hub.publish(ev) - s.agents.Observe(ev) - if (ev.Kind == "task.merged" || ev.Kind == "task.discarded") && ev.TaskId != nil { - s.terminals.close(*ev.TaskId) - go s.agents.Unexpose(context.Background(), *ev.TaskId) +func (s *Server) publish(events []api.Event) { + for _, ev := range events { + s.hub.publish(ev) + } + s.agents.Observe(events) + for _, ev := range events { + if (ev.Kind == "task.merged" || ev.Kind == "task.discarded") && ev.TaskId != nil { + s.terminals.close(*ev.TaskId) + go s.agents.Unexpose(context.Background(), *ev.TaskId) + } } } diff --git a/internal/store/discard.go b/internal/store/discard.go index 5cd2255..908fe74 100644 --- a/internal/store/discard.go +++ b/internal/store/discard.go @@ -114,7 +114,7 @@ func (s *Store) StartDiscardTurn(ctx context.Context, taskID, sessionID, actor s }) } -func (s *Store) EndTurn(ctx context.Context, sessionID, actor string, payload map[string]any) (DiscardState, error) { +func (s *Store) EndTurn(ctx context.Context, sessionID, actor string, payload map[string]any, turn *api.Event, reviewRequest bool) (DiscardState, error) { next := DiscardNone err := s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { taskID := "" @@ -133,6 +133,18 @@ func (s *Store) EndTurn(ctx context.Context, sessionID, actor string, payload ma return err } + if turn != nil { + err = emit(*turn) + if err != nil { + return err + } + } + if reviewRequest { + _, err = tx.ExecContext(ctx, `UPDATE tasks SET review_requests = review_requests + 1 WHERE id = ?`, taskID) + if err != nil { + return err + } + } _, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(api.AgentSessionStateIdle), sessionID) if err != nil { return err diff --git a/internal/store/store.go b/internal/store/store.go index cd73594..2676b32 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -446,6 +446,10 @@ CREATE TABLE summary_reads ( seq INTEGER NOT NULL, PRIMARY KEY (login, task_id) ); +`}, {"add-task-state", ` +ALTER TABLE tasks ADD COLUMN state TEXT NOT NULL DEFAULT ''; +ALTER TABLE tasks ADD COLUMN review_requests INTEGER NOT NULL DEFAULT 0; +UPDATE tasks SET state = ` + taskStateExpr + `; `}} const ( @@ -459,10 +463,10 @@ type Store struct { path string portFirst int writeMu sync.Mutex - onEvent func(api.Event) + onEvent func([]api.Event) } -func Open(path string, portFirst int, onEvent func(api.Event)) (*Store, error) { +func Open(path string, portFirst int, onEvent func([]api.Event)) (*Store, error) { dsn := "file:" + path + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_txlock=immediate" db, err := sql.Open("sqlite", dsn) if err != nil { @@ -1253,7 +1257,7 @@ func (s *Store) Broadcast(ev api.Event) { s.writeMu.Lock() defer s.writeMu.Unlock() ev.CreatedAt = time.Now().UTC() - s.onEvent(ev) + s.onEvent([]api.Event{ev}) } func (s *Store) CreateSession(ctx context.Context, sess api.AgentSession, projectID string) error { @@ -1521,20 +1525,125 @@ func (s *Store) write(ctx context.Context, fn func(tx *sql.Tx, emit emitFunc) er return err } + touched := []string{} + seen := map[string]bool{} + for _, ev := range events { + if ev.TaskId == nil || seen[*ev.TaskId] { + continue + } + seen[*ev.TaskId] = true + touched = append(touched, *ev.TaskId) + } + for _, taskID := range touched { + actor := "byre" + for _, ev := range events { + if ev.TaskId != nil && *ev.TaskId == taskID { + actor = ev.Actor + } + } + err = settleTaskState(ctx, tx, emit, taskID, actor) + if err != nil { + return err + } + } + err = tx.Commit() if err != nil { return err } - for _, ev := range events { - s.onEvent(ev) - } + s.onEvent(events) return nil } +func settleTaskState(ctx context.Context, tx *sql.Tx, emit emitFunc, taskID, actor string) error { + projectID := "" + before := "" + now := "" + err := tx.QueryRowContext(ctx, `SELECT project_id, state, `+taskStateExpr+` FROM tasks WHERE id = ?`, taskID).Scan(&projectID, &before, &now) + if errors.Is(err, sql.ErrNoRows) { + return nil + } + if err != nil { + return err + } + if before == now { + return nil + } + _, err = tx.ExecContext(ctx, `UPDATE tasks SET state = ? WHERE id = ?`, now, taskID) + if err != nil { + return err + } + return emit(api.Event{ + Kind: "task.state", + ProjectId: &projectID, + TaskId: &taskID, + Actor: actor, + Payload: map[string]any{"from": before, "to": now}, + }) +} + +const taskStateExpr = `CASE + WHEN tasks.status != 'active' THEN tasks.status + WHEN tasks.discard_state != '' THEN 'discarding' + WHEN tasks.merging_by != '' THEN 'merging' + ELSE coalesce(( + SELECT CASE + WHEN s.state != 'idle' THEN s.state + WHEN EXISTS (SELECT 1 FROM reviews r WHERE r.task_id = tasks.id AND r.state = 'running') THEN 'reviewing' + WHEN tasks.review_requests > 0 THEN 'review_pending' + ELSE 'idle' + END + FROM agent_sessions s + WHERE s.task_id = tasks.id AND s.role != 'reviewer' AND s.state != 'completed' + ORDER BY s.created_at DESC LIMIT 1 + ), 'no_agent') +END` + +func (s *Store) EndReviewRequest(ctx context.Context, taskID string) error { + return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + _, err := tx.ExecContext(ctx, `UPDATE tasks SET review_requests = max(review_requests - 1, 0) WHERE id = ?`, taskID) + if err != nil { + return err + } + return settleTaskState(ctx, tx, emit, taskID, "byre") + }) +} + +func (s *Store) ClearReviewRequests(ctx context.Context) error { + return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error { + rows, err := tx.QueryContext(ctx, `UPDATE tasks SET review_requests = 0 WHERE review_requests > 0 RETURNING id`) + if err != nil { + return err + } + ids := []string{} + for rows.Next() { + id := "" + err = rows.Scan(&id) + if err != nil { + rows.Close() + return err + } + ids = append(ids, id) + } + rows.Close() + err = rows.Err() + if err != nil { + return err + } + for _, id := range ids { + err = settleTaskState(ctx, tx, emit, id, "byre") + if err != nil { + return err + } + } + return nil + }) +} + const projectColumns = `id, name, repo_path, default_branch, remote, created_by, created_at` -const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at, workflow_name, workflow_run, workflow_stage, parent_task_id, parent_merge_by, EXISTS (SELECT 1 FROM reviews WHERE reviews.task_id = tasks.id AND reviews.state = 'running'), merging_by != '', discard_state != '' AND status = 'active', (SELECT coalesce(max(seq), 0) FROM events WHERE events.task_id = tasks.id AND kind = 'turn.ended' AND json_extract(payload, '$.summary') = 1)` +const taskColumns = `id, project_id, title, merge_title, branch, worktree_path, base_ref, port_base, color, status, created_by, created_at, closed_at, merge_commit, todo_item, forked_from, forked_at, merge_queued_at, workflow_name, workflow_run, workflow_stage, parent_task_id, parent_merge_by, state, merging_by != '', discard_state != '' AND status = 'active', (SELECT coalesce(max(seq), 0) FROM events WHERE events.task_id = tasks.id AND kind = 'turn.ended' AND json_extract(payload, '$.summary') = 1)` const sessionColumns = `id, coalesce(task_id, ''), owner, vendor, model, vendor_session_id, state, role, created_by, created_at, forked_from, forked_at` @@ -1568,10 +1677,12 @@ func scanTask(row scanner) (api.Task, error) { workflowStage := "" parentTaskID := "" parentMergeBy := "" - err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &t.Color, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt, &mergeQueuedAt, &workflowName, &workflowRun, &workflowStage, &parentTaskID, &parentMergeBy, &t.Reviewing, &t.Merging, &t.Discarding, &t.SummarySeq) + taskState := "" + err := row.Scan(&t.Id, &t.ProjectId, &t.Title, &t.MergeTitle, &t.Branch, &t.WorktreePath, &t.BaseRef, &t.PortBase, &t.Color, &status, &t.CreatedBy, &createdAt, &closedAt, &mergeCommit, &todoItem, &forkedFrom, &forkedAt, &mergeQueuedAt, &workflowName, &workflowRun, &workflowStage, &parentTaskID, &parentMergeBy, &taskState, &t.Merging, &t.Discarding, &t.SummarySeq) if err != nil { return t, err } + t.State = api.TaskState(taskState) if parentTaskID != "" { t.ParentTaskId = &parentTaskID } diff --git a/web/dist/state.js b/web/dist/state.js index 975e48f..30b964f 100644 --- a/web/dist/state.js +++ b/web/dist/state.js @@ -56,6 +56,12 @@ export function findTask(id) { export function currentSession(taskId) { return state.sessions.get(taskId)?.findLast((s) => s.state !== "completed") ?? state.sessions.get(taskId)?.at(-1); } +const workingTitles = { + running: "The agent is mid-turn", + review_pending: "A review of the turn's work is about to start", + reviewing: "A review is running", + discarding: "Discarding", +}; export function taskState(task) { const open = state.inbox.filter((item) => item.task_id === task.id); if (open.some((item) => item.kind === "question")) { @@ -73,8 +79,9 @@ export function taskState(task) { if (open.length > 0) { return { kind: "error", label: "Error", title: "The agent's session failed or hit a rate limit; see the inbox" }; } - if (task.discarding || task.reviewing || currentSession(task.id)?.state === "running") { - return { kind: "working", label: "Working", title: task.discarding ? "Discarding" : task.reviewing ? "A review is running" : "The agent is mid-turn" }; + const working = workingTitles[task.state]; + if (working !== undefined) { + return { kind: "working", label: "Working", title: working }; } return { kind: "idle", label: "Idle", title: "Waiting for a message" }; } @@ -360,6 +367,7 @@ export function handleEvent(ev) { case "task.discard_started": case "task.merged": case "task.discarded": + case "task.state": if (ev.project_id) { void loadTasks(ev.project_id); } @@ -395,9 +403,6 @@ export function handleEvent(ev) { break; case "review.started": case "review.finished": - if (ev.project_id) { - void loadTasks(ev.project_id); - } if (state.inbox.some((i) => i.task_id === ev.task_id && i.kind === "question")) { void loadInbox(); } diff --git a/web/dist/state.js.map b/web/dist/state.js.map index 41fb235..1295cb9 100644 --- a/web/dist/state.js.map +++ b/web/dist/state.js.map @@ -1 +1 @@ -{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/src/api.gen.ts b/web/src/api.gen.ts index 2b95793..6d8cc68 100644 --- a/web/src/api.gen.ts +++ b/web/src/api.gen.ts @@ -982,8 +982,11 @@ export interface components { workflow_run?: string; /** @description With workflow: the name of the stage this task runs. */ workflow_stage?: string; - /** @description Whether a review of one of the task's snapshots is running. */ - reviewing: boolean; + /** + * @description What the task is doing, kept by the store in the same transaction as every change to it (a task.state event carries each change): merged or discarded once closed, else discarding, merging, the state of its agent's latest live session when that isn't idle, reviewing while a review runs, review_pending between a turn's end and its snapshot's review starting or being skipped, idle, or no_agent. + * @enum {string} + */ + state: "running" | "idle" | "review_pending" | "reviewing" | "merging" | "discarding" | "merged" | "discarded" | "no_agent"; /** @description Whether a Merge was started and its turn hasn't ended yet. */ merging: boolean; /** @description Whether a Discard was started and the task isn't closed yet: its summary turn is waiting for the running turn, running, or done and the task is being removed. */ diff --git a/web/src/state.ts b/web/src/state.ts index f6c7805..a415351 100644 --- a/web/src/state.ts +++ b/web/src/state.ts @@ -88,6 +88,13 @@ export function currentSession(taskId: string): AgentSession | undefined { return state.sessions.get(taskId)?.findLast((s) => s.state !== "completed") ?? state.sessions.get(taskId)?.at(-1); } +const workingTitles: Partial> = { + running: "The agent is mid-turn", + review_pending: "A review of the turn's work is about to start", + reviewing: "A review is running", + discarding: "Discarding", +}; + export type TaskState = { kind: "answer" | "merging" | "held" | "error" | "working" | "idle"; label: string; title: string }; export function taskState(task: Task): TaskState { @@ -107,8 +114,9 @@ export function taskState(task: Task): TaskState { if (open.length > 0) { return { kind: "error", label: "Error", title: "The agent's session failed or hit a rate limit; see the inbox" }; } - if (task.discarding || task.reviewing || currentSession(task.id)?.state === "running") { - return { kind: "working", label: "Working", title: task.discarding ? "Discarding" : task.reviewing ? "A review is running" : "The agent is mid-turn" }; + const working = workingTitles[task.state]; + if (working !== undefined) { + return { kind: "working", label: "Working", title: working }; } return { kind: "idle", label: "Idle", title: "Waiting for a message" }; } @@ -434,6 +442,7 @@ export function handleEvent(ev: ByreEvent): void { case "task.discard_started": case "task.merged": case "task.discarded": + case "task.state": if (ev.project_id) { void loadTasks(ev.project_id); } @@ -468,9 +477,6 @@ export function handleEvent(ev: ByreEvent): void { break; case "review.started": case "review.finished": - if (ev.project_id) { - void loadTasks(ev.project_id); - } if (state.inbox.some((i) => i.task_id === ev.task_id && i.kind === "question")) { void loadInbox(); }