feat: add revisioned worker execution state

This commit is contained in:
2026-09-06 06:54:29 +09:00
parent 3ed1545c3c
commit e8b9adcde4
32 changed files with 2168 additions and 750 deletions
+39 -3
View File
@@ -10,6 +10,37 @@ export type CompletionKind = "file";
export type WorkerStatus = "idle" | "running" | "paused" | "stopped";
export type WorkerCommandEnvelope = {
/**
* Caller-owned sequence. A controller accepts command ids in strictly
* increasing order for one execution generation.
*/
command_id: number, expected_execution_generation: number, expected_worker_state_revision: number, };
export type WorkerCommandKind = "resume" | "cancel" | "pause" | "compact" | "shutdown";
export type WorkerCommandDisposition = "accepted" | "stale_execution_generation" | "stale_worker_state_revision" | "stale_command_id" | "invalid_state";
export type WorkerCommandAcknowledgement = { command_id: number, command: WorkerCommandKind, disposition: WorkerCommandDisposition,
/**
* The complete authoritative state observed after command admission.
*/
state: WorkerStateSnapshot, };
export type WorkerRunState = "running" | "pausing" | "paused" | "cancelling";
export type WorkerMaintenanceState = "compacting";
export type WorkerBusyState = { "kind": "run", "state": WorkerRunState } | { "kind": "maintenance", "state": WorkerMaintenanceState };
export type WorkerState = { "kind": "idle" } | { "kind": "busy", "state": WorkerBusyState };
export type WorkerStateSnapshot = { execution_generation: number, revision: number,
/**
* Highest lifecycle command id observed by this controller generation.
*/
last_command_id: number, state: WorkerState, };
export type TurnResult = "finished" | "paused";
export type InvokeKind = "user_send" | "notify" | "worker_event" | "system_reminder" | "wakeup";
@@ -231,7 +262,7 @@ export type SubscriptionFramePayload = { "frame": "request", "message": Subscrip
export type SubscriptionFrame = { protocol_version: number, } & ({ "frame": "request", "message": SubscriptionRequest } | { "frame": "response", "message": SubscriptionResponse } | { "frame": "event", "message": SubscriptionEvent } | { "frame": "worker_protocol", "message": SubscriptionWorkerProtocolMethod });
export type Method = { "method": "submit", "params": { submission_request_id: string, input: Array<Segment>, } } | { "method": "notify", "params": { notification_request_id: string, message: string, auto_run?: boolean, } } | { "method": "worker_event", "params": WorkerEvent } | { "method": "list_pending_submissions" } | { "method": "cancel_pending_submission", "params": { submission_id: string, expected_revision: number, } } | { "method": "clear_pending_submissions", "params": { expected_revision: number, } } | { "method": "continue_pending", "params": { expected_revision: number, expected_head_id: string, } } | { "method": "resume" } | { "method": "cancel" } | { "method": "pause" } | { "method": "compact" } | { "method": "list_rewind_targets" } | { "method": "rewind_to", "params": { target: RewindTargetId, expected_head_entries: number, } } | { "method": "shutdown" } | { "method": "list_completions", "params": { kind: CompletionKind, prefix: string, } } | { "method": "list_workers" } | { "method": "restore_worker", "params": { name: string, } } | { "method": "register_peer", "params": { name: string, } };
export type Method = { "method": "submit", "params": { submission_request_id: string, input: Array<Segment>, } } | { "method": "notify", "params": { notification_request_id: string, message: string, auto_run?: boolean, } } | { "method": "worker_event", "params": WorkerEvent } | { "method": "list_pending_submissions" } | { "method": "cancel_pending_submission", "params": { submission_id: string, expected_revision: number, } } | { "method": "clear_pending_submissions", "params": { expected_revision: number, } } | { "method": "continue_pending", "params": { expected_revision: number, expected_head_id: string, } } | { "method": "resume", "params": { command: WorkerCommandEnvelope, } } | { "method": "cancel", "params": { command: WorkerCommandEnvelope, } } | { "method": "pause", "params": { command: WorkerCommandEnvelope, } } | { "method": "compact", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_rewind_targets" } | { "method": "rewind_to", "params": { target: RewindTargetId, expected_head_entries: number, } } | { "method": "shutdown", "params": { command: WorkerCommandEnvelope, } } | { "method": "list_completions", "params": { kind: CompletionKind, prefix: string, } } | { "method": "list_workers" } | { "method": "restore_worker", "params": { name: string, } } | { "method": "register_peer", "params": { name: string, } };
export type Event = { "event": "submission_accepted", "data": { submission_request_id: string, submission_id: string, disposition: SubmissionDisposition, } } | { "event": "submission_rejected", "data": { submission_request_id: string, message: string, } } | { "event": "pending_submissions_changed", "data": { pending: PendingSubmissionsSnapshot, } } | { "event": "user_message", "data": { segments: Array<Segment>, } } | { "event": "system_item", "data": { item: unknown, } } | { "event": "invoke_start", "data": { kind: InvokeKind, } } | { "event": "turn_start", "data": { turn: number, } } | { "event": "turn_end", "data": { turn: number, result: TurnResult, } } | { "event": "llm_call_start", "data": { llm_call: number, } } | { "event": "llm_call_end", "data": { llm_call: number, } } | { "event": "llm_retry", "data": { llm_call: number,
/**
@@ -247,7 +278,12 @@ summary: string,
* Full tool output. Absent when the tool chose to return
* summary-only, or when the result was pruned.
*/
output?: string | null, disposition?: ToolResultDisposition | null, is_error: boolean, } } | { "event": "usage", "data": { input_tokens: number | null, output_tokens: number | null, cache_read_input_tokens?: number | null, } } | { "event": "run_end", "data": { result: RunResult, } } | { "event": "error", "data": { code: ErrorCode, message: string, } } | { "event": "snapshot", "data": { session: SessionSnapshot, greeting: Greeting, status: WorkerStatus,
output?: string | null, disposition?: ToolResultDisposition | null, is_error: boolean, } } | { "event": "usage", "data": { input_tokens: number | null, output_tokens: number | null, cache_read_input_tokens?: number | null, } } | { "event": "run_end", "data": { result: RunResult, } } | { "event": "error", "data": { code: ErrorCode, message: string, } } | { "event": "snapshot", "data": { session: SessionSnapshot, greeting: Greeting,
/**
* Full revisioned live execution state. `Stopped` remains Runtime
* catalog authority and is deliberately not represented here.
*/
state: WorkerStateSnapshot,
/**
* Unfinished model output that has already streamed in the current
* run but is not yet represented by committed snapshot entries.
@@ -257,4 +293,4 @@ in_flight?: InFlightSnapshot,
* Parent-owned Internal Worker sessions visible to this client.
* Service-private Internal Workers are deliberately excluded.
*/
internal_workers?: Array<InternalWorkerSnapshot>, } } | { "event": "internal_worker", "data": { worker: InternalWorkerRef, revision: number, event: Event, } } | { "event": "internal_worker_removed", "data": { worker: InternalWorkerRef, revision: number, } } | { "event": "segment_rotated", "data": { session: SessionSnapshot, } } | { "event": "status", "data": { status: WorkerStatus, } } | { "event": "command", "data": { event: CommandEvent, } } | { "event": "completions", "data": { kind: CompletionKind, entries: Array<CompletionEntry>, } } | { "event": "rewind_targets", "data": { head_entries: number, targets: Array<RewindTarget>, } } | { "event": "rewind_applied", "data": { session: SessionSnapshot, input: Array<Segment>, summary: RewindSummary, } } | { "event": "workers_listed", "data": { workers: unknown, } } | { "event": "worker_restored", "data": { result: unknown, } } | { "event": "peer_registered", "data": { result: unknown, } } | { "event": "alert", "data": Alert } | { "event": "memory_worker", "data": MemoryWorkerEvent } | { "event": "compact_start", "data": { lifecycle: CompactionLifecycle, } } | { "event": "compact_done", "data": { lifecycle: CompactionLifecycle, } } | { "event": "compact_failed", "data": { lifecycle: CompactionLifecycle, } } | { "event": "shutdown" };
internal_workers?: Array<InternalWorkerSnapshot>, } } | { "event": "internal_worker", "data": { worker: InternalWorkerRef, revision: number, event: Event, } } | { "event": "internal_worker_removed", "data": { worker: InternalWorkerRef, revision: number, } } | { "event": "segment_rotated", "data": { session: SessionSnapshot, } } | { "event": "worker_state", "data": { snapshot: WorkerStateSnapshot, } } | { "event": "command_acknowledged", "data": { acknowledgement: WorkerCommandAcknowledgement, } } | { "event": "command", "data": { event: CommandEvent, } } | { "event": "completions", "data": { kind: CompletionKind, entries: Array<CompletionEntry>, } } | { "event": "rewind_targets", "data": { head_entries: number, targets: Array<RewindTarget>, } } | { "event": "rewind_applied", "data": { session: SessionSnapshot, input: Array<Segment>, summary: RewindSummary, } } | { "event": "workers_listed", "data": { workers: unknown, } } | { "event": "worker_restored", "data": { result: unknown, } } | { "event": "peer_registered", "data": { result: unknown, } } | { "event": "alert", "data": Alert } | { "event": "memory_worker", "data": MemoryWorkerEvent } | { "event": "compact_start", "data": { lifecycle: CompactionLifecycle, } } | { "event": "compact_done", "data": { lifecycle: CompactionLifecycle, } } | { "event": "compact_failed", "data": { lifecycle: CompactionLifecycle, } } | { "event": "shutdown" };
@@ -1,4 +1,4 @@
import type { Event } from "$lib/generated/protocol";
import type { Event, WorkerStateSnapshot, WorkerStatus } from "$lib/generated/protocol";
import {
type ConsoleEventInput,
type ConsoleLine,
@@ -19,6 +19,23 @@ declare const Deno: {
test(name: string, fn: () => void): void;
};
function workerState(status: WorkerStatus): WorkerStateSnapshot {
return {
execution_generation: 1,
revision: status === "idle" ? 0 : 1,
last_command_id: 0,
state: status === "idle"
? { kind: "idle" }
: {
kind: "busy",
state: {
kind: "run",
state: status === "paused" ? "paused" : "running",
},
},
};
}
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
@@ -131,7 +148,7 @@ function snapshotEvent(cwd: string, entries: unknown[] = []): Event {
context_window: 100,
context_tokens: 20,
},
status: "idle",
state: workerState("idle"),
in_flight: { blocks: [] },
},
};
@@ -213,7 +230,7 @@ Deno.test("snapshot replaces a live error with one durable run_errored row", ()
},
{
eventId: "idle-after-error",
event: { event: "status", data: { status: "idle" } } satisfies Event,
event: { event: "worker_state", data: { snapshot: workerState("idle") } } satisfies Event,
},
]);
@@ -653,7 +670,7 @@ Deno.test("projectConsole streams distinct Bash stdout and stderr through termin
Deno.test("snapshot restores bounded in-flight Bash command output", () => {
const snapshot = snapshotEvent("/repo");
if (snapshot.event !== "snapshot") throw new Error("snapshot fixture expected");
snapshot.data.status = "running";
snapshot.data.state = workerState("running");
snapshot.data.in_flight = {
blocks: [{
kind: "tool_call",
@@ -1403,7 +1420,7 @@ Deno.test("projectConsole hides lifecycle events and renders system items", () =
const projection = projectConsole([
{
eventId: "30",
event: { event: "status", data: { status: "running" } } satisfies Event,
event: { event: "worker_state", data: { snapshot: workerState("running") } } satisfies Event,
},
{
eventId: "31",
@@ -1527,7 +1544,7 @@ Deno.test("projectConsole renders snapshot entries and in-flight output", () =>
context_window: 100,
context_tokens: 20,
},
status: "running",
state: workerState("running"),
in_flight: {
blocks: [
{ kind: "text", text: "partial" },
@@ -1578,7 +1595,7 @@ Deno.test("projectConsole restores system items from snapshot entries", () => {
context_window: 100,
context_tokens: 20,
},
status: "idle",
state: workerState("idle"),
},
} satisfies Event,
}]);
@@ -1922,7 +1939,7 @@ Deno.test("console Worker views expose only direct Internal Workers", () => {
kind: "sub_worker",
},
revision: 1,
event: { event: "status", data: { status: "running" } },
event: { event: "worker_state", data: { snapshot: workerState("running") } },
},
},
},
@@ -1941,7 +1958,7 @@ Deno.test("console Worker views expose only direct Internal Workers", () => {
kind: "sub_worker",
},
revision: 1,
event: { event: "status", data: { status: "idle" } },
event: { event: "worker_state", data: { snapshot: workerState("idle") } },
},
},
}]);
@@ -2033,7 +2050,7 @@ Deno.test("parent snapshot authoritatively replaces Internal Worker projections"
kind: "sub_worker",
},
revision: 1,
event: { event: "status", data: { status: "running" } },
event: { event: "worker_state", data: { snapshot: workerState("running") } },
},
},
}]);
@@ -10,6 +10,8 @@ import type {
InternalWorkerRef,
InternalWorkerSnapshot,
Segment,
WorkerStateSnapshot,
WorkerStatus,
} from "$lib/generated/protocol";
import { stringify as stringifyYaml } from "yaml";
import { workspaceRoute } from "$lib/workspace/api/http";
@@ -169,6 +171,7 @@ export type ConsoleProjection = {
tasks: ConsoleTask[];
taskNextId: number;
status: string | null;
workerState: WorkerStateSnapshot | null;
usage: string | null;
runActivity: RunActivityStats;
cwd: string | null;
@@ -251,12 +254,22 @@ export function isConsoleProjectionEvent(event: ProtocolEvent): boolean {
return event.event !== "completions";
}
function workerStatusFromState(snapshot: WorkerStateSnapshot): WorkerStatus {
if (snapshot.state.kind === "idle") return "idle";
if (
snapshot.state.state.kind === "run" &&
snapshot.state.state.state === "paused"
) return "paused";
return "running";
}
export function emptyConsoleProjection(): ConsoleProjection {
return {
lines: [],
tasks: [],
taskNextId: 1,
status: null,
workerState: null,
usage: null,
runActivity: emptyRunActivityStats(),
cwd: null,
@@ -793,6 +806,7 @@ export function applyProtocolEvent(
tasks: [...projection.tasks],
taskNextId: projection.taskNextId,
status: projection.status,
workerState: projection.workerState,
usage: projection.usage,
runActivity: applyRunActivityEvent(
projection.runActivity,
@@ -903,7 +917,8 @@ export function applyProtocolEvent(
);
break;
case "snapshot": {
next.status = event.data.status;
next.workerState = event.data.state;
next.status = workerStatusFromState(event.data.state);
next.cwd = event.data.greeting.cwd;
const snapshot = snapshotProjectionFromSession(
envelope.eventId,
@@ -1000,8 +1015,13 @@ export function applyProtocolEvent(
if (existingIndex >= 0) next.internalWorkers.splice(existingIndex, 1);
break;
}
case "status":
next.status = event.data.status;
case "worker_state":
next.workerState = event.data.snapshot;
next.status = workerStatusFromState(event.data.snapshot);
break;
case "command_acknowledged":
next.workerState = event.data.acknowledgement.state;
next.status = workerStatusFromState(event.data.acknowledgement.state);
break;
case "command":
applyCommandEvent(next, envelope.eventId, event.data.event);
@@ -1939,6 +1959,7 @@ function snapshotProjectionFromSession(
tasks: [],
taskNextId: 1,
status: null,
workerState: null,
usage: null,
runActivity: emptyRunActivityStats(),
cwd,
@@ -75,7 +75,12 @@ Deno.test("new invoke and running snapshot reset run activity", () => {
data: {
entries: [],
greeting: { text: "", profile: "" },
status: "idle",
state: {
execution_generation: 1,
revision: 0,
last_command_id: 0,
state: { kind: "idle" },
},
in_flight: {},
internal_workers: [],
},
@@ -25,7 +25,9 @@ export function applyRunActivityEvent(
case "invoke_start":
return { ...emptyRunActivityStats(), startedAtMs: observedAtMs };
case "snapshot":
return event.data.status === "running"
return event.data.state.state.kind === "busy" &&
!(event.data.state.state.state.kind === "run" &&
event.data.state.state.state.state === "paused")
? { ...emptyRunActivityStats(), startedAtMs: observedAtMs }
: emptyRunActivityStats();
case "turn_start":
@@ -541,9 +541,42 @@
}
}
let nextWorkerCommandId = 1;
function lifecycleMethod(
command: "pause" | "cancel" | "resume" | "compact",
): ProtocolMethod | null {
const state = consoleProjection.workerState;
if (!state) {
sendError = "Worker state snapshot is not available; reconnect before sending control.";
return null;
}
const commandId = Math.max(
nextWorkerCommandId,
state.last_command_id + 1,
);
nextWorkerCommandId = commandId + 1;
const envelope = {
command_id: commandId,
expected_execution_generation: state.execution_generation,
expected_worker_state_revision: state.revision,
};
switch (command) {
case "pause":
return { method: "pause", params: { command: envelope } };
case "cancel":
return { method: "cancel", params: { command: envelope } };
case "resume":
return { method: "resume", params: { command: envelope } };
case "compact":
return { method: "compact", params: { command: envelope } };
}
}
function sendWorkerControl(command: "pause" | "cancel" | "resume") {
const label = command[0].toUpperCase() + command.slice(1);
sendControl({ method: command }, label);
const method = lifecycleMethod(command);
if (method) sendControl(method, label);
}
function isEditableTarget(target: EventTarget | null): boolean {
@@ -627,8 +660,11 @@
auto_run: true,
},
};
case "compact":
return { method: "compact" };
case "compact": {
const method = lifecycleMethod("compact");
if (!method) throw new Error("Worker state snapshot is not available");
return method;
}
case "list_rewind_targets":
return { method: "list_rewind_targets" };
case "register_peer":
@@ -691,7 +727,7 @@
function handleComposerSubmit() {
if (workerRunning) {
sendControl({ method: "cancel" }, "Stop");
sendWorkerControl("cancel");
return;
}
void submitDraft(composerInputElement?.snapshot() ?? draft);
@@ -894,8 +930,26 @@
): string | null {
switch (event.event) {
case "snapshot":
case "status":
return event.data.status;
return event.data.state.state.kind === "idle"
? "idle"
: event.data.state.state.state.kind === "run" &&
event.data.state.state.state.state === "paused"
? "paused"
: "running";
case "worker_state":
return event.data.snapshot.state.kind === "idle"
? "idle"
: event.data.snapshot.state.state.kind === "run" &&
event.data.snapshot.state.state.state === "paused"
? "paused"
: "running";
case "command_acknowledged":
return event.data.acknowledgement.state.state.kind === "idle"
? "idle"
: event.data.acknowledgement.state.state.state.kind === "run" &&
event.data.acknowledgement.state.state.state.state === "paused"
? "paused"
: "running";
case "shutdown":
return "shutdown";
default:
@@ -1620,7 +1674,10 @@
type="button"
class="secondary-button"
disabled={protocolState !== "open"}
onclick={() => sendControl({ method: "compact" }, "Compact")}
onclick={() => {
const method = lifecycleMethod("compact");
if (method) sendControl(method, "Compact");
}}
>
Compact
</button>