import { pathToFileURL, fileURLToPath } from "node:url"; import { createHash } from "node:crypto"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { resolvePrimeRoot } from "./prime-root.mjs"; const primeRoot = resolvePrimeRoot(); const { loadExtensions } = await import(pathToFileURL(join(primeRoot, "dist/core/extensions/loader.js"))); const ai = await import(pathToFileURL(join( primeRoot, "node_modules/@earendil-works/pi-ai/dist/index.js", ))); const extensionPath = fileURLToPath(new URL("../index.ts", import.meta.url)); const root = mkdtempSync(join(tmpdir(), "prime-snapcompact-integration-")); const sessionDir = `${root}/sessions/project`; mkdirSync(sessionDir, { recursive: true }); let activeSessionId = "11111111-1111-4111-8111-111111111111"; let activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`; writeFileSync(activeSessionFile, ""); const model = { id: "gpt-5.2", name: "test vision model", api: "openai-responses", provider: "openai", baseUrl: "https://example.invalid", reasoning: true, input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 272000, maxTokens: 16384, }; const longText = "日本語の履歴と解析結果です。ファイルを確認しました。\n".repeat(6000); const usage = { input: 100, output: 100, cacheRead: 0, cacheWrite: 0, totalTokens: 200, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }; const oldMessages = [ { role: "user", content: "inspect the repository", timestamp: 1 }, { role: "assistant", content: [{ type: "text", text: longText }], api: model.api, provider: model.provider, model: model.id, usage, stopReason: "stop", timestamp: 2 }, ]; const prep = (messages, previousSummary) => ({ firstKeptEntryId: "kept-entry", messagesToSummarize: messages, turnPrefixMessages: [], isSplitTurn: false, tokensBefore: 100000, previousSummary, fileOps: { read: new Set(["src/file.ts"]), written: new Set(), edited: new Set() }, settings: { enabled: true, reserveTokens: 16384, keepRecentTokens: 20000 }, }); const controller = new AbortController(); const notifications = []; // Independent recomputation of the estimates the extension reports, so a notice // with a hardcoded or stale number cannot pass this suite. const plainText = (text) => text.replace(/[\u000e\u000f]/g, "").replaceAll("\u2588", "\n"); const conservativeTextTokens = (text) => { let nonAscii = 0; for (let index = 0; index < text.length; index++) { if (text.charCodeAt(index) > 0x7f) nonAscii++; } return Math.ceil((text.length - nonAscii) / 3 + nonAscii * 2); }; const noticeNumbers = (value) => Number(value.replaceAll(",", "")); const migrationEstimate = (message) => { const match = /about ([\d,]+) input tokens \(archive ([\d,]+), current messages ([\d,]+)\) against a ([\d,]+)-token budget; carrying ([\d,]+) of the archive's ([\d,]+) text tokens(?: and dropping ([\d,]+) of them from this request)?/.exec(message ?? ""); if (!match) return undefined; return { total: noticeNumbers(match[1]), archive: noticeNumbers(match[2]), messages: noticeNumbers(match[3]), budget: noticeNumbers(match[4]), carried: noticeNumbers(match[5]), archiveTotal: noticeNumbers(match[6]), dropped: match[7] === undefined ? 0 : noticeNumbers(match[7]), }; }; const archiveSourceTokens = (sessionId, ref) => { const artifact = JSON.parse(readFileSync( join(root, "sessions", "session-artifacts", sessionId, "snapcompact", ref.file), "utf8", )); return conservativeTextTokens(plainText(artifact.preserveData.snapcompact.text)); }; let branch = []; const ctx = { cwd: process.cwd(), hasUI: true, ui: { notify(message, level) { notifications.push({ message, level }); } }, model: { ...model }, modelRegistry: { async getApiKeyAndHeaders(targetModel) { if (targetModel?.provider === "faux") { return { ok: true, apiKey: "faux-key" }; } throw new Error("auth should not be needed in this test"); }, }, sessionManager: { getSessionFile: () => activeSessionFile, getSessionDir: () => sessionDir, getSessionId: () => activeSessionId, getBranch: () => branch, getEntries: () => branch, }, getSystemPrompt: () => "test system prompt", compact: () => { throw new Error("context hook must not compact reentrantly"); }, }; const loaded = await loadExtensions([extensionPath], process.cwd()); if (loaded.errors.length) throw new Error(JSON.stringify(loaded.errors)); loaded.runtime.getThinkingLevel = () => "medium"; const extension = loaded.extensions[0]; const before = extension.handlers.get("session_before_compact")[0]; const context = extension.handlers.get("context")[0]; const sessionStart = extension.handlers.get("session_start")[0]; const sessionCompact = extension.handlers.get("session_compact")[0]; ctx.model = { ...model, input: ["text"] }; const initialText = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: undefined, signal: controller.signal }, ctx); if (initialText !== undefined) throw new Error("initial text-only compaction did not defer to Prime native compaction"); ctx.model = { ...model }; const initialCustom = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: "focus on tests", signal: controller.signal }, ctx); if (initialCustom !== undefined) throw new Error("initial custom-instruction compaction did not defer to Prime native compaction"); const originalWarn = console.warn; console.warn = () => {}; process.env.PRIME_SNAPCOMPACT_BUN = "/definitely/missing/bun"; const initialFailure = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: [], customInstructions: undefined, signal: controller.signal }, ctx); delete process.env.PRIME_SNAPCOMPACT_BUN; console.warn = originalWarn; if (initialFailure !== undefined) throw new Error("first worker failure did not fall back to Prime native compaction"); const first = await before({ type: "session_before_compact", preparation: prep(oldMessages), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx); if (!first?.compaction?.details?.snapcompact?.artifact) throw new Error("first SnapCompact compaction missing artifact reference"); const detailsJson = JSON.stringify(first.compaction.details); if (Buffer.byteLength(detailsJson) >= 1024 * 1024) throw new Error("compaction details exceed Prime line safety limit"); if (detailsJson.includes("iVBORw0KGgo")) throw new Error("PNG data leaked into compaction details"); const ref1 = first.compaction.details.snapcompact.artifact; const compactionEntry = { type: "compaction", id: "compaction-1", parentId: null, timestamp: Date.now(), summary: first.compaction.summary, firstKeptEntryId: first.compaction.firstKeptEntryId, tokensBefore: first.compaction.tokensBefore, details: first.compaction.details, fromHook: true, }; branch = [compactionEntry, { type: "message", id: "recent-1", parentId: "compaction-1", timestamp: Date.now(), message: { role: "user", content: "continue", timestamp: Date.now() } }]; const hydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue", timestamp: Date.now() }, ]}, ctx); const visual = hydrated.messages[0]; const images = visual.content.filter((block) => block.type === "image"); if (visual.role !== "custom" || images.length < 1 || images.length > first.compaction.details.snapcompact.frameBudget) { throw new Error("vision context did not rehydrate within frame budget"); } if (images.some((image) => image.detail !== "original")) { throw new Error("vision context dropped SnapCompact frame detail"); } // A different vision model reads the same frozen epoch: model identity never // gates the archive (the oh-my-pi contract), so the frames render unclamped and // no cross-model degradation notice appears. const switchBaseline = notifications.length; ctx.model = { ...model, id: "different-model-same-provider" }; const switched = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue after model switch", timestamp: Date.now() }, ]}, ctx); const switchedImages = switched.messages[0].content.filter((block) => block.type === "image").length; if (switchedImages !== images.length) { throw new Error(`model switch changed the frame budget: ${switchedImages} of ${images.length} frames`); } if (notifications.length !== switchBaseline) { throw new Error(`model switch emitted a degradation notice: ${JSON.stringify(notifications.slice(switchBaseline))}`); } // Lane flip: the same model id and api served from another provider lane (as in // zai/glm-5.3-flash and hyper/glm-5.3-flash) is still the same model. The archive // frames are images of text, so a lane flip must not report cross-model image // support as unverified, must not clamp the frame budget, and must not force the // next compaction into the text path. ctx.model = { ...model, provider: "hyper" }; const laneFlipBaseline = notifications.length; const laneFlipped = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue on the other provider lane", timestamp: Date.now() }, ]}, ctx); const laneFlipNotices = notifications.slice(laneFlipBaseline); const laneFlipDegradation = laneFlipNotices.find((n) => /different model|cross-model/.test(n.message)); const laneFlipImages = laneFlipped.messages[0].content.filter((block) => block.type === "image").length; const laneFlipCompaction = await before({ type: "session_before_compact", preparation: prep(oldMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx); ctx.model = { ...model }; const laneFlipFailures = []; if (laneFlipDegradation) { laneFlipFailures.push(`reported a cross-model degradation: ${JSON.stringify(laneFlipDegradation)}`); } if (laneFlipImages !== images.length) { laneFlipFailures.push(`clamped the frame budget: ${laneFlipImages} of ${images.length} frames`); } const laneFlipMigration = notifications.slice(laneFlipBaseline).find((n) => /image archive to text compaction/.test(n.message)); if (laneFlipMigration) { laneFlipFailures.push(`started a text migration: ${laneFlipMigration.message}`); } if (laneFlipCompaction?.compaction?.details?.snapcompactText) { laneFlipFailures.push("forced the archive into the text path"); } else if (laneFlipCompaction?.compaction?.details?.snapcompact?.producer?.provider !== "hyper") { laneFlipFailures.push("did not stay on the visual compaction path"); } if (laneFlipFailures.length > 0) { throw new Error(`provider lane flip ${laneFlipFailures.join("; ")}`); } const steadyStateNoticeBaseline = notifications.length; // Frozen epoch layout: block bytes must not depend on the live window or the // growing tail. Every rehydration of the same archive must be byte-identical, // otherwise each change busts the prompt cache back to the system prefix. const blockSignature = (content) => createHash("sha256").update(JSON.stringify(content)).digest("hex"); let frozenSignature = blockSignature(visual.content); for (let turn = 0; turn < 20; turn++) { ctx.model = turn % 2 === 0 ? { ...model, contextWindow: 10_000, maxTokens: 8192 } : { ...model }; const turnHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, ...Array.from({ length: turn }, (_, i) => ({ role: "user", content: `tail ${i} ${longText.slice(0, 400)}`, timestamp: Date.now() + i })), ]}, ctx); const signature = blockSignature(turnHydrated.messages[0].content); if (signature !== frozenSignature) { throw new Error(`vision archive blocks changed at tail step ${turn}; prompt cache would bust`); } } if (notifications.length !== steadyStateNoticeBaseline) { throw new Error("degradation notice re-emitted during steady-state turns for the same compaction"); } ctx.model = { ...model }; ctx.model = { ...model, input: ["text"], contextWindow: 10_000, maxTokens: 8192 }; const exhaustedText = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "text context is already full", timestamp: Date.now() }, ]}, ctx); if (exhaustedText.messages[0].content.length !== 1) { throw new Error("exhausted text context forced archive text past zero budget"); } ctx.model = { ...model }; const previousSessionFile = activeSessionFile; const forkSessionId = "22222222-2222-4222-8222-222222222222"; activeSessionId = forkSessionId; activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`; writeFileSync(activeSessionFile, ""); await sessionStart({ type: "session_start", reason: "fork", previousSessionFile }, ctx); const forkArtifact = join( root, "sessions", "session-artifacts", forkSessionId, "snapcompact", ref1.file, ); if (!existsSync(forkArtifact)) throw new Error("fork did not copy its inherited SnapCompact artifact"); const forkHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue fork", timestamp: Date.now() }, ]}, ctx); if (!forkHydrated.messages[0].content.some((block) => block.type === "image")) { throw new Error("forked context did not rehydrate copied archive images"); } ctx.model = { ...model, input: ["text"] }; const nonVisionBaseline = notifications.length; await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue fork text", timestamp: Date.now() }, ]}, ctx); const nonVisionNotices = notifications.slice(nonVisionBaseline); if (nonVisionNotices.length !== 1 || nonVisionNotices[0].level !== "warning" || !nonVisionNotices[0].message.includes("this model cannot read images")) { throw new Error(`expected one non-vision degradation warning, got ${JSON.stringify(nonVisionNotices)}`); } await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "second fork text turn", timestamp: Date.now() }, ]}, ctx); if (notifications.length !== nonVisionBaseline + 1) { throw new Error("non-vision degradation notice re-emitted on the second turn of the same compaction"); } ctx.model = { ...model }; const sourceArtifact = join( root, "sessions", "session-artifacts", "11111111-1111-4111-8111-111111111111", "snapcompact", ref1.file, ); rmSync(sourceArtifact, { force: true }); activeSessionId = "33333333-3333-4333-8333-333333333333"; activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`; writeFileSync(activeSessionFile, ""); const missingSourceWarnings = []; console.warn = (...args) => missingSourceWarnings.push(args); try { await sessionStart({ type: "session_start", reason: "fork", previousSessionFile }, ctx); } finally { console.warn = originalWarn; } if (missingSourceWarnings.length > 0) { throw new Error("fork warned after its inherited artifact disappeared"); } activeSessionId = forkSessionId; activeSessionFile = `${sessionDir}/${activeSessionId}.jsonl`; const orphan = join(forkArtifact, "..", "33333333-3333-4333-8333-333333333333.json"); writeFileSync(orphan, "orphan", { mode: 0o600 }); await sessionCompact({ type: "session_compact", compactionEntry, fromExtension: true }, ctx); if (existsSync(orphan)) throw new Error("artifact cleanup did not remove an unreferenced artifact"); if (!existsSync(forkArtifact)) throw new Error("artifact cleanup removed a referenced fork artifact"); const nextMessages = [ { role: "user", content: "implement and test the fix", timestamp: 3 }, { role: "assistant", content: [{ type: "text", text: "test passed\n".repeat(3000) }], api: model.api, provider: model.provider, model: model.id, usage, stopReason: "stop", timestamp: 4 }, ]; const second = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx); if (!second?.compaction?.details?.snapcompact?.artifact) throw new Error("repeat compaction missing artifact"); if (second.compaction.details.snapcompact.artifact.sha256 === ref1.sha256) throw new Error("repeat compaction did not create a new archive"); if (JSON.stringify(second.compaction.details).includes("iVBORw0KGgo")) throw new Error("repeat details inlined PNG data"); const secondArtifactPath = join( root, "sessions", "session-artifacts", activeSessionId, "snapcompact", second.compaction.details.snapcompact.artifact.file, ); const secondArchive = JSON.parse(readFileSync(secondArtifactPath, "utf8")).preserveData.snapcompact; if (secondArchive.frames.some((frame) => frame.font !== "silver")) { throw new Error("repeat compaction did not retain the prior non-Latin-safe render shape"); } ctx.model = { ...model, input: ["text"], contextWindow: 128000 }; const textHydrated = await context({ type: "context", messages: [ { role: "compactionSummary", summary: first.compaction.summary, tokensBefore: first.compaction.tokensBefore, timestamp: Date.now() }, { role: "user", content: "continue in text", timestamp: Date.now() }, ]}, ctx); if (textHydrated.messages[0].content.some((block) => block.type === "image")) throw new Error("text-only context contains images"); const textChars = textHydrated.messages[0].content.reduce((n, block) => n + (block.type === "text" ? block.text.length : 0), 0); if (textChars > 128000 * 3) throw new Error("text-only context was not bounded"); ctx.model = { ...model, contextWindow: 20000, maxTokens: 4096 }; console.warn = () => {}; const workerFailureWithArchive = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal }, ctx); console.warn = originalWarn; if (workerFailureWithArchive?.cancel !== true) throw new Error("worker failure with an existing archive did not cancel safely"); const retainedNotice = notifications.at(-1); if ( retainedNotice?.level !== "error" || !retainedNotice.message.includes("SnapCompact visual compaction failed: SnapCompact worker failed") || !retainedNotice.message.includes("maxArchiveTokens must be positive") || !retainedNotice.message.includes("text fallback also failed") || !retainedNotice.message.includes("existing archive was retained") ) { throw new Error(`worker failure notice hid the primary error: ${JSON.stringify(retainedNotice)}`); } // Migration that cannot fit: the current messages alone exceed the safe input // budget, so the archive text cannot ride along. Compaction must still run, using // native text compaction without the archive, and say what it dropped. const overBudgetRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-over-budget-text", name: "over-budget text migration test", input: ["text"], reasoning: false, contextWindow: 20_000, maxTokens: 8192, }], tokensPerSecond: 100_000, }); const overBudgetModel = overBudgetRegistration.getModel(); overBudgetRegistration.setResponses([ ai.fauxAssistantMessage("over-budget text migration summary"), ]); ctx.model = overBudgetModel; const overBudgetBaseline = notifications.length; const expectedArchiveTokens = archiveSourceTokens(activeSessionId, ref1); const overBudget = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx); if (overBudget?.cancel === true) { throw new Error("over-budget text migration cancelled compaction instead of falling back"); } if (overBudget?.compaction?.details?.snapcompactText?.schema !== 1) { throw new Error("over-budget text migration did not compact through the native fallback"); } const overBudgetNotices = notifications.slice(overBudgetBaseline); const progressNotice = overBudgetNotices.find((n) => /image archive to text compaction/.test(n.message)); if (!progressNotice) { throw new Error(`over-budget text migration emitted no progress notice: ${JSON.stringify(overBudgetNotices)}`); } const overBudgetEstimate = migrationEstimate(progressNotice.message); if (!overBudgetEstimate) { throw new Error(`text migration progress notice carried no token estimate: ${progressNotice.message}`); } if (overBudgetEstimate.budget !== 0) { throw new Error(`over-budget migration reported budget ${overBudgetEstimate.budget}: ${progressNotice.message}`); } if (overBudgetEstimate.archiveTotal !== expectedArchiveTokens) { throw new Error(`text migration progress notice estimate ignored the archive: ${progressNotice.message}`); } if (overBudgetEstimate.carried !== 0 || overBudgetEstimate.dropped !== expectedArchiveTokens) { throw new Error(`over-budget notice reported carried ${overBudgetEstimate.carried} and dropped ${overBudgetEstimate.dropped} of ${expectedArchiveTokens}: ${progressNotice.message}`); } if (!(overBudgetEstimate.total > overBudgetEstimate.messages && overBudgetEstimate.total < overBudgetEstimate.messages + expectedArchiveTokens)) { throw new Error(`text migration progress notice reported an implausible request size of ${overBudgetEstimate.total}: ${progressNotice.message}`); } const droppedNotice = overBudgetNotices.find( (n) => n.level === "warning" && /cannot fit the archive's [\d,]+ text tokens/.test(n.message), ); if (!droppedNotice) { throw new Error(`over-budget migration did not state that the archive was dropped: ${JSON.stringify(overBudgetNotices)}`); } overBudgetRegistration.unregister(); ctx.model = { ...model }; const largeRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-large-text", name: "large text migration test", input: ["text"], reasoning: false, contextWindow: 2_000_000, maxTokens: 8192, }], tokensPerSecond: 100_000, }); const largeTextModel = largeRegistration.getModel(); largeRegistration.setResponses([ ai.fauxAssistantMessage("large text migration summary"), ]); ctx.model = largeTextModel; const largeMigration = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx); if (!largeMigration?.compaction) throw new Error("large text migration failed to compact"); if (largeMigration.compaction.details?.snapcompactText?.schema !== 1) { throw new Error("large text migration did not record snapcompactText details"); } const truncatedRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-truncated-text", name: "truncated text migration test", input: ["text"], reasoning: false, contextWindow: 60_000, maxTokens: 8192, }], tokensPerSecond: 100_000, }); const truncatedTextModel = truncatedRegistration.getModel(); truncatedRegistration.setResponses([ ai.fauxAssistantMessage("truncated text migration summary"), ]); ctx.model = truncatedTextModel; const truncatedMigration = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: "focus on tests", signal: controller.signal }, ctx); if (!truncatedMigration?.compaction) throw new Error("truncated text migration failed to compact"); if (truncatedMigration.compaction.details?.snapcompactText?.schema !== 1) { throw new Error("truncated text migration did not record snapcompactText details"); } const truncatedEstimate = migrationEstimate(notifications.at(-1)?.message); if (!truncatedEstimate) { throw new Error(`truncated text migration emitted no token estimate: ${JSON.stringify(notifications.at(-1))}`); } if (truncatedEstimate.archiveTotal !== expectedArchiveTokens) { throw new Error(`truncated migration estimate ignored the archive: ${JSON.stringify(notifications.at(-1))}`); } if (truncatedEstimate.carried <= 0 || truncatedEstimate.dropped <= 0) { throw new Error(`truncated migration did not bound one call's archive carry: ${JSON.stringify(notifications.at(-1))}`); } if (truncatedEstimate.carried + truncatedEstimate.dropped !== truncatedEstimate.archiveTotal) { throw new Error(`truncated migration notice did not account for the dropped archive text: ${JSON.stringify(notifications.at(-1))}`); } // A compaction under a different vision model stays on the visual path and // records the new model as the archive's producer; it never migrates to text. const crossModelRegistration = ai.registerFauxProvider({ models: [{ id: "snapcompact-cross-model-vision", name: "cross-model vision compaction test", input: ["text", "image"], reasoning: false, contextWindow: 2_000_000, maxTokens: 8192, }], tokensPerSecond: 100_000, }); const crossModel = crossModelRegistration.getModel(); crossModelRegistration.setResponses([ ai.fauxAssistantMessage("cross-model migration summary"), ]); ctx.model = crossModel; const crossModelCompaction = await before({ type: "session_before_compact", preparation: prep(nextMessages, first.compaction.summary), branchEntries: branch, customInstructions: undefined, signal: controller.signal, }, ctx); if (!crossModelCompaction?.compaction) throw new Error("cross-model compaction failed to compact"); if (crossModelCompaction.compaction.details?.snapcompactText) { throw new Error("cross-model compaction migrated the archive to text"); } if (crossModelCompaction.compaction.details?.snapcompact?.producer?.id !== crossModel.id) { throw new Error("cross-model compaction did not stay on the visual path"); } crossModelRegistration.unregister(); console.log(JSON.stringify({ firstDetailsBytes: Buffer.byteLength(detailsJson), firstFrames: images.length, switchedModelFrames: switchedImages, exhaustedBudgetArchiveBlocks: 0, forkArtifactCopied: true, missingForkArtifactIgnored: true, orphanGc: true, repeatFont: "silver", firstArtifactBytes: ref1.bytes, repeatArtifactBytes: second.compaction.details.snapcompact.artifact.bytes, textContextChars: textChars, firstFailureNativeFallback: initialFailure === undefined, existingFailureCancelled: workerFailureWithArchive.cancel, overBudgetFallback: overBudget.compaction.details.snapcompactText.schema, overBudgetDroppedNotice: Boolean(droppedNotice), laneFlipFrames: laneFlipImages, laneFlipCompactionPath: laneFlipCompaction?.compaction?.details?.snapcompact ? "visual" : "text", textMigrationTokensReported: expectedArchiveTokens, largeModelMigrationSuccess: Boolean(largeMigration?.compaction), truncatedModelMigrationSuccess: Boolean(truncatedMigration?.compaction), crossModelStayedVisual: crossModelCompaction?.compaction?.details?.snapcompact?.producer?.id, }, null, 2)); rmSync(root, { recursive: true, force: true });