| 1 | import assert from "node:assert/strict"; |
| 2 | import { JSDOM } from "jsdom"; |
| 3 | import { canonicalHistorySlice, canonicalMessage } from "../lib/canonicalTranscriptBackend"; |
| 4 | import { installDesktopHostStub } from "./desktopHostStub"; |
| 5 | import { mockHistoryContentField, mockHistorySlice } from "../lib/bridgeHistoryFixtures"; |
| 6 | import { makeMockSessionReaderBindings } from "../lib/sessionReaderBridge"; |
| 7 | import type { HistoryContentRef, HistoryMessage, HistorySliceRequest } from "../lib/types"; |
| 8 | |
| 9 | const marker = "ASYNC LAYOUT EXPANSION COMPLETE"; |
| 10 | const messages: HistoryMessage[] = [ |
| 11 | { role: "user", content: "question", reasoning: "inline reasoning", toolCalls: [{ id: "call-1", name: "read", arguments: "{}" }] }, |
| 12 | { role: "assistant", content: `${"preview ".repeat(700)}\n${marker}`, reasoning: "complete reasoning" }, |
| 13 | ]; |
| 14 | let contentReads = 0; |
| 15 | const host = Object.assign({ |
| 16 | async HistoryForTab() { return messages; }, |
| 17 | async HistorySliceForTab(tabID: string, req: HistorySliceRequest) { return mockHistorySlice(tabID, messages, req, true); }, |
| 18 | async HistoryContentForTab(_tabID: string, ref: HistoryContentRef) { |
| 19 | contentReads += 1; |
| 20 | return { entryId: ref.entryId, field: ref.field, chunk: 0, chunks: 1, data: mockHistoryContentField(messages[1], ref), done: true, stale: false }; |
| 21 | }, |
| 22 | }, makeMockSessionReaderBindings()); |
| 23 | |
| 24 | const view = await host.SessionOpenForTab("tab-1"); |
| 25 | assert.equal(view.recent.entries.length, 2); |
| 26 | const inline = canonicalMessage(view.recent.entries[0], view.recent.entries[0].inline); |
| 27 | assert.equal(inline.reasoning, "inline reasoning"); |
| 28 | assert.equal(inline.toolCalls?.[0].id, "call-1"); |
| 29 | const persistent = view.recent.entries[1]; |
| 30 | assert.ok(persistent.contentRef, "lazy fixture keeps a canonical content reference"); |
| 31 | assert.equal((persistent.inline as HistoryMessage | undefined)?.content.includes(marker), false, "open response contains only the legacy preview"); |
| 32 | |
| 33 | const chunk = await host.SessionHistoryContentForTab("tab-1", persistent.contentRef!, 0); |
| 34 | assert.equal(contentReads, 1, "canonical hydration preserves the legacy asynchronous read path"); |
| 35 | const decoded = new TextDecoder().decode(Uint8Array.from(atob(chunk.data), character => character.charCodeAt(0))); |
| 36 | const hydrated = canonicalMessage(persistent, JSON.parse(decoded)); |
| 37 | assert.equal(hydrated.messageId, persistent.messageId, "hydration keeps the stable message identity"); |
| 38 | assert.ok(hydrated.content.includes(marker)); |
| 39 | assert.equal(hydrated.reasoning, "complete reasoning"); |
| 40 | |
| 41 | const dom = new JSDOM("", { url: "http://localhost/" }); |
| 42 | globalThis.window = dom.window as unknown as Window & typeof globalThis; |
| 43 | const stub = installDesktopHostStub({ SessionHistoryWindowForTab: async () => ({ |
| 44 | messages: [{ ...view.recent.entries[0], position: 50, visibleTurn: 4 }], status: "ready", snapshotSequence: 0, |
| 45 | coverageSequence: 0, generation: "mock", totalTurns: 4, hasOlder: true, hasNewer: false, olderCursor: "older", |
| 46 | }) }); |
| 47 | const limited = await canonicalHistorySlice("tab-1", { cursor: "" }); |
| 48 | assert.equal(limited.entries.length, 1); |
| 49 | assert.equal(limited.hasOlder, true, "a byte-limited recent window still exposes older history"); |
| 50 | assert.ok(limited.nextCursor); |
| 51 | assert.equal(limited.revisionKnown, false, "zero sequence does not invent a durable fingerprint"); |
| 52 | stub.commands.SessionHistoryWindowForTab = async () => ({ messages: [], status: "ready", snapshotSequence: 0, coverageSequence: 0, totalTurns: 0, hasOlder: false, hasNewer: false }); |
| 53 | const empty = await canonicalHistorySlice("tab-1", { cursor: "" }); |
| 54 | assert.equal(empty.entries.length, 0, "an empty recent snapshot does not require a locator read"); |
| 55 | stub.uninstall(); |
| 56 | dom.window.close(); |
| 57 | |
| 58 | console.log("PASS protocol 7 mock sessions preserve benchmark history and lazy hydration"); |
| 59 |