mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 03:03:04 +00:00
fix(timeline): keep assistant block identity stable
Assign canonical block IDs during live promotion so terminal finalization cannot remount image-bearing rows.
This commit is contained in:
@@ -3462,29 +3462,39 @@ describe("processAgentStreamEvents", () => {
|
||||
expect(getAssistantTexts(result.head)).toEqual(["Identified"]);
|
||||
});
|
||||
|
||||
it("promotes completed assistant markdown blocks to tail while keeping the live block in head", () => {
|
||||
const result = processAgentStreamEvents({
|
||||
it("keeps a promoted live image block identity when the turn completes", () => {
|
||||
const imageMarkdown = "";
|
||||
const streaming = processAgentStreamEvents({
|
||||
events: [
|
||||
makeStreamReducerEvent(makeTimelineEvent("First paragraph"), 1),
|
||||
makeStreamReducerEvent(makeTimelineEvent("\n\nSecond paragraph"), 2),
|
||||
makeStreamReducerEvent(makeTimelineEvent("Introductory paragraph"), 1),
|
||||
makeStreamReducerEvent(makeTimelineEvent(`\n\n${imageMarkdown}`), 2),
|
||||
],
|
||||
currentTail: [],
|
||||
currentHead: [],
|
||||
currentCursor: undefined,
|
||||
});
|
||||
|
||||
expect(result.changedTail).toBe(true);
|
||||
expect(result.changedHead).toBe(true);
|
||||
expect(result.tail).toHaveLength(1);
|
||||
expect(result.tail[0]).toMatchObject({
|
||||
kind: "assistant_message",
|
||||
text: "First paragraph",
|
||||
});
|
||||
expect(result.head).toHaveLength(1);
|
||||
expect(result.head[0]).toMatchObject({
|
||||
kind: "assistant_message",
|
||||
text: "Second paragraph",
|
||||
expect([streaming.changedTail, streaming.changedHead]).toEqual([true, true]);
|
||||
expect(getAssistantTexts(streaming.tail)).toEqual(["Introductory paragraph"]);
|
||||
expect(getAssistantTexts(streaming.head)).toEqual([imageMarkdown]);
|
||||
const liveImageId = streaming.head[0]?.id;
|
||||
|
||||
const completed = processAgentStreamEvent({
|
||||
...baseStreamInput,
|
||||
event: { type: "turn_completed", provider: "claude" } as AgentStreamEventPayload,
|
||||
currentTail: streaming.tail,
|
||||
currentHead: streaming.head,
|
||||
currentCursor: streaming.cursor ?? undefined,
|
||||
});
|
||||
const assistantBlocks = [...completed.tail, ...completed.head].filter(
|
||||
(item): item is Extract<StreamItem, { kind: "assistant_message" }> =>
|
||||
item.kind === "assistant_message",
|
||||
);
|
||||
expect(assistantBlocks.map((item) => [item.id, item.text])).toEqual([
|
||||
[streaming.tail[0]?.id, "Introductory paragraph"],
|
||||
[liveImageId, imageMarkdown],
|
||||
]);
|
||||
expect(assistantBlocks.filter((item) => item.id === liveImageId)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("preserves a live block trailing newline after promoting completed markdown blocks", () => {
|
||||
|
||||
@@ -1501,15 +1501,6 @@ function finalizeHeadItems(head: StreamItem[]): StreamItem[] {
|
||||
if (item.kind === "thought" && item.status !== "ready") {
|
||||
return markThoughtReady(item);
|
||||
}
|
||||
if (item.kind === "assistant_message" && item.blockGroupId) {
|
||||
return {
|
||||
...item,
|
||||
id: createAssistantBlockId({
|
||||
groupId: item.blockGroupId,
|
||||
blockIndex: item.blockIndex ?? 0,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
@@ -1597,7 +1588,10 @@ function promoteCompletedAssistantBlocks(params: { tail: StreamItem[]; head: Str
|
||||
const nextTail = flushHeadToTail(params.tail, promotedItems);
|
||||
const liveItem: AssistantMessageItem = {
|
||||
...activeItem,
|
||||
id: `${blockGroupId}:head`,
|
||||
id: createAssistantBlockId({
|
||||
groupId: blockGroupId,
|
||||
blockIndex: firstBlockIndex + completedBlocks.length,
|
||||
}),
|
||||
blockGroupId,
|
||||
blockIndex: firstBlockIndex + completedBlocks.length,
|
||||
text: liveBlock,
|
||||
|
||||
Reference in New Issue
Block a user