Skip to content

Commit 68adf20

Browse files
authored
fix(cloud): reconcile resumed events by leaf identity (#3539)
1 parent 5f0f3d0 commit 68adf20

6 files changed

Lines changed: 2639 additions & 286 deletions

File tree

packages/core/src/sessions/sessionEvents.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,60 @@ import { skillTagsToSlashCommands } from "../message-editor/skillTags";
2323
import { isNotification, POSTHOG_NOTIFICATIONS } from "./acpNotifications";
2424
import { extractPromptDisplayContent } from "./promptContent";
2525

26+
export interface StoredLogEventPosition {
27+
taskRunId: string;
28+
entryIndex: number;
29+
}
30+
31+
export interface StoredLogEventPositionOptions {
32+
taskRunId: string;
33+
startEntryIndex: number;
34+
firstPositionedEntryIndex?: number;
35+
}
36+
37+
// Ordinals are local reconciliation provenance, so keep them out of the ACP
38+
// event shape that crosses host and renderer boundaries.
39+
const storedLogEventPositions = new WeakMap<
40+
AcpMessage,
41+
StoredLogEventPosition
42+
>();
43+
44+
export function getStoredLogEventPosition(
45+
event: AcpMessage,
46+
): StoredLogEventPosition | undefined {
47+
return storedLogEventPositions.get(event);
48+
}
49+
50+
function recordStoredLogEventPosition(
51+
event: AcpMessage,
52+
position: StoredLogEventPosition | undefined,
53+
): AcpMessage {
54+
if (position) storedLogEventPositions.set(event, position);
55+
return event;
56+
}
57+
2658
/**
2759
* Convert a stored log entry to an ACP message.
2860
*/
29-
function storedEntryToAcpMessage(entry: StoredLogEntry): AcpMessage {
61+
function storedEntryToAcpMessage(
62+
entry: StoredLogEntry,
63+
position?: StoredLogEventPosition,
64+
): AcpMessage {
3065
const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
3166
const promoted = promoteImportedUserPrompt(entry, ts);
3267
// Freeze at creation: events assigned via setSession bypass the store's
3368
// per-append freeze, so this keeps them read-only once stored.
34-
if (promoted) return Object.freeze(promoted);
35-
return Object.freeze({
36-
type: "acp_message",
37-
ts,
38-
message: (entry.notification ?? {}) as JsonRpcMessage,
39-
});
69+
if (promoted) {
70+
return recordStoredLogEventPosition(Object.freeze(promoted), position);
71+
}
72+
return recordStoredLogEventPosition(
73+
Object.freeze({
74+
type: "acp_message",
75+
ts,
76+
message: (entry.notification ?? {}) as JsonRpcMessage,
77+
}),
78+
position,
79+
);
4080
}
4181

4282
/**
@@ -192,13 +232,16 @@ function withToolCallUpdate(
192232
update: Record<string, unknown>,
193233
): AcpMessage {
194234
const msg = event.message as { params?: SessionNotification };
195-
return Object.freeze({
196-
...event,
197-
message: {
198-
...msg,
199-
params: { ...msg.params, update },
200-
} as JsonRpcMessage,
201-
});
235+
return recordStoredLogEventPosition(
236+
Object.freeze({
237+
...event,
238+
message: {
239+
...msg,
240+
params: { ...msg.params, update },
241+
} as JsonRpcMessage,
242+
}),
243+
getStoredLogEventPosition(event),
244+
);
202245
}
203246

204247
/**
@@ -262,6 +305,7 @@ export function collapseSupersededToolCallUpdates(
262305
export function convertStoredEntriesToEvents(
263306
entries: StoredLogEntry[],
264307
taskDescription?: string,
308+
positionOptions?: StoredLogEventPositionOptions,
265309
): AcpMessage[] {
266310
const events: AcpMessage[] = [];
267311

@@ -272,8 +316,20 @@ export function convertStoredEntriesToEvents(
272316
events.push(createUserMessageEvent(taskDescription, startTs));
273317
}
274318

275-
for (const entry of entries) {
276-
events.push(storedEntryToAcpMessage(entry));
319+
const firstPositionedEntryIndex =
320+
positionOptions?.firstPositionedEntryIndex ?? 0;
321+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
322+
const position =
323+
positionOptions && entryIndex >= firstPositionedEntryIndex
324+
? {
325+
taskRunId: positionOptions.taskRunId,
326+
entryIndex:
327+
positionOptions.startEntryIndex +
328+
entryIndex -
329+
firstPositionedEntryIndex,
330+
}
331+
: undefined;
332+
events.push(storedEntryToAcpMessage(entries[entryIndex], position));
277333
}
278334

279335
return collapseSupersededToolCallUpdates(events);

0 commit comments

Comments
 (0)