feat(session): expose canonical public snapshots
This commit is contained in:
@@ -73,11 +73,35 @@ export type InFlightBlock = { "kind": "text", text: string, finished?: boolean,
|
||||
|
||||
export type InFlightSnapshot = { blocks?: Array<InFlightBlock>, commands?: Array<CommandSnapshot>, };
|
||||
|
||||
export type SessionEntryProvenance = "human_input" | "worker_input" | "flow_instruction" | "backend_instruction" | "model_output" | "tool_output" | "derived_summary" | "legacy_unknown";
|
||||
|
||||
export type SessionMessageRole = "user" | "assistant";
|
||||
|
||||
export type SessionContentPart = { "kind": "text", text: string, } | { "kind": "refusal", refusal: string, };
|
||||
|
||||
export type SessionToolAttachment = { media_type: string,
|
||||
/**
|
||||
* Base64-encoded durable attachment body. Public snapshots preserve the
|
||||
* committed multimodal value instead of replacing it with placeholder text.
|
||||
*/
|
||||
data_base64: string, };
|
||||
|
||||
export type SessionSnapshotEntryData = { "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_error", message: string, };
|
||||
|
||||
export type SessionSnapshotEntry = {
|
||||
/**
|
||||
* Stable identity from durable history metadata, or a deterministic
|
||||
* identity derived from the legacy segment and log position.
|
||||
*/
|
||||
entry_id: string, provenance: SessionEntryProvenance, derived_from?: Array<string>, } & ({ "kind": "user_input", segments: Array<Segment>, } | { "kind": "message", role: SessionMessageRole, content: Array<SessionContentPart>, } | { "kind": "tool_call", call_id: string, name: string, arguments: string, } | { "kind": "tool_result", call_id: string, summary: string, content?: string | null, is_error: boolean, attachments?: Array<SessionToolAttachment>, } | { "kind": "system_item", item_kind: string, content: string, data?: unknown, } | { "kind": "run_error", message: string, });
|
||||
|
||||
export type SessionSnapshot = { entries: Array<SessionSnapshotEntry>, };
|
||||
|
||||
export type InternalWorkerKind = "sub_worker" | { "service": { kind: string, } };
|
||||
|
||||
export type InternalWorkerRef = { session_id: string, name: string, parent_session_id?: string | null, kind: InternalWorkerKind, };
|
||||
|
||||
export type InternalWorkerSnapshot = { worker: InternalWorkerRef, revision: number, entries: Array<unknown>, status: WorkerStatus, error?: string | null, in_flight?: InFlightSnapshot, internal_workers?: Array<InternalWorkerSnapshot>, };
|
||||
export type InternalWorkerSnapshot = { worker: InternalWorkerRef, revision: number, session: SessionSnapshot, status: WorkerStatus, error?: string | null, in_flight?: InFlightSnapshot, internal_workers?: Array<InternalWorkerSnapshot>, };
|
||||
|
||||
export type Greeting = { worker_name: string, cwd: string, provider: string, model: string, scope_summary: string, tools: Array<string>,
|
||||
/**
|
||||
@@ -193,7 +217,7 @@ 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": { entries: Array<unknown>, 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, status: WorkerStatus,
|
||||
/**
|
||||
* Unfinished model output that has already streamed in the current
|
||||
* run but is not yet represented by committed snapshot entries.
|
||||
@@ -203,4 +227,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": { entry: unknown, } } | { "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": { entries: Array<unknown>, 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": "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" };
|
||||
|
||||
@@ -43,11 +43,84 @@ function consoleLine(id: string, kind: ConsoleLine["kind"]): ConsoleLine {
|
||||
};
|
||||
}
|
||||
|
||||
function canonicalSession(logEntries: unknown[]): Event extends {
|
||||
event: "snapshot";
|
||||
data: infer D;
|
||||
} ? D extends { session: infer S } ? S : never : never {
|
||||
const entries: Record<string, unknown>[] = [];
|
||||
let sequence = 0;
|
||||
const itemEntry = (item: Record<string, unknown>) => {
|
||||
const kind = item["kind"];
|
||||
if (kind === "reasoning" || item["role"] === "system") return;
|
||||
entries.push({
|
||||
entry_id: `legacy-test-${sequence++}`,
|
||||
provenance: "legacy_unknown",
|
||||
...item,
|
||||
});
|
||||
};
|
||||
for (const raw of logEntries) {
|
||||
if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
|
||||
const entry = raw as Record<string, unknown>;
|
||||
switch (entry["kind"]) {
|
||||
case "segment_start":
|
||||
case "annotated_segment_start":
|
||||
entries.length = 0;
|
||||
for (const history of Array.isArray(entry["history"]) ? entry["history"] : []) {
|
||||
const value = history as Record<string, unknown>;
|
||||
itemEntry((value["item"] as Record<string, unknown> | undefined) ?? value);
|
||||
}
|
||||
break;
|
||||
case "user_input":
|
||||
case "annotated_user_input":
|
||||
entries.push({
|
||||
entry_id: `legacy-test-${sequence++}`,
|
||||
provenance: "legacy_unknown",
|
||||
kind: "user_input",
|
||||
segments: entry["segments"] ?? [],
|
||||
});
|
||||
break;
|
||||
case "assistant_item":
|
||||
case "tool_result":
|
||||
case "annotated_assistant_item":
|
||||
case "annotated_tool_result": {
|
||||
const annotated = entry["entry"] as Record<string, unknown> | undefined;
|
||||
itemEntry((annotated?.["item"] as Record<string, unknown> | undefined) ??
|
||||
(entry["item"] as Record<string, unknown>));
|
||||
break;
|
||||
}
|
||||
case "system_item":
|
||||
case "annotated_system_item": {
|
||||
const annotated = entry["entry"] as Record<string, unknown> | undefined;
|
||||
const item = (annotated?.["item"] as Record<string, unknown> | undefined) ??
|
||||
(entry["item"] as Record<string, unknown>);
|
||||
entries.push({
|
||||
entry_id: `legacy-test-${sequence++}`,
|
||||
provenance: "legacy_unknown",
|
||||
kind: "system_item",
|
||||
item_kind: item?.["kind"] ?? "system_item",
|
||||
content: item?.["body"] ?? item?.["message"] ?? "",
|
||||
data: item,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "run_errored":
|
||||
entries.push({
|
||||
entry_id: `legacy-test-${sequence++}`,
|
||||
provenance: "legacy_unknown",
|
||||
kind: "run_error",
|
||||
message: entry["message"] ?? "Worker run failed.",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { entries } as never;
|
||||
}
|
||||
|
||||
function snapshotEvent(cwd: string, entries: unknown[] = []): Event {
|
||||
return {
|
||||
event: "snapshot",
|
||||
data: {
|
||||
entries,
|
||||
session: canonicalSession(entries),
|
||||
greeting: {
|
||||
worker_name: "Worker",
|
||||
cwd,
|
||||
@@ -138,7 +211,7 @@ Deno.test("segment rotation retains a live error beside the real SegmentStart hi
|
||||
event: {
|
||||
event: "segment_rotated",
|
||||
data: {
|
||||
entry: {
|
||||
session: canonicalSession([{
|
||||
kind: "segment_start",
|
||||
ts: 5,
|
||||
session_id: "session-1",
|
||||
@@ -149,7 +222,7 @@ Deno.test("segment rotation retains a live error beside the real SegmentStart hi
|
||||
role: "user",
|
||||
content: [{ kind: "text", text: "retained conversation" }],
|
||||
}],
|
||||
},
|
||||
}]),
|
||||
},
|
||||
} satisfies Event,
|
||||
},
|
||||
@@ -852,7 +925,7 @@ Deno.test("compaction service activity stays nested in one lifecycle item", () =
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test("snapshot normalizes orphaned running compaction to interrupted", () => {
|
||||
Deno.test("snapshot excludes storage-only compaction extension records", () => {
|
||||
const projection = projectConsole([{
|
||||
eventId: "snapshot",
|
||||
observedAtMs: 9_000,
|
||||
@@ -876,10 +949,7 @@ Deno.test("snapshot normalizes orphaned running compaction to interrupted", () =
|
||||
}]),
|
||||
}]);
|
||||
|
||||
assertEquals(projection.lines.length, 1);
|
||||
assertEquals(projection.lines[0].compaction?.state, "interrupted");
|
||||
assertEquals(projection.lines[0].compaction?.endedAtMs, 9_000);
|
||||
assertEquals(projection.lines[0].streaming, false);
|
||||
assertEquals(projection.lines.length, 0);
|
||||
});
|
||||
|
||||
Deno.test("createConsoleProjector ignores stale compaction revisions", () => {
|
||||
@@ -1238,7 +1308,7 @@ Deno.test("projectConsole renders snapshot entries and in-flight output", () =>
|
||||
event: {
|
||||
event: "snapshot",
|
||||
data: {
|
||||
entries: [
|
||||
session: canonicalSession([
|
||||
{
|
||||
kind: "segment_start",
|
||||
ts: 1,
|
||||
@@ -1300,7 +1370,7 @@ Deno.test("projectConsole renders snapshot entries and in-flight output", () =>
|
||||
message: "Compacting…",
|
||||
},
|
||||
},
|
||||
],
|
||||
]),
|
||||
greeting: {
|
||||
worker_name: "Worker",
|
||||
cwd: "/repo",
|
||||
@@ -1332,7 +1402,6 @@ Deno.test("projectConsole renders snapshot entries and in-flight output", () =>
|
||||
"user:new user:false",
|
||||
"assistant:assistant reply:false",
|
||||
"tool:Read(1 file)\n /tmp/a.md:false",
|
||||
"status:Compacting…:true",
|
||||
"in_flight:partial:true",
|
||||
],
|
||||
);
|
||||
@@ -1344,7 +1413,7 @@ Deno.test("projectConsole restores system items from snapshot entries", () => {
|
||||
event: {
|
||||
event: "snapshot",
|
||||
data: {
|
||||
entries: [{
|
||||
session: canonicalSession([{
|
||||
kind: "system_item",
|
||||
ts: 1,
|
||||
item: {
|
||||
@@ -1352,7 +1421,7 @@ Deno.test("projectConsole restores system items from snapshot entries", () => {
|
||||
message: "Worker completed",
|
||||
body: "Child Worker coder-1 completed.",
|
||||
},
|
||||
}],
|
||||
}]),
|
||||
greeting: {
|
||||
worker_name: "Worker",
|
||||
cwd: "/repo",
|
||||
@@ -1388,7 +1457,7 @@ Deno.test("projectConsole reseeds visible rows from segment rotation", () => {
|
||||
event: {
|
||||
event: "segment_rotated",
|
||||
data: {
|
||||
entry: {
|
||||
session: canonicalSession([{
|
||||
kind: "segment_start",
|
||||
ts: 10,
|
||||
session_id: "00000000-0000-0000-0000-000000000001",
|
||||
@@ -1401,7 +1470,7 @@ Deno.test("projectConsole reseeds visible rows from segment rotation", () => {
|
||||
content: [{ kind: "text", text: "after rotation seed" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
}]),
|
||||
},
|
||||
} satisfies Event,
|
||||
},
|
||||
@@ -1773,7 +1842,7 @@ Deno.test("parent snapshot authoritatively replaces Internal Worker projections"
|
||||
kind: "sub_worker",
|
||||
},
|
||||
revision: 4,
|
||||
entries: [{
|
||||
session: canonicalSession([{
|
||||
kind: "assistant_item",
|
||||
ts: 1,
|
||||
item: {
|
||||
@@ -1792,7 +1861,7 @@ Deno.test("parent snapshot authoritatively replaces Internal Worker projections"
|
||||
content: "content",
|
||||
is_error: false,
|
||||
},
|
||||
}],
|
||||
}]),
|
||||
status: "idle",
|
||||
in_flight: {
|
||||
blocks: [{
|
||||
@@ -1934,18 +2003,20 @@ Deno.test("snapshot restores TaskStore state from system history", () => {
|
||||
`[Session TaskStore snapshot]\n\n\`\`\`json\n{\n "tasks": [{"taskid": 3, "status": "pending", "subject": "Restored", "description": "From compaction"}]\n}\n\`\`\``;
|
||||
const event = snapshotEvent("/repo");
|
||||
if (event.event !== "snapshot") throw new Error("snapshot fixture expected");
|
||||
event.data.entries = [{
|
||||
kind: "segment_start",
|
||||
ts: 1,
|
||||
session_id: "00000000-0000-0000-0000-000000000001",
|
||||
system_prompt: null,
|
||||
config: {},
|
||||
history: [{
|
||||
kind: "message",
|
||||
role: "system",
|
||||
content: [{ kind: "text", text: taskSnapshot }],
|
||||
event.data.session = {
|
||||
entries: [{
|
||||
entry_id: "task-reminder-1",
|
||||
provenance: "backend_instruction",
|
||||
kind: "system_item",
|
||||
item_kind: "task_reminder",
|
||||
content: taskSnapshot,
|
||||
data: {
|
||||
kind: "task_reminder",
|
||||
body: taskSnapshot,
|
||||
source: "automatic",
|
||||
},
|
||||
}],
|
||||
}];
|
||||
};
|
||||
|
||||
const projection = projectConsole([{ eventId: "task-snapshot", event }]);
|
||||
assertEquals(projection.tasks, [{
|
||||
|
||||
@@ -650,9 +650,9 @@ function projectInternalWorkerSnapshot(
|
||||
eventId: string,
|
||||
cwd: string | null,
|
||||
): InternalWorkerProjection {
|
||||
const console = snapshotProjectionFromEntries(
|
||||
const console = snapshotProjectionFromSession(
|
||||
`${eventId}:internal:${snapshot.worker.session_id}:snapshot`,
|
||||
snapshot.entries,
|
||||
snapshot.session,
|
||||
cwd,
|
||||
);
|
||||
console.status = snapshot.status;
|
||||
@@ -905,9 +905,9 @@ export function applyProtocolEvent(
|
||||
case "snapshot": {
|
||||
next.status = event.data.status;
|
||||
next.cwd = event.data.greeting.cwd;
|
||||
const snapshot = snapshotProjectionFromEntries(
|
||||
const snapshot = snapshotProjectionFromSession(
|
||||
envelope.eventId,
|
||||
event.data.entries,
|
||||
event.data.session,
|
||||
next.cwd,
|
||||
);
|
||||
next.lines = snapshot.lines;
|
||||
@@ -1008,9 +1008,9 @@ export function applyProtocolEvent(
|
||||
break;
|
||||
case "segment_rotated": {
|
||||
const retainedErrors = next.lines.filter((line) => line.kind === "error");
|
||||
const segment = snapshotProjectionFromEntries(
|
||||
const segment = snapshotProjectionFromSession(
|
||||
envelope.eventId,
|
||||
[event.data.entry],
|
||||
event.data.session,
|
||||
next.cwd,
|
||||
);
|
||||
next.lines = [...segment.lines, ...retainedErrors];
|
||||
@@ -1925,9 +1925,9 @@ function applyTaskSystemItem(
|
||||
if (typeof body === "string") applyTaskSnapshot(projection, body);
|
||||
}
|
||||
|
||||
function snapshotProjectionFromEntries(
|
||||
function snapshotProjectionFromSession(
|
||||
eventId: string,
|
||||
entries: unknown[],
|
||||
snapshot: unknown,
|
||||
cwd: string | null,
|
||||
): ConsoleProjection {
|
||||
const projection: ConsoleProjection = {
|
||||
@@ -1942,58 +1942,74 @@ function snapshotProjectionFromEntries(
|
||||
internalWorkers: [],
|
||||
removedInternalWorkers: {},
|
||||
};
|
||||
const entries = isRecord(snapshot) ? arrayField(snapshot, "entries") : [];
|
||||
entries.forEach((entry, index) =>
|
||||
applyLogEntry(projection, `${eventId}-snapshot-${index}`, entry)
|
||||
applySessionEntry(projection, `${eventId}-snapshot-${index}`, entry)
|
||||
);
|
||||
return projection;
|
||||
}
|
||||
|
||||
function applyLogEntry(
|
||||
function applySessionEntry(
|
||||
projection: ConsoleProjection,
|
||||
eventId: string,
|
||||
entry: unknown,
|
||||
fallbackEventId: string,
|
||||
value: unknown,
|
||||
): void {
|
||||
if (!isRecord(entry)) return;
|
||||
switch (stringField(entry, "kind")) {
|
||||
case "segment_start":
|
||||
arrayField(entry, "history").forEach((item, index) =>
|
||||
applyLoggedItem(projection, `${eventId}-history-${index}`, item)
|
||||
);
|
||||
break;
|
||||
if (!isRecord(value)) return;
|
||||
const eventId = stringField(value, "entry_id") ?? fallbackEventId;
|
||||
switch (stringField(value, "kind")) {
|
||||
case "user_input":
|
||||
projection.lines.push(
|
||||
line(
|
||||
eventId,
|
||||
"user",
|
||||
"User",
|
||||
segmentsToText(arrayField(entry, "segments") as Segment[]),
|
||||
segmentsToText(arrayField(value, "segments") as Segment[]),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case "system_item":
|
||||
projection.lines.push(systemItemLine(eventId, entry["item"]));
|
||||
applyTaskSystemItem(projection, entry["item"]);
|
||||
case "message":
|
||||
applyLoggedItem(projection, eventId, {
|
||||
kind: "message",
|
||||
role: value["role"],
|
||||
content: value["content"],
|
||||
});
|
||||
break;
|
||||
case "tool_call":
|
||||
applyLoggedItem(projection, eventId, {
|
||||
kind: "tool_call",
|
||||
call_id: value["call_id"],
|
||||
name: value["name"],
|
||||
arguments: value["arguments"],
|
||||
});
|
||||
break;
|
||||
case "assistant_item":
|
||||
case "tool_result":
|
||||
applyLoggedItem(projection, eventId, entry["item"]);
|
||||
applyLoggedItem(projection, eventId, {
|
||||
kind: "tool_result",
|
||||
call_id: value["call_id"],
|
||||
summary: value["summary"],
|
||||
content: value["content"],
|
||||
is_error: value["is_error"],
|
||||
});
|
||||
break;
|
||||
case "run_errored":
|
||||
case "system_item": {
|
||||
const item = value["data"];
|
||||
projection.lines.push(systemItemLine(eventId, item ?? value));
|
||||
applyTaskSystemItem(projection, item);
|
||||
break;
|
||||
}
|
||||
case "run_error":
|
||||
projection.lines.push(
|
||||
line(
|
||||
eventId,
|
||||
"error",
|
||||
"Run error",
|
||||
stringField(entry, "message") ?? "Worker run failed.",
|
||||
stringField(value, "message") ?? "Worker run failed.",
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case "extension":
|
||||
applyExtensionEntry(projection, eventId, entry);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user