mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 03:03:04 +00:00
fix(timeline): restore prompt identity on resume
This commit is contained in:
@@ -258,9 +258,73 @@ const baseStreamInput: ProcessAgentStreamEventInput = {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("processTimelineResponse", () => {
|
||||
it("applies a missed same-sequence provider identity revision from an unchanged resume tail", () => {
|
||||
const canonical = createUserMessage({
|
||||
id: "canonical-prompt",
|
||||
clientMessageId: "client-message",
|
||||
text: "local prompt",
|
||||
timestamp: new Date(1000),
|
||||
timelineCursor: { epoch: "epoch-1", seq: 5 },
|
||||
});
|
||||
const unrelatedTail = makeAssistantItem("unrelated tail", "unrelated-tail");
|
||||
const currentTail = [canonical, unrelatedTail];
|
||||
const currentHead = [makeAssistantItem("live head", "live-head")];
|
||||
const currentCursor: TimelineCursor = { epoch: "epoch-1", startSeq: 1, endSeq: 8 };
|
||||
|
||||
const result = processTimelineResponse({
|
||||
...baseTimelineInput,
|
||||
currentTail,
|
||||
currentHead,
|
||||
currentCursor,
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
direction: "tail",
|
||||
window: { minSeq: 1, maxSeq: 8, nextSeq: 9 },
|
||||
startCursor: { seq: 5 },
|
||||
endCursor: { seq: 8 },
|
||||
entries: [
|
||||
{
|
||||
...makeTimelineEntry(5, "provider prompt", "user_message"),
|
||||
item: {
|
||||
type: "user_message",
|
||||
text: "provider prompt",
|
||||
messageId: "provider-message",
|
||||
clientMessageId: "client-message",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.commit).toBe("apply");
|
||||
expect(result.tail).not.toBe(currentTail);
|
||||
expect(result.tail[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "canonical-prompt",
|
||||
clientMessageId: "client-message",
|
||||
messageId: "provider-message",
|
||||
text: "local prompt",
|
||||
timestamp: canonical.timestamp,
|
||||
timelineCursor: { epoch: "epoch-1", seq: 5 },
|
||||
}),
|
||||
);
|
||||
expect(result.tail[1]).toBe(unrelatedTail);
|
||||
expect(result.head).toBe(currentHead);
|
||||
expect(result.cursor).toBe(currentCursor);
|
||||
expect(result.cursorChanged).toBe(false);
|
||||
expect(result.acknowledgedClientMessageIds).toEqual(["client-message"]);
|
||||
});
|
||||
|
||||
it("discards an unchanged resume tail without replacing timeline state", () => {
|
||||
const submitted = makeSubmittedUserMessage("local prompt", "client-message");
|
||||
const currentTail = [submitted, makeAssistantItem("existing tail", "existing-tail")];
|
||||
const canonical = createUserMessage({
|
||||
id: "canonical-prompt",
|
||||
clientMessageId: "client-message",
|
||||
messageId: "provider-message",
|
||||
text: "local prompt",
|
||||
timestamp: new Date(1000),
|
||||
timelineCursor: { epoch: "epoch-1", seq: 1 },
|
||||
});
|
||||
const currentTail = [canonical, makeAssistantItem("existing tail", "existing-tail")];
|
||||
const currentHead = [makeAssistantItem("existing head", "existing-head")];
|
||||
|
||||
const result = processTimelineResponse({
|
||||
|
||||
@@ -782,6 +782,20 @@ function deriveCanonicalAcknowledgements(params: {
|
||||
return [...acknowledged];
|
||||
}
|
||||
|
||||
function shouldDiscardUnchangedTimelineResult(params: {
|
||||
discardPolicy: boolean;
|
||||
currentTail: StreamItem[];
|
||||
currentHead: StreamItem[];
|
||||
nextTail: StreamItem[];
|
||||
nextHead: StreamItem[];
|
||||
}): boolean {
|
||||
return (
|
||||
params.discardPolicy &&
|
||||
params.nextTail === params.currentTail &&
|
||||
params.nextHead === params.currentHead
|
||||
);
|
||||
}
|
||||
|
||||
function applyTimelineIncrementalPath(args: {
|
||||
timelineUnits: TimelineUnit[];
|
||||
payload: ProcessTimelineResponseInput["payload"];
|
||||
@@ -964,9 +978,20 @@ export function processTimelineResponse(
|
||||
const discard = resumeTailPolicy.kind === "discard";
|
||||
let timelineResult: TimelinePathResult;
|
||||
if (discard) {
|
||||
const revision = processAgentStreamEvents({
|
||||
events: timelineUnits.map((unit) => ({
|
||||
event: unit.event,
|
||||
seq: unit.seqEnd,
|
||||
epoch: payload.epoch,
|
||||
timestamp: unit.timestamp,
|
||||
})),
|
||||
currentTail,
|
||||
currentHead,
|
||||
currentCursor,
|
||||
});
|
||||
timelineResult = {
|
||||
tail: currentTail,
|
||||
head: currentHead,
|
||||
tail: revision.tail,
|
||||
head: revision.head,
|
||||
cursor: currentCursor,
|
||||
cursorChanged: false,
|
||||
older: "unchanged",
|
||||
@@ -1036,9 +1061,18 @@ export function processTimelineResponse(
|
||||
timelineResponseComplete;
|
||||
|
||||
const initResolution: "resolve" | "reject" | null = shouldResolveDeferredInit ? "resolve" : null;
|
||||
const commit = shouldDiscardUnchangedTimelineResult({
|
||||
discardPolicy: discard,
|
||||
currentTail,
|
||||
currentHead,
|
||||
nextTail,
|
||||
nextHead,
|
||||
})
|
||||
? "discard"
|
||||
: "apply";
|
||||
|
||||
return {
|
||||
commit: discard ? "discard" : "apply",
|
||||
commit,
|
||||
tail: nextTail,
|
||||
head: nextHead,
|
||||
cursor: nextCursor,
|
||||
@@ -1135,7 +1169,7 @@ function applyCanonicalUserMessageRevision(input: {
|
||||
messageId: event.item.messageId,
|
||||
text: event.item.text,
|
||||
timestamp: input.timestamp,
|
||||
timelineCursor: { epoch, seq },
|
||||
timelineCursor: existing.timelineCursor,
|
||||
}),
|
||||
insert: "none",
|
||||
presentation: "existing",
|
||||
|
||||
Reference in New Issue
Block a user