Make task state store-owned and signal its changes to the lead

This commit is contained in:
Ian Gulliver
2026-10-16 11:15:32 -07:00
parent 34db128afe
commit d26c81ceb1
15 changed files with 451 additions and 199 deletions
+5 -2
View File
@@ -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. */
+11 -5
View File
@@ -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<Record<Task["state"], string>> = {
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();
}