Fix timeline catch-up for projected overlap
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
|||||||
function makeTimelineEntry(seq: number, text: string, type: string = "assistant_message") {
|
function makeTimelineEntry(seq: number, text: string, type: string = "assistant_message") {
|
||||||
return {
|
return {
|
||||||
seqStart: seq,
|
seqStart: seq,
|
||||||
|
seqEnd: seq,
|
||||||
provider: "claude",
|
provider: "claude",
|
||||||
item: { type, text },
|
item: { type, text },
|
||||||
timestamp: new Date(1000 + seq).toISOString(),
|
timestamp: new Date(1000 + seq).toISOString(),
|
||||||
@@ -318,6 +319,39 @@ describe("processTimelineResponse", () => {
|
|||||||
expect(result.cursorChanged).toBe(false);
|
expect(result.cursorChanged).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("requests canonical catch-up when a projected entry overlaps unseen seqs", () => {
|
||||||
|
const existingCursor: TimelineCursor = {
|
||||||
|
epoch: "epoch-1",
|
||||||
|
startSeq: 1,
|
||||||
|
endSeq: 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = processTimelineResponse({
|
||||||
|
...baseTimelineInput,
|
||||||
|
currentCursor: existingCursor,
|
||||||
|
payload: {
|
||||||
|
...baseTimelineInput.payload,
|
||||||
|
epoch: "epoch-1",
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
...makeTimelineEntry(4, "merged assistant message"),
|
||||||
|
seqEnd: 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.tail).toBe(baseTimelineInput.currentTail);
|
||||||
|
expect(result.cursorChanged).toBe(false);
|
||||||
|
expect(result.sideEffects).toContainEqual({
|
||||||
|
type: "catch_up",
|
||||||
|
cursor: {
|
||||||
|
epoch: "epoch-1",
|
||||||
|
endSeq: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("drops entries with epoch mismatch", () => {
|
it("drops entries with epoch mismatch", () => {
|
||||||
const existingCursor: TimelineCursor = {
|
const existingCursor: TimelineCursor = {
|
||||||
epoch: "epoch-1",
|
epoch: "epoch-1",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ type InitRequestDirection = "tail" | "after";
|
|||||||
|
|
||||||
type TimelineResponseEntry = {
|
type TimelineResponseEntry = {
|
||||||
seqStart: number;
|
seqStart: number;
|
||||||
|
seqEnd: number;
|
||||||
provider: string;
|
provider: string;
|
||||||
item: Record<string, unknown>;
|
item: Record<string, unknown>;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
@@ -122,6 +123,7 @@ export function processTimelineResponse(
|
|||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
const timelineUnits = payload.entries.map((entry) => ({
|
const timelineUnits = payload.entries.map((entry) => ({
|
||||||
seq: entry.seqStart,
|
seq: entry.seqStart,
|
||||||
|
seqEnd: entry.seqEnd,
|
||||||
event: {
|
event: {
|
||||||
type: "timeline",
|
type: "timeline",
|
||||||
provider: entry.provider,
|
provider: entry.provider,
|
||||||
@@ -200,7 +202,14 @@ export function processTimelineResponse(
|
|||||||
gapCursor = cursor ? { epoch: cursor.epoch, endSeq: cursor.endSeq } : null;
|
gapCursor = cursor ? { epoch: cursor.epoch, endSeq: cursor.endSeq } : null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (decision === "drop_stale" || decision === "drop_epoch") {
|
if (decision === "drop_stale") {
|
||||||
|
if (cursor && unit.seqEnd > cursor.endSeq) {
|
||||||
|
gapCursor = { epoch: cursor.epoch, endSeq: cursor.endSeq };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (decision === "drop_epoch") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +218,7 @@ export function processTimelineResponse(
|
|||||||
cursor = {
|
cursor = {
|
||||||
epoch: payload.epoch,
|
epoch: payload.epoch,
|
||||||
startSeq: unit.seq,
|
startSeq: unit.seq,
|
||||||
endSeq: unit.seq,
|
endSeq: unit.seqEnd,
|
||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -218,7 +227,7 @@ export function processTimelineResponse(
|
|||||||
}
|
}
|
||||||
cursor = {
|
cursor = {
|
||||||
...cursor,
|
...cursor,
|
||||||
endSeq: unit.seq,
|
endSeq: unit.seqEnd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user