feat(desktop): reconcile cloud session snapshots (#14080)

* feat(desktop): isolate cloud Hub snapshot reconciliation

* fix(desktop): reconcile reflected attachment-only cloud prompts

* fix(desktop): preserve unreflected cloud tool and reasoning events

* fix(desktop): preserve unfinished aborted cloud output

* fix(desktop): retain interrupted output after saved replies

* fix(desktop): retain output completed after cloud snapshot

* fix(desktop): exclude tool output from empty prompt matches
This commit is contained in:
John Choi
2026-09-16 13:47:56 -07:00
committed by GitHub
parent d6d4566451
commit 025baa8fb7
2 changed files with 867 additions and 0 deletions
@@ -0,0 +1,505 @@
import type { HubEventEnvelope } from "@cline/shared";
import { describe, expect, it } from "vitest";
import { reconcileBufferedCloudEvents } from "./cloud-session-snapshots";
describe("reconcileBufferedCloudEvents", () => {
const event = (
name: HubEventEnvelope["event"],
id: string,
payload: Record<string, unknown> = {},
): HubEventEnvelope => ({
version: "v1",
event: name,
eventId: id,
timestamp: Date.now(),
sessionId: "inner-1",
payload,
});
it.each([
"Continue",
"",
])("preserves submitted lifecycle while marking only newly reflected %j prompts", (prompt) => {
const submitted = (id: string) =>
event("session.pending_prompt_submitted", id, {
prompt: {
id,
prompt,
delivery: "queue",
...(prompt
? {}
: {
userImages: ["data:image/png;base64,AA=="],
attachmentCount: 1,
}),
},
});
const first = submitted("q-1");
const later = submitted("q-2");
const baseline = [
{
role: "user",
content: prompt || [
{
type: "image",
source: { type: "base64", media_type: "image/png", data: "AA==" },
},
],
},
];
// An older identical user message must not consume a new submission.
expect(
reconcileBufferedCloudEvents([first], baseline, {
baselineMessages: baseline,
}),
).toEqual([first]);
const toolResult = {
role: "user",
content: [
{ type: "tool_result", tool_use_id: "call-1", content: "done" },
],
};
expect(
reconcileBufferedCloudEvents([first], [...baseline, toolResult], {
baselineMessages: baseline,
}),
).toEqual([first]);
const snapshot = [
...baseline,
toolResult,
{
...baseline[0],
...(prompt
? {
content: [
...toolResult.content,
{ type: "text", text: `<user_input>${prompt}</user_input>` },
],
}
: {}),
},
];
const completed = event("run.completed", "done");
const running = event("run.started", "next");
// Keep the next turn's start between lifecycle events; only its bubble is reflected.
expect(
reconcileBufferedCloudEvents(
[completed, first, running, later],
snapshot,
{
baselineMessages: baseline,
},
),
).toEqual([
completed,
{ ...first, payload: { ...first.payload, transcriptReflected: true } },
running,
later,
]);
// A submission received after the transcript reply cannot be in it.
expect(
reconcileBufferedCloudEvents([later], snapshot, {
baselineMessages: baseline,
messagesSnapshotEventCutoff: 0,
}),
).toEqual([later]);
});
it("replays content the snapshot does NOT contain", () => {
const buffered = [
event("assistant.delta", "a-1", { text: "unpersisted reply" }),
event("run.completed", "done-1"),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{ role: "assistant", content: "a completely different answer" },
]).map((item) => item.event),
).toEqual(["assistant.delta", "run.completed"]);
});
it("does not let an interior substring claim another run's snapshot", () => {
const buffered = [
event("assistant.finished", "a-1", { text: "foo" }),
event("run.completed", "done-1"),
event("assistant.finished", "a-2", { text: "The answer is foobar" }),
event("run.completed", "done-2"),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{ role: "assistant", content: "The answer is foobar" },
]).map((item) => item.eventId),
).toEqual(["a-1", "done-1", "done-2"]);
});
it.each([
["bar", "foobar", false],
["bar", "foobar", true],
["Done", "Done", false],
] as const)("preserves aborted %j beside saved %j (reverse: %s)", (partial, saved, reverse) => {
const aborted = [
event("assistant.delta", "partial", { text: partial }),
event("run.aborted", "aborted"),
];
const completed = [
event("assistant.delta", "saved-delta", { text: saved }),
event("assistant.finished", "saved-finished", { text: saved }),
event("run.completed", "completed"),
];
expect(
reconcileBufferedCloudEvents(
reverse ? [...completed, ...aborted] : [...aborted, ...completed],
[{ role: "assistant", content: saved }],
).map((item) => item.eventId),
).toEqual(
reverse
? ["completed", "partial", "aborted"]
: ["partial", "aborted", "completed"],
);
});
it.each([
"assistant",
"reasoning",
] as const)("does not let finished %s hide the other aborted content", (finished) => {
const partial = finished === "assistant" ? "reasoning" : "assistant";
const buffered = [
event(`${finished}.finished`, "saved", {
text: "foobar",
reasoning: "foobar",
}),
event(`${partial}.delta`, "partial", { text: "bar" }),
event("run.aborted", "aborted"),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{
role: "assistant",
content: [
{ type: "text", text: "foobar" },
{ type: "thinking", thinking: "foobar" },
],
},
]).map((item) => item.eventId),
).toEqual(["partial", "aborted"]);
});
it.each([
["assistant", false],
["reasoning", false],
["reasoning", true],
] as const)("preserves unfinished %s after saved output (redacted: %s)", (kind, redacted) => {
const saved = [
event(`${kind}.delta`, "saved-delta", {
text: redacted ? "" : "saved",
redacted,
}),
event(`${kind}.finished`, "saved-finished", {
[kind === "assistant" ? "text" : "reasoning"]: redacted
? undefined
: "saved",
}),
];
const partial = event(`${kind}.delta`, "partial", { text: "unfinished" });
const snapshot = [
{
role: "assistant",
content: [
redacted
? { type: "redacted_thinking", data: "opaque" }
: kind === "assistant"
? { type: "text", text: "saved" }
: { type: "thinking", thinking: "saved" },
],
},
];
for (const terminal of ["run.aborted", "run.failed"] as const) {
const end = event(terminal, "end");
const buffered = [...saved, partial, end];
expect(reconcileBufferedCloudEvents(buffered, snapshot)).toEqual([
partial,
end,
]);
const earlier = [
{
role: "assistant",
content: [
kind === "assistant"
? { type: "text", text: "earlier" }
: { type: "thinking", thinking: "earlier" },
],
},
];
expect(
reconcileBufferedCloudEvents(
[
event(`${kind}.finished`, "earlier", {
[kind === "assistant" ? "text" : "reasoning"]: "earlier",
}),
...buffered,
],
[...earlier, ...snapshot],
{ baselineMessages: earlier },
),
).toEqual([partial, end]);
expect(reconcileBufferedCloudEvents(buffered, [])).toEqual(buffered);
expect(
reconcileBufferedCloudEvents(buffered, snapshot, {
baselineMessages: snapshot,
}),
).toEqual(buffered);
expect(
reconcileBufferedCloudEvents(buffered, snapshot, {
messagesSnapshotEventCutoff: 1,
}),
).toEqual(
terminal === "run.aborted" ? buffered : [saved[1], partial, end],
);
}
});
it.each([
"assistant",
"reasoning",
] as const)("preserves %s output completed after the snapshot", (kind) => {
const key = kind === "assistant" ? "text" : "reasoning";
const saved = event(`${kind}.finished`, "saved", { [key]: "saved" });
const partial = event(`${kind}.delta`, "partial", { text: "new reply" });
const finished = event(`${kind}.finished`, "finished", {
[key]: "new reply",
});
const end = event("run.completed", "end");
expect(
reconcileBufferedCloudEvents(
[saved, partial, finished, end],
[
{
role: "assistant",
content: [
{
type: kind === "assistant" ? "text" : "thinking",
[kind === "assistant" ? "text" : "thinking"]: "saved",
},
],
},
],
{ messagesSnapshotEventCutoff: 2 },
),
).toEqual([partial, finished, end]);
});
it.each([
"run.completed",
"run.aborted",
"run.failed",
] as const)("ignores empty finishes after saved content in %s", (terminal) => {
const end = event(terminal, "end");
expect(
reconcileBufferedCloudEvents(
[
event("assistant.finished", "saved", { text: "saved" }),
event("assistant.finished", "empty", { text: "" }),
event("assistant.finished", "missing"),
end,
],
[{ role: "assistant", content: "saved" }],
),
).toEqual([end]);
});
it("supersedes despite trailing whitespace in the streamed text", () => {
const buffered = [
event("assistant.finished", "f-1", { text: "the answer \n" }),
event("run.completed", "done-1"),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{ role: "assistant", content: "prefix the answer" },
]).map((item) => item.event),
).toEqual(["run.completed"]);
});
it("drops buffered queue snapshots when a fresh queue snapshot was applied", () => {
const buffered = [
event("session.pending_prompts", "q-1", { prompts: [] }),
event("assistant.delta", "a-1", { text: "live tail" }),
];
expect(
reconcileBufferedCloudEvents(buffered, []).map((item) => item.event),
).toEqual(["assistant.delta"]);
});
it("replays the newest buffered queue snapshot when the queue fetch failed", () => {
const buffered = [
event("session.pending_prompts", "q-1", { prompts: [] }),
event("session.pending_prompts", "q-2", {
prompts: [{ id: "p-1", prompt: "queued work" }],
}),
event("assistant.delta", "a-1", { text: "live tail" }),
];
expect(
reconcileBufferedCloudEvents(buffered, [], {
queueSnapshotApplied: false,
}).map((item) => item.eventId),
).toEqual(["q-2", "a-1"]);
});
it("replays a queue snapshot received after the queue fetch", () => {
const buffered = [
event("session.pending_prompts", "q-old", { prompts: [] }),
event("assistant.delta", "a-1", { text: "live tail" }),
event("session.pending_prompts", "q-new", {
prompts: [{ id: "p-1", prompt: "queued work" }],
}),
];
expect(
reconcileBufferedCloudEvents(buffered, [], {
queueSnapshotEventCutoff: 1,
}).map((item) => item.eventId),
).toEqual(["a-1", "q-new"]);
});
it("supersedes content independently across two terminal run segments", () => {
const buffered = [
event("assistant.delta", "a-1", { text: "first tail" }),
event("run.completed", "done-1"),
event("assistant.delta", "a-2", { text: "second tail" }),
event("run.completed", "done-2"),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{ role: "assistant", content: "prefix first tail" },
{ role: "assistant", content: "prefix second tail" },
]).map((item) => item.event),
).toEqual(["run.completed", "run.completed"]);
});
it("does not let an older identical reply supersede a new buffered turn", () => {
const buffered = [
event("assistant.delta", "a-2", { text: "Done" }),
event("run.completed", "done-2"),
];
const baseline = [{ role: "assistant", content: "Done" }];
expect(
reconcileBufferedCloudEvents(buffered, baseline, {
baselineMessages: baseline,
}).map((item) => item.event),
).toEqual(["assistant.delta", "run.completed"]);
expect(
reconcileBufferedCloudEvents(
buffered,
[...baseline, { role: "assistant", content: "Done" }],
{ baselineMessages: baseline },
).map((item) => item.event),
).toEqual(["run.completed"]);
});
it("keeps run.failed while suppressing reflected content and dedupes tools by id", () => {
const buffered = [
event("assistant.delta", "a-1", { text: "partial failure" }),
event("tool.started", "tool-1", { toolCallId: "call-1" }),
event("run.failed", "failed-1", { error: "boom" }),
];
expect(
reconcileBufferedCloudEvents(buffered, [
{ role: "assistant", content: "saved partial failure" },
{
role: "assistant",
content: [{ type: "tool_use", id: "call-1", name: "read_file" }],
},
]).map((item) => item.event),
).toEqual(["run.failed"]);
});
it("only suppresses tool phases present in the transcript before its cutoff", () => {
const started = event("tool.started", "start", { toolCallId: "call-1" });
const finished = event("tool.finished", "finish", {
toolCallId: "call-1",
output: "done",
});
const snapshot = [
{
role: "assistant",
content: [{ type: "tool_use", id: "call-1", name: "read_file" }],
},
];
expect(reconcileBufferedCloudEvents([started, finished], snapshot)).toEqual(
[finished],
);
const completedSnapshot = [
...snapshot,
{
role: "user",
content: [
{ type: "tool_result", tool_use_id: "call-1", content: "done" },
],
},
];
expect(
reconcileBufferedCloudEvents([started, finished], completedSnapshot),
).toEqual([]);
expect(
reconcileBufferedCloudEvents([started, finished], completedSnapshot, {
messagesSnapshotEventCutoff: 1,
}),
).toEqual([finished]);
});
it.each([
["run.failed", false],
["run.aborted", false],
["run.aborted", true],
] as const)("does not replay persisted thinking for %s (redacted: %s)", (terminal, redacted) => {
const buffered = [
event("reasoning.delta", "thinking", {
text: redacted ? "" : "Checking",
redacted,
}),
event("reasoning.finished", "thought", {
reasoning: redacted ? undefined : "Checking",
}),
event(terminal, "end"),
];
const snapshot = [
{
role: "assistant",
content: [
redacted
? { type: "redacted_thinking", data: "opaque" }
: { type: "thinking", thinking: "Checking" },
],
},
];
expect(reconcileBufferedCloudEvents(buffered, snapshot)).toEqual([
buffered[2],
]);
expect(reconcileBufferedCloudEvents(buffered, [])).toEqual(buffered);
expect(
reconcileBufferedCloudEvents(buffered, snapshot, {
baselineMessages: snapshot,
}),
).toEqual(buffered);
expect(
reconcileBufferedCloudEvents(buffered, snapshot, {
messagesSnapshotEventCutoff: 0,
}),
).toEqual(buffered);
});
it("does not treat persisted assistant text as proof that thinking was saved", () => {
const thinking = event("reasoning.delta", "thinking", { text: "Checking" });
const done = event("run.completed", "done");
expect(
reconcileBufferedCloudEvents(
[
thinking,
event("assistant.finished", "answer", { text: "Done" }),
done,
],
[{ role: "assistant", content: "Done" }],
),
).toEqual([thinking, done]);
});
});
@@ -0,0 +1,362 @@
import { isUserRunMessage } from "@cline/core";
import type { HubEventEnvelope } from "@cline/shared";
import type { JsonRecord, PromptInQueue } from "./types";
export function readSessionRows(
payload: Record<string, unknown> | undefined,
): JsonRecord[] {
return Array.isArray(payload?.sessions)
? payload.sessions.filter(
(item): item is JsonRecord =>
Boolean(item) && typeof item === "object" && !Array.isArray(item),
)
: [];
}
export function updatedAt(record: JsonRecord): number {
const value = record.updatedAt;
return typeof value === "number"
? value
: Date.parse(String(value ?? "")) || 0;
}
export function sessionRowModelId(record: JsonRecord | undefined): string {
const metadata =
record?.metadata && typeof record.metadata === "object"
? (record.metadata as JsonRecord)
: undefined;
return String(metadata?.model ?? record?.model ?? "").trim();
}
export function isRootSessionRow(record: JsonRecord): boolean {
const metadata =
record.metadata && typeof record.metadata === "object"
? (record.metadata as JsonRecord)
: undefined;
return !String(
metadata?.parentSessionId ?? record.parentSessionId ?? "",
).trim();
}
function messageText(
message: unknown,
kind: "text" | "thinking" = "text",
): string {
if (!message || typeof message !== "object" || Array.isArray(message)) {
return "";
}
const content = (message as JsonRecord).content;
if (typeof content === "string") {
return kind === "text" ? content.trim() : "";
}
if (!Array.isArray(content)) {
return "";
}
return content
.map((part) =>
part && typeof part === "object" && !Array.isArray(part)
? kind === "thinking" &&
(part as JsonRecord).type === "redacted_thinking"
? "[redacted]"
: String((part as JsonRecord)[kind] ?? "")
: "",
)
.join("")
.trim();
}
function normalizeUserPrompt(text: string): string {
const trimmed = text.trim();
const match = trimmed.match(/^<user_input\b[^>]*>([\s\S]*)<\/user_input>$/);
return (match ? match[1] : trimmed).trim();
}
export function countPromptOccurrences(
messages: unknown[],
prompts: PromptInQueue[],
prompt: string,
): number {
const expected = normalizeUserPrompt(prompt);
return (
messages.filter(
(message) =>
Boolean(message) &&
typeof message === "object" &&
!Array.isArray(message) &&
String((message as JsonRecord).role ?? "").toLowerCase() === "user" &&
(expected !== "" || isUserRunMessage(message as JsonRecord)) &&
normalizeUserPrompt(messageText(message)) === expected,
).length +
prompts.filter((item) => normalizeUserPrompt(item.prompt) === expected)
.length
);
}
export function submittedPromptsFromEvents(
events: HubEventEnvelope[],
): PromptInQueue[] {
return events.flatMap((event) => {
if (event.event !== "session.pending_prompt_submitted") return [];
const prompt =
event.payload?.prompt &&
typeof event.payload.prompt === "object" &&
!Array.isArray(event.payload.prompt)
? (event.payload.prompt as JsonRecord)
: undefined;
const id = String(prompt?.id ?? "").trim();
if (!id) return [];
return [
{
id,
prompt: String(prompt?.prompt ?? ""),
steer: prompt?.delivery === "steer",
attachmentCount:
typeof prompt?.attachmentCount === "number"
? prompt.attachmentCount
: 0,
userImages: Array.isArray(prompt?.userImages)
? prompt.userImages.filter(
(image): image is string => typeof image === "string",
)
: undefined,
},
];
});
}
const TERMINAL_RUN_EVENTS = new Set([
"run.completed",
"run.aborted",
"run.failed",
]);
const SUPERSEDABLE_CONTENT_EVENTS = new Set([
"assistant.delta",
"assistant.finished",
"reasoning.delta",
"reasoning.finished",
]);
function assistantTexts(
messages: unknown[],
kind: "text" | "thinking",
): string[] {
return messages
.filter(
(message): message is JsonRecord =>
Boolean(message) &&
typeof message === "object" &&
!Array.isArray(message) &&
String((message as JsonRecord).role ?? "").toLowerCase() ===
"assistant",
)
.map((message) => messageText(message, kind))
.filter(Boolean);
}
function newlyPersistedAssistantTexts(
snapshotMessages: unknown[],
baselineMessages: unknown[],
kind: "text" | "thinking" = "text",
): string[] {
const baselineCounts = new Map<string, number>();
for (const text of assistantTexts(baselineMessages, kind)) {
baselineCounts.set(text, (baselineCounts.get(text) ?? 0) + 1);
}
return assistantTexts(snapshotMessages, kind).filter((text) => {
const count = baselineCounts.get(text) ?? 0;
if (count === 0) return true;
if (count === 1) baselineCounts.delete(text);
else baselineCounts.set(text, count - 1);
return false;
});
}
function collectToolCallIds(
value: unknown,
phase: "tool_use" | "tool_result",
result = new Set<string>(),
): Set<string> {
if (!value || typeof value !== "object") return result;
if (Array.isArray(value)) {
for (const item of value) collectToolCallIds(item, phase, result);
return result;
}
const record = value as JsonRecord;
const id = phase === "tool_use" ? record.id : record.tool_use_id;
if (record.type === phase && typeof id === "string") {
result.add(id);
}
for (const child of Object.values(record))
collectToolCallIds(child, phase, result);
return result;
}
function streamedAssistantText(
events: HubEventEnvelope[],
kind: "assistant" | "reasoning" = "assistant",
): string {
let finishedText = "";
let deltas = "";
for (const event of events) {
if (event.event === `${kind}.delta`) {
deltas +=
kind === "reasoning" && event.payload?.redacted && !event.payload?.text
? "[redacted]"
: typeof event.payload?.text === "string"
? event.payload.text
: "";
} else if (event.event === `${kind}.finished`) {
const text = event.payload?.[kind === "assistant" ? "text" : "reasoning"];
const completed = typeof text === "string" && text ? text : deltas;
if (completed) finishedText = completed;
deltas = "";
}
}
return (finishedText || deltas).trim();
}
/** Reconciles each completed run separately; tools dedupe by stable call id. */
export function reconcileBufferedCloudEvents(
events: HubEventEnvelope[],
snapshotMessages: unknown[],
options: {
/**
* Whether a fresh queue snapshot was fetched and applied during
* rehydration. When it was, queue events received before its reply are
* stale; later events still win. When the fetch failed, the newest
* buffered queue event is the best state available.
*/
queueSnapshotApplied?: boolean;
queueSnapshotEventCutoff?: number;
/** Events received after the transcript reply cannot be reflected in it. */
messagesSnapshotEventCutoff?: number;
baselineMessages?: unknown[];
} = {},
): HubEventEnvelope[] {
const queueSnapshotApplied = options.queueSnapshotApplied !== false;
const unclaimedAssistantTexts = newlyPersistedAssistantTexts(
snapshotMessages,
options.baselineMessages ?? [],
);
const unclaimedThinking = newlyPersistedAssistantTexts(
snapshotMessages,
options.baselineMessages ?? [],
"thinking",
);
const snapshotToolCallIds = collectToolCallIds(snapshotMessages, "tool_use");
const snapshotToolResultIds = collectToolCallIds(
snapshotMessages,
"tool_result",
);
const beforeTranscript = new Set(
events.slice(0, options.messagesSnapshotEventCutoff ?? events.length),
);
const reflectedSubmissions = new Set<HubEventEnvelope>();
const unclaimedUserCounts = new Map<string, number>();
for (const event of events.slice(
0,
options.messagesSnapshotEventCutoff ?? events.length,
)) {
const submitted = submittedPromptsFromEvents([event])[0];
if (!submitted) continue;
const prompt = normalizeUserPrompt(submitted.prompt);
const count =
unclaimedUserCounts.get(prompt) ??
Math.max(
0,
countPromptOccurrences(snapshotMessages, [], prompt) -
countPromptOccurrences(options.baselineMessages ?? [], [], prompt),
);
unclaimedUserCounts.set(prompt, Math.max(0, count - 1));
if (count > 0) reflectedSubmissions.add(event);
}
// Queue events are full snapshots, so only the newest one matters.
const queueEvents = queueSnapshotApplied
? events.slice(options.queueSnapshotEventCutoff ?? events.length)
: events;
const lastQueueEvent = queueEvents.findLast(
(event) => event.event === "session.pending_prompts",
);
const reconciled: HubEventEnvelope[] = [];
let segment: HubEventEnvelope[] = [];
const flush = (terminal: boolean) => {
if (segment.length === 0) return;
const snapshotSegment = segment.filter((event) =>
beforeTranscript.has(event),
);
// A buffered run can contain saved replies followed by unsaved output.
const contentEnd = (kind: "assistant" | "reasoning") => {
if (!terminal) return -1;
const finished = snapshotSegment.findLastIndex(
(event) => event.event === `${kind}.finished`,
);
return finished >= 0 || segment.at(-1)?.event === "run.aborted"
? finished
: snapshotSegment.length - 1;
};
const assistantEnd = contentEnd("assistant");
const reasoningEnd = contentEnd("reasoning");
const streamed = streamedAssistantText(
snapshotSegment.slice(0, assistantEnd + 1),
);
const persistedIndex = streamed
? unclaimedAssistantTexts.findIndex((text) => text.endsWith(streamed))
: -1;
const contentPersisted = persistedIndex >= 0;
if (contentPersisted) unclaimedAssistantTexts.splice(persistedIndex, 1);
const thinking = streamedAssistantText(
snapshotSegment.slice(0, reasoningEnd + 1),
"reasoning",
);
const thinkingIndex = thinking
? unclaimedThinking.findIndex((text) => text.endsWith(thinking))
: -1;
if (thinkingIndex >= 0) unclaimedThinking.splice(thinkingIndex, 1);
for (const [index, event] of segment.entries()) {
// Preserve the turn-start lifecycle; the UI must only skip its user bubble.
if (reflectedSubmissions.has(event)) {
reconciled.push({
...event,
payload: { ...event.payload, transcriptReflected: true },
});
continue;
}
if (
beforeTranscript.has(event) &&
SUPERSEDABLE_CONTENT_EVENTS.has(event.event) &&
(event.event.startsWith("reasoning.")
? thinkingIndex >= 0 && index <= reasoningEnd
: contentPersisted && index <= assistantEnd)
) {
continue;
}
if (
event.event === "session.pending_prompts" &&
event !== lastQueueEvent
) {
continue;
}
// Keep terminal events: run.failed may carry the only error detail.
if (beforeTranscript.has(event) && event.event.startsWith("tool.")) {
const toolCallId = String(event.payload?.toolCallId ?? "").trim();
if (
snapshotToolResultIds.has(toolCallId) ||
(event.event === "tool.started" &&
snapshotToolCallIds.has(toolCallId))
)
continue;
}
reconciled.push(event);
}
segment = [];
};
for (const event of events) {
segment.push(event);
if (TERMINAL_RUN_EVENTS.has(event.event)) flush(true);
}
// Never supersede an unterminated tail.
flush(false);
return reconciled;
}