Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
50 kB · 1369 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370import { existsSync, mkdirSync, readdirSync, realpathSync, rmSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path";import { fauxAssistantMessage, registerFauxProvider } from "@earendil-works/pi-ai";import { afterEach, describe, expect, it, vi } from "vitest";import { ENV_AGENT_DIR } from "../../src/config.js";import type { AgentSession } from "../../src/core/agent-session.js";import type { AgentSessionRuntimeConfig } from "../../src/core/agent-session-config.js";import { AgentSessionRuntime, type AgentSessionServices, type CreateAgentSessionRuntimeFactory, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices,} from "../../src/core/agent-session-runtime.js";import { AuthStorage } from "../../src/core/auth-storage.js";import type { SubagentRuntimeHost } from "../../src/core/rlm-runtime.js";import { createAgentSession } from "../../src/core/sdk.js";import { deriveSemanticEdges, readSemanticEdgeLedger, SEMANTIC_EDGES_LEDGER_FILENAME,} from "../../src/core/semantic-edges.js";import { SESSION_LEASE_OWNER_ID_ENV, SESSION_LEASES_ENABLED_ENV } from "../../src/core/session-lease.js";import { SessionManager } from "../../src/core/session-manager.js";import { SettingsManager } from "../../src/core/settings-manager.js";import type { ExtensionAPI, ExtensionFactory, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionShutdownEvent, SessionStartEvent,} from "../../src/index.js";import { createDefaultRuntimeFactory } from "../../src/main.js";import { assistantMsg, createTestResourceLoader, userMsg, writeTestRlmPolicy } from "../utilities.js";import { conversationMessages, createHarness, getMessageText, type Harness } from "./harness.js";
type RecordedSessionEvent = | SessionBeforeSwitchEvent | SessionBeforeForkEvent | SessionShutdownEvent | SessionStartEvent;
type RuntimeSubagentMapAccess = { subagentRuntimes: Map<string, AgentSessionRuntime>;};
describe("AgentSessionRuntime characterization", () => { const cleanups: Array<() => Promise<void> | void> = [];
afterEach(async () => { while (cleanups.length > 0) { await cleanups.pop()?.(); } vi.unstubAllEnvs(); });
async function createRuntimeForTest( extensionFactory: ExtensionFactory, options?: { cwd?: string; bootstrapModel?: boolean; bootstrapThinkingLevel?: boolean; inMemory?: boolean; sessionConfig?: AgentSessionRuntimeConfig; sessionManager?: SessionManager; sessionOptions?: Parameters<CreateAgentSessionRuntimeFactory>[0]["sessionOptions"]; onCreateRuntime?: (options: Parameters<CreateAgentSessionRuntimeFactory>[0]) => void; }, ) { const tempDir = options?.cwd ?? join(tmpdir(), `pi-runtime-suite-${Date.now()}-${Math.random().toString(36).slice(2)}`); mkdirSync(tempDir, { recursive: true });
const faux = registerFauxProvider({ models: [ { id: "faux-1", reasoning: true }, { id: "faux-2", reasoning: false }, ], }); faux.setResponses([fauxAssistantMessage("one"), fauxAssistantMessage("two"), fauxAssistantMessage("three")]); // A routed child reads the policy from the agent dir, so the fixture must live there. const previousAgentDir = process.env[ENV_AGENT_DIR]; process.env[ENV_AGENT_DIR] = tempDir; writeTestRlmPolicy(tempDir, [`${faux.getModel().provider}/${faux.getModel().id}`]);
const authStorage = AuthStorage.inMemory(); authStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key");
const serviceOptions = { agentDir: tempDir, authStorage, model: options?.bootstrapModel === false ? undefined : faux.getModel(), thinkingLevel: options?.bootstrapThinkingLevel === false ? undefined : undefined, resourceLoaderOptions: { extensionFactories: [ (pi: ExtensionAPI) => { pi.registerProvider(faux.getModel().provider, { baseUrl: faux.getModel().baseUrl, apiKey: "faux-key", api: faux.api, models: faux.models.map((registeredModel) => ({ id: registeredModel.id, name: registeredModel.name, api: registeredModel.api, reasoning: registeredModel.reasoning, input: registeredModel.input, cost: registeredModel.cost, contextWindow: registeredModel.contextWindow, maxTokens: registeredModel.maxTokens, })), }); extensionFactory(pi); }, ], noSkills: true, noPromptTemplates: true, noThemes: true, }, }; const createRuntime: CreateAgentSessionRuntimeFactory = async (runtimeOptions) => { options?.onCreateRuntime?.(runtimeOptions); const { cwd, sessionManager, sessionStartEvent } = runtimeOptions; const services = await createAgentSessionServices({ ...serviceOptions, cwd, }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, model: serviceOptions.model, thinkingLevel: serviceOptions.thinkingLevel, ...runtimeOptions.sessionOptions, })), services, diagnostics: services.diagnostics, }; }; const runtime = await createAgentSessionRuntime(createRuntime, { cwd: tempDir, agentDir: tempDir, sessionManager: options?.sessionManager ?? (options?.inMemory ? SessionManager.inMemory(tempDir) : SessionManager.create(tempDir, join(tempDir, "sessions"))), sessionConfig: options?.sessionConfig, sessionOptions: options?.sessionOptions, }); await runtime.session.bindExtensions({});
cleanups.push(async () => { await runtime.dispose(); faux.unregister(); if (existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } if (previousAgentDir === undefined) { delete process.env[ENV_AGENT_DIR]; } else { process.env[ENV_AGENT_DIR] = previousAgentDir; } });
return { runtime, faux, tempDir }; }
function createRuntimeWithFakeSession(options?: { onShutdown?: () => void }) { const disposeSession = vi.fn(); const session = { extensionRunner: { hasHandlers: (event: string) => event === "session_shutdown" && options?.onShutdown !== undefined, emit: async () => { options?.onShutdown?.(); }, }, setSubagentRuntimeHost: vi.fn(), dispose: disposeSession, disposeAsync: disposeSession, } as unknown as AgentSession; const services = { cwd: "/tmp", agentDir: "/tmp" } as unknown as AgentSessionServices; const createRuntime: CreateAgentSessionRuntimeFactory = async () => { throw new Error("unexpected runtime creation"); }; const runtime = new AgentSessionRuntime(session, services, createRuntime); return { runtime, disposeSession }; }
it("passes session config to replacement runtimes", async () => { const calls: Array<Parameters<CreateAgentSessionRuntimeFactory>[0]> = []; const sessionConfig: AgentSessionRuntimeConfig = { cwd: "/tmp/session-config-cwd", model: "faux-2", tools: ["bash"], }; const { runtime } = await createRuntimeForTest(() => {}, { sessionConfig, onCreateRuntime: (call) => calls.push(call), });
expect(calls).toHaveLength(1); expect(calls[0]?.sessionConfig).toBe(sessionConfig);
await runtime.newSession();
expect(calls).toHaveLength(2); expect(calls[1]?.sessionConfig).toBe(sessionConfig); });
it("copies depth across new-session parent reference edges", async () => { const { runtime } = await createRuntimeForTest(() => {}); const parentSession = runtime.session.sessionFile; if (!parentSession) throw new Error("Missing parent session file");
await runtime.newSession({ parentSession });
expect(runtime.session.sessionManager.getHeader()).toMatchObject({ parentSession, rlmDepth: 0 }); });
it("uses effective runtime depth for a parented new session from a legacy header", async () => { const tempDir = join(tmpdir(), `pi-runtime-legacy-new-${Date.now()}-${Math.random().toString(36).slice(2)}`); const sessionManager = SessionManager.create(tempDir, join(tempDir, "sessions")); sessionManager.newSession({ rlmDepth: undefined }); const parentSession = sessionManager.getSessionFile(); if (!parentSession) throw new Error("Missing parent session file"); const { runtime } = await createRuntimeForTest(() => {}, { cwd: tempDir, sessionManager, sessionOptions: { rlmDepth: 2 }, });
await runtime.newSession({ parentSession });
expect(runtime.session.sessionManager.getHeader()).toMatchObject({ parentSession, rlmDepth: 2 }); });
it.each([false, true])( "uses the effective runtime depth when forking a legacy session before its first entry (inMemory=%s)", async (inMemory) => { const tempDir = join(tmpdir(), `pi-runtime-legacy-fork-${Date.now()}-${Math.random().toString(36).slice(2)}`); const sessionManager = inMemory ? SessionManager.inMemory(tempDir) : SessionManager.create(tempDir, join(tempDir, "sessions")); sessionManager.newSession({ rlmDepth: undefined }); const firstEntry = sessionManager.appendMessage({ role: "user", content: "fork here", timestamp: 1 }); const { runtime } = await createRuntimeForTest(() => {}, { cwd: tempDir, sessionManager, sessionOptions: { rlmDepth: 2 }, });
await runtime.fork(firstEntry);
expect(runtime.session.sessionManager.getHeader()?.rlmDepth).toBe(2); expect(runtime.session.rlmDepth).toBe(2); }, );
it("disposes a runtime only once across repeated teardown calls", async () => { const shutdownEvents: SessionShutdownEvent[] = []; const beforeInvalidate = vi.fn(); const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("session_shutdown", (event) => { shutdownEvents.push(event); }); }); runtime.setBeforeSessionInvalidate(beforeInvalidate);
await Promise.all([runtime.dispose(), runtime.dispose()]); await runtime.dispose();
expect(shutdownEvents).toEqual([{ type: "session_shutdown", reason: "quit" }]); expect(beforeInvalidate).toHaveBeenCalledTimes(1); });
it("does not replay shutdown events when runtime disposal throws", async () => { let shutdownCount = 0; const { runtime, disposeSession } = createRuntimeWithFakeSession({ onShutdown: () => { shutdownCount += 1; }, }); runtime.setBeforeSessionInvalidate(() => { throw new Error("invalidate failed"); });
await expect(runtime.dispose()).rejects.toThrow("invalidate failed"); await expect(runtime.dispose()).rejects.toThrow("invalidate failed");
expect(shutdownCount).toBe(1); expect(disposeSession).toHaveBeenCalledTimes(1); });
it("continues disposing tracked subagents after one child dispose fails", async () => { const { runtime } = createRuntimeWithFakeSession(); const firstDispose = vi.fn(async () => { throw new Error("first child failed"); }); const secondDispose = vi.fn(async () => {}); const firstChild = { dispose: firstDispose } as unknown as AgentSessionRuntime; const secondChild = { dispose: secondDispose } as unknown as AgentSessionRuntime; const runtimeWithSubagents = runtime as unknown as RuntimeSubagentMapAccess; runtimeWithSubagents.subagentRuntimes.set("first", firstChild); runtimeWithSubagents.subagentRuntimes.set("second", secondChild);
await expect(runtime.dispose()).rejects.toThrow("first child failed");
expect(firstDispose).toHaveBeenCalledTimes(1); expect(secondDispose).toHaveBeenCalledTimes(1); });
it("deletes exact and replaced in-process RLM child runtimes and retained sessions", async () => { const { runtime } = await createRuntimeForTest(() => {}); const childSession = {} as AgentSession; const disposeRuntime = vi.fn(async () => {}); const childRuntime = { session: childSession, dispose: disposeRuntime } as unknown as AgentSessionRuntime; const runtimeWithSubagents = runtime as unknown as RuntimeSubagentMapAccess; runtimeWithSubagents.subagentRuntimes.set("child-1", childRuntime);
await runtime.deleteRlmSubagentRuntime("child-1", childSession);
expect(disposeRuntime).toHaveBeenCalledOnce(); expect(runtimeWithSubagents.subagentRuntimes.has("child-1")).toBe(false);
const currentSession = {} as AgentSession; const disposeReplacedRuntime = vi.fn(async () => {}); const replacedRuntime = { session: currentSession, dispose: disposeReplacedRuntime, } as unknown as AgentSessionRuntime; const disposeStaleSession = vi.fn(async () => {}); const staleSession = { disposeAsync: disposeStaleSession } as unknown as AgentSession; runtimeWithSubagents.subagentRuntimes.set("replaced-child", replacedRuntime);
await runtime.deleteRlmSubagentRuntime("replaced-child", staleSession);
expect(disposeReplacedRuntime).toHaveBeenCalledOnce(); expect(disposeStaleSession).toHaveBeenCalledOnce(); expect(runtimeWithSubagents.subagentRuntimes.has("replaced-child")).toBe(false);
const disposeRetained = vi.fn(async () => {}); const retainedSession = { disposeAsync: disposeRetained } as unknown as AgentSession; await runtime.deleteRlmSubagentRuntime("retained-child", retainedSession); expect(disposeRetained).toHaveBeenCalledOnce(); });
it("publishes in-process RLM sessions before create resolves and rejects cancelled startup", async () => { const { runtime, faux, tempDir } = await createRuntimeForTest(() => {}); const parentSession = runtime.session; const getRunStatus = vi.spyOn(parentSession, "getRlmChildRunStatus").mockReturnValue("running"); let createResolved = false; const onSessionPublished = vi.fn((session: AgentSession) => { expect(createResolved).toBe(false); expect(session.sessionName).toBe("in-process-worker"); }); const baseOptions = { parentSession, prompt: "run in process", model: faux.getModel(), thinkingLevel: "off" as const, serviceTier: null, scopedModels: [], activeToolNames: [], customTools: [], includeGoals: false, includeCompactSkill: false, rlmDepth: 1, rlmMaxDepth: 2, };
const childRuntime = await runtime.createRlmSubagentRuntime({ ...baseOptions, id: "in-process-child", sessionName: "in-process-worker", sessionDir: join(tempDir, "in-process-child"), rlmParentNodeId: "in-process-child", onSessionPublished, }); createResolved = true; expect(onSessionPublished).toHaveBeenCalledOnce(); await runtime.deleteRlmSubagentRuntime("in-process-child", childRuntime.session);
getRunStatus.mockReturnValue("cancelled"); await expect( runtime.createRlmSubagentRuntime({ ...baseOptions, id: "cancelled-child", sessionName: "cancelled-worker", sessionDir: join(tempDir, "cancelled-child"), rlmParentNodeId: "cancelled-child", }), ).rejects.toThrow("startup was cancelled"); expect((runtime as unknown as RuntimeSubagentMapAccess).subagentRuntimes.has("cancelled-child")).toBe(false); });
it("releases a failed child run from the inline runtime host", async () => { const { runtime } = await createRuntimeForTest(() => {}); const deleteRlmSubagentRuntime = vi.spyOn(runtime, "deleteRlmSubagentRuntime"); vi.spyOn(runtime, "createRlmSubagentRuntime").mockImplementationOnce(async (options) => { const childRuntime = await AgentSessionRuntime.prototype.createRlmSubagentRuntime.call(runtime, options); vi.spyOn(childRuntime.session, "promptAndWait").mockRejectedValue(new Error("child run failed")); return childRuntime; });
runtime.session.hasCalledRlmModels = true; await runtime.session.runRlmChild("fail after startup", { model: `${runtime.session.model!.provider}/${runtime.session.model!.id}`, });
await vi.waitFor(() => expect(deleteRlmSubagentRuntime).toHaveBeenCalledOnce()); expect(runtime.listSubagentRuntimes()).toEqual([]); });
it("plumbs the parent agent identity into runtime-created child prompts", async () => { const { runtime } = await createRuntimeForTest(() => {}); runtime.session.setSessionName("parent-worker"); const childRuntime = await runtime.createRlmSubagentRuntime({ parentSession: runtime.session, id: "parent-agent-child", prompt: "inspect parent identity", sessionName: "child-worker", sessionDir: join(runtime.cwd, "parent-agent-child"), model: runtime.session.model!, thinkingLevel: "off", serviceTier: null, scopedModels: [], activeToolNames: [], customTools: [], includeGoals: false, includeCompactSkill: false, rlmDepth: 1, rlmMaxDepth: 2, rlmParentNodeId: "parent-agent-child", });
expect(childRuntime.session.systemPrompt).toContain("spawned by parent-worker"); await runtime.deleteRlmSubagentRuntime("parent-agent-child", childRuntime.session); });
it("plumbs semantic-edge ancestry into runtime-created child ledgers", async () => { const { runtime, tempDir } = await createRuntimeForTest(() => {}); const spawnedByRequestId = "a".repeat(32); const sessionDir = join(tempDir, "lineage-child"); const childRuntime = await runtime.createRlmSubagentRuntime({ parentSession: runtime.session, id: "lineage-child", prompt: "carry ancestry", sessionName: "lineage-worker", sessionDir, model: runtime.session.model!, thinkingLevel: "off", serviceTier: null, scopedModels: [], activeToolNames: [], customTools: [], includeGoals: false, includeCompactSkill: false, rlmDepth: 1, rlmMaxDepth: 2, rlmParentNodeId: "lineage-child", spawnedByRequestId, });
expect(readSemanticEdgeLedger(join(sessionDir, SEMANTIC_EDGES_LEDGER_FILENAME))[0]).toMatchObject({ type: "session_registered", parent_session_id: runtime.session.sessionId, spawned_by_request_id: spawnedByRequestId, }); await runtime.deleteRlmSubagentRuntime("lineage-child", childRuntime.session); });
it("keeps semantic spawn lineage through the production runtime factory", async () => { const tempDir = join(tmpdir(), `pi-runtime-factory-${Date.now()}-${Math.random().toString(36).slice(2)}`); vi.stubEnv("HOME", tempDir); cleanups.push(() => { vi.unstubAllEnvs(); }); mkdirSync(tempDir, { recursive: true }); cleanups.push(() => rmSync(tempDir, { recursive: true, force: true })); const faux = registerFauxProvider({ models: [{ id: "faux-1", reasoning: false }] }); cleanups.push(() => faux.unregister());
// The PRODUCTION factory (daemon workers and runtime hosts create every // session through it), not a test factory that forwards all options: the // original defect lived in its sessionOptions whitelist and stayed // invisible to factory-boundary assertions. const factory = createDefaultRuntimeFactory( { agentDir: tempDir, cwd: tempDir, sessionDir: join(tempDir, "sessions"), noSkills: true, noPromptTemplates: true, noThemes: true, telemetryDisabled: true, }, [ (pi: ExtensionAPI) => { pi.registerProvider(faux.getModel().provider, { baseUrl: faux.getModel().baseUrl, apiKey: "faux-key", api: faux.api, models: faux.models.map((registeredModel) => ({ id: registeredModel.id, name: registeredModel.name, api: registeredModel.api, reasoning: registeredModel.reasoning, input: registeredModel.input, cost: registeredModel.cost, contextWindow: registeredModel.contextWindow, maxTokens: registeredModel.maxTokens, })), }); }, ], );
const childSessionDir = join(tempDir, "lineage-child"); const spawnedByRequestId = "a".repeat(32); const created = await factory({ cwd: tempDir, agentDir: tempDir, sessionManager: SessionManager.create(tempDir, childSessionDir), sessionStartEvent: { type: "session_start", reason: "startup" }, sessionOptions: { model: faux.getModel(), thinkingLevel: "off", rlmDepth: 1, rlmMaxDepth: 2, rlmSessionDir: childSessionDir, rlmParentNodeId: "lineage-child", rlmParentAgent: "parent-worker", semanticParentSessionId: "parent-session-id", semanticSpawnedByRequestId: spawnedByRequestId, }, }); cleanups.push(() => created.session.dispose()); expect(created.services.modelRegistry.authStorage).toBe(created.services.authStorage); expect(created.services.authStorage.getPrimeCliConfigPath()).toBe(join(tempDir, ".prime", "config.json")); await created.session.bindExtensions({});
const ledgerPath = join(childSessionDir, SEMANTIC_EDGES_LEDGER_FILENAME); expect(readSemanticEdgeLedger(ledgerPath)[0]).toMatchObject({ type: "session_registered", parent_session_id: "parent-session-id", spawned_by_request_id: spawnedByRequestId, });
faux.setResponses([fauxAssistantMessage("child work")]); await created.session.prompt("do the work"); const edges = deriveSemanticEdges([readSemanticEdgeLedger(ledgerPath)]).edges; expect(edges.filter((edge) => edge.type === "subagent_call")).toMatchObject([ { source_request_id: spawnedByRequestId }, ]); });
it("disposes hosted RLM children during session replacement", async () => { const disposeRlmSubagentRuntimes = vi.fn(async () => {}); const host: SubagentRuntimeHost = { createRlmSubagentRuntime: async () => { throw new Error("unexpected child creation"); }, deleteRlmSubagentRuntime: async () => {}, disposeRlmSubagentRuntimes, }; const { runtime } = await createRuntimeForTest(() => {}); runtime.setSubagentRuntimeHost(host);
await runtime.newSession();
expect(disposeRlmSubagentRuntimes).toHaveBeenCalledTimes(1); });
it("persists message_end assistant replacements to the session manager", async () => { const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("message_end", (event) => { if (event.message.role !== "assistant") return;
return { message: { ...event.message, usage: { ...event.message.usage, cost: { ...event.message.usage.cost, total: 0.123, }, }, }, }; }); });
await runtime.session.prompt("hello");
const sessionAssistant = runtime.session.messages.find((message) => message.role === "assistant"); expect(sessionAssistant?.role).toBe("assistant"); if (sessionAssistant?.role !== "assistant") { throw new Error("missing assistant message"); } expect(sessionAssistant.usage.cost.total).toBe(0.123);
const persistedAssistant = runtime.session.sessionManager .getEntries() .filter((entry) => entry.type === "message") .map((entry) => entry.message) .find((message) => message.role === "assistant"); expect(persistedAssistant?.role).toBe("assistant"); if (persistedAssistant?.role !== "assistant") { throw new Error("missing persisted assistant message"); } expect(persistedAssistant.usage.cost.total).toBe(0.123); });
it("emits session_before_switch and session_start for new and resume flows", async () => { const events: RecordedSessionEvent[] = []; const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("session_before_switch", (event) => { events.push(event); }); pi.on("session_shutdown", (event) => { events.push(event); }); pi.on("session_start", (event) => { events.push(event); }); });
expect(events).toEqual([{ type: "session_start", reason: "startup" }]); events.length = 0;
await runtime.session.prompt("hello"); const originalSessionFile = runtime.session.sessionFile; const originalSession = runtime.session;
const newSessionResult = await runtime.newSession(); expect(newSessionResult.cancelled).toBe(false); await runtime.session.bindExtensions({}); expect(runtime.session).not.toBe(originalSession); expect(conversationMessages(runtime.session)).toEqual([]); const secondSessionFile = runtime.session.sessionFile; expect(events).toEqual([ { type: "session_before_switch", reason: "new", targetSessionFile: undefined }, { type: "session_shutdown", reason: "new", targetSessionFile: secondSessionFile }, { type: "session_start", reason: "new", previousSessionFile: originalSessionFile }, ]);
events.length = 0;
const switchResult = await runtime.switchSession(originalSessionFile!); expect(switchResult.cancelled).toBe(false); await runtime.session.bindExtensions({}); expect(events).toEqual([ { type: "session_before_switch", reason: "resume", targetSessionFile: originalSessionFile }, { type: "session_shutdown", reason: "resume", targetSessionFile: originalSessionFile }, { type: "session_start", reason: "resume", previousSessionFile: secondSessionFile }, ]); });
it("honors session_before_switch cancellation for new and resume", async () => { const events: RecordedSessionEvent[] = []; let cancelReason: "new" | "resume" | undefined; const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("session_before_switch", (event) => { events.push(event); if (event.reason === cancelReason) { return { cancel: true }; } }); pi.on("session_start", (event) => { events.push(event); }); });
await runtime.session.prompt("hello"); const originalSessionFile = runtime.session.sessionFile;
cancelReason = "new"; const newResult = await runtime.newSession(); expect(newResult.cancelled).toBe(true); expect(runtime.session.sessionFile).toBe(originalSessionFile);
events.length = 0; const otherDir = join(tmpdir(), `pi-runtime-other-${Date.now()}-${Math.random().toString(36).slice(2)}`); mkdirSync(otherDir, { recursive: true }); const otherSession = SessionManager.create(otherDir, join(otherDir, "sessions")); otherSession.appendMessage({ role: "user", content: [{ type: "text", text: "other" }], timestamp: Date.now() }); const otherSessionFile = otherSession.getSessionFile(); cancelReason = "resume"; const resumeResult = await runtime.switchSession(otherSessionFile!); expect(resumeResult.cancelled).toBe(true); expect(runtime.session.sessionFile).toBe(originalSessionFile); });
it("emits session_before_fork and session_start and honors cancellation", async () => { const events: RecordedSessionEvent[] = []; let cancelNextFork = false; const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("session_before_fork", (event) => { events.push(event); if (cancelNextFork) { cancelNextFork = false; return { cancel: true }; } }); pi.on("session_shutdown", (event) => { events.push(event); }); pi.on("session_start", (event) => { events.push(event); }); });
events.length = 0; await runtime.session.prompt("hello"); const userMessage = runtime.session.getUserMessagesForForking()[0]!; const previousSessionFile = runtime.session.sessionFile;
const successResult = await runtime.fork(userMessage.entryId); expect(successResult.cancelled).toBe(false); expect(successResult.selectedText).toBe("hello"); await runtime.session.bindExtensions({}); expect(events).toEqual([ { type: "session_before_fork", entryId: userMessage.entryId, position: "before" }, { type: "session_shutdown", reason: "fork", targetSessionFile: runtime.session.sessionFile }, { type: "session_start", reason: "fork", previousSessionFile }, ]);
events.length = 0; cancelNextFork = true; const cancelResult = await runtime.fork(userMessage.entryId); expect(cancelResult).toEqual({ cancelled: true }); expect(events).toEqual([{ type: "session_before_fork", entryId: userMessage.entryId, position: "before" }]);
events.length = 0; cancelNextFork = true; const cancelAtResult = await runtime.fork("missing-entry", { position: "at" }); expect(cancelAtResult).toEqual({ cancelled: true }); expect(events).toEqual([{ type: "session_before_fork", entryId: "missing-entry", position: "at" }]); });
it("forks before a selected middle prompt and preserves its selection metadata", async () => { const { runtime } = await createRuntimeForTest(() => {}); await runtime.session.prompt("Say one"); await runtime.session.prompt("Say two"); await runtime.session.prompt("Say three"); const userMessages = runtime.session.getUserMessagesForForking(); expect(userMessages.map((message) => message.text)).toEqual(["Say one", "Say two", "Say three"]);
const result = await runtime.fork(userMessages[1]!.entryId);
expect(result).toEqual({ cancelled: false, selectedText: "Say two" }); expect( conversationMessages(runtime.session).map((message) => message.role === "user" ? typeof message.content === "string" ? message.content : message.content .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join("") : message.role, ), ).toEqual(["Say one", "assistant"]); expect(runtime.session.sessionFile).toBeDefined(); });
it("forks before the first prompt in-memory and preserves its selection metadata", async () => { const { runtime } = await createRuntimeForTest(() => {}, { inMemory: true }); await runtime.session.prompt("Say one"); const userMessages = runtime.session.getUserMessagesForForking();
const result = await runtime.fork(userMessages[0]!.entryId);
expect(result).toEqual({ cancelled: false, selectedText: "Say one" }); expect(conversationMessages(runtime.session)).toEqual([]); expect(runtime.session.sessionFile).toBeUndefined(); });
it("duplicates the current active branch when forking at the current position", async () => { const { runtime } = await createRuntimeForTest(() => {}); await runtime.session.prompt("hello"); await runtime.session.prompt("again");
const beforeMessages = conversationMessages(runtime.session).map((message) => ({ role: message.role, text: message.role === "user" ? typeof message.content === "string" ? message.content : message.content .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join("") : undefined, })); const previousSessionFile = runtime.session.sessionFile; const leafId = runtime.session.sessionManager.getLeafId(); expect(leafId).toBeTruthy();
const result = await runtime.fork(leafId!, { position: "at" }); expect(result).toEqual({ cancelled: false, selectedText: undefined }); expect(runtime.session.sessionFile).not.toBe(previousSessionFile); expect( conversationMessages(runtime.session).map((message) => ({ role: message.role, text: message.role === "user" ? typeof message.content === "string" ? message.content : message.content .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join("") : undefined, })), ).toEqual(beforeMessages); });
it("duplicates the current active branch in-memory when forking at the current position", async () => { const { runtime } = await createRuntimeForTest(() => {}, { inMemory: true });
await runtime.session.prompt("hello"); await runtime.session.prompt("again");
const beforeMessages = conversationMessages(runtime.session).map((message) => ({ role: message.role, text: message.role === "user" ? typeof message.content === "string" ? message.content : message.content .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join("") : undefined, })); const leafId = runtime.session.sessionManager.getLeafId(); expect(leafId).toBeTruthy(); expect(runtime.session.sessionFile).toBeUndefined();
const result = await runtime.fork(leafId!, { position: "at" }); expect(result).toEqual({ cancelled: false, selectedText: undefined }); expect(runtime.session.sessionFile).toBeUndefined(); expect( conversationMessages(runtime.session).map((message) => ({ role: message.role, text: message.role === "user" ? typeof message.content === "string" ? message.content : message.content .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join("") : undefined, })), ).toEqual(beforeMessages); });
it("throws when forking with an invalid entry id", async () => { const { runtime } = await createRuntimeForTest(() => {}); await expect(runtime.fork("missing-entry")).rejects.toThrow("Invalid entry ID for forking"); });
it("updates the runtime session cwd on cross-cwd session replacement", async () => { const firstDir = join(tmpdir(), `pi-runtime-cwd-a-${Date.now()}-${Math.random().toString(36).slice(2)}`); const secondDir = join(tmpdir(), `pi-runtime-cwd-b-${Date.now()}-${Math.random().toString(36).slice(2)}`); mkdirSync(firstDir, { recursive: true }); mkdirSync(secondDir, { recursive: true }); const { runtime, faux, tempDir } = await createRuntimeForTest(() => {}, { cwd: firstDir }); const otherAuthStorage = AuthStorage.inMemory(); otherAuthStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key"); const otherRuntimeOptions = { agentDir: tempDir, authStorage: otherAuthStorage, resourceLoaderOptions: { extensionFactories: [ (pi: ExtensionAPI) => { pi.registerProvider(faux.getModel().provider, { baseUrl: faux.getModel().baseUrl, apiKey: "faux-key", api: faux.api, models: faux.models.map((registeredModel) => ({ id: registeredModel.id, name: registeredModel.name, api: registeredModel.api, reasoning: registeredModel.reasoning, input: registeredModel.input, cost: registeredModel.cost, contextWindow: registeredModel.contextWindow, maxTokens: registeredModel.maxTokens, })), }); }, ], noSkills: true, noPromptTemplates: true, noThemes: true, }, }; const createOtherRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent, }) => { const services = await createAgentSessionServices({ ...otherRuntimeOptions, cwd, }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, })), services, diagnostics: services.diagnostics, }; }; const otherRuntime = await createAgentSessionRuntime(createOtherRuntime, { cwd: secondDir, agentDir: tempDir, sessionManager: SessionManager.create(secondDir, join(secondDir, "sessions")), }); cleanups.push(async () => { await otherRuntime.dispose(); }); await otherRuntime.session.prompt("other"); const otherSessionFile = otherRuntime.session.sessionFile!; await otherRuntime.dispose();
await runtime.switchSession(otherSessionFile);
expect(realpathSync(runtime.session.sessionManager.getCwd())).toBe(realpathSync(secondDir)); expect(realpathSync(runtime.cwd)).toBe(realpathSync(secondDir)); });
it("restores model and thinking state from the destination session", async () => { const { runtime, faux, tempDir } = await createRuntimeForTest(() => {}, { bootstrapModel: false, bootstrapThinkingLevel: false, }); const otherDir = join(tempDir, "other"); mkdirSync(otherDir, { recursive: true }); const otherAuthStorage = AuthStorage.inMemory(); otherAuthStorage.setRuntimeApiKey(faux.getModel().provider, "faux-key"); const otherRuntimeOptions = { agentDir: tempDir, authStorage: otherAuthStorage, resourceLoaderOptions: { extensionFactories: [ (pi: ExtensionAPI) => { pi.registerProvider(faux.getModel().provider, { baseUrl: faux.getModel().baseUrl, apiKey: "faux-key", api: faux.api, models: faux.models.map((registeredModel) => ({ id: registeredModel.id, name: registeredModel.name, api: registeredModel.api, reasoning: registeredModel.reasoning, input: registeredModel.input, cost: registeredModel.cost, contextWindow: registeredModel.contextWindow, maxTokens: registeredModel.maxTokens, })), }); }, ], noSkills: true, noPromptTemplates: true, noThemes: true, }, }; const createOtherRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent, }) => { const services = await createAgentSessionServices({ ...otherRuntimeOptions, cwd, }); return { ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, })), services, diagnostics: services.diagnostics, }; }; const otherRuntime = await createAgentSessionRuntime(createOtherRuntime, { cwd: otherDir, agentDir: tempDir, sessionManager: SessionManager.create(otherDir, join(otherDir, "sessions")), }); cleanups.push(async () => { await otherRuntime.dispose(); }); await otherRuntime.session.setModel(faux.getModel("faux-2")!); otherRuntime.session.setThinkingLevel("off"); await otherRuntime.session.prompt("hello"); const targetSessionFile = otherRuntime.session.sessionFile!; await otherRuntime.dispose();
await runtime.switchSession(targetSessionFile);
expect(runtime.session.model?.id).toBe("faux-2"); expect(runtime.session.thinkingLevel).toBe("off"); });
it("runs beforeSessionInvalidate after session_shutdown and before rebindSession", async () => { const phases: string[] = []; const { runtime } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.on("session_shutdown", () => { phases.push("session_shutdown"); }); }); const oldSession = runtime.session; runtime.setBeforeSessionInvalidate(() => { phases.push("beforeSessionInvalidate"); expect(oldSession.extensionRunner.createContext().cwd).toBe(oldSession.sessionManager.getCwd()); }); runtime.setRebindSession(async () => { phases.push("rebindSession"); });
await runtime.newSession();
expect(phases).toEqual(["session_shutdown", "beforeSessionInvalidate", "rebindSession"]); expect(() => oldSession.extensionRunner.createContext().cwd).toThrow("stale after session replacement or reload"); runtime.setBeforeSessionInvalidate(undefined); runtime.setRebindSession(undefined); });
it("releases a replacement lease when current-session teardown fails", async () => { vi.stubEnv(SESSION_LEASES_ENABLED_ENV, "1"); vi.stubEnv(SESSION_LEASE_OWNER_ID_ENV, "runtime-events"); const { runtime } = await createRuntimeForTest(() => {}); runtime.setBeforeSessionInvalidate(() => { throw new Error("teardown failed"); });
await expect(runtime.newSession()).rejects.toThrow("teardown failed"); runtime.setBeforeSessionInvalidate(undefined); const leaseRoot = join(runtime.services.agentDir, "session-leases"); expect(readdirSync(leaseRoot).filter((entry) => entry.endsWith(".lock"))).toHaveLength(1); });
/** * The runtime rebinds extensions before `withSession` runs, the callback targets the replacement * session, and stale pi/ctx handles from the replaced session stop working. */ describe("regression #2860: replaced session callbacks", () => { /** Re-create the production rebind wiring so command contexts can replace the live session. */ async function bindCommandContextActions(runtime: AgentSessionRuntime): Promise<void> { const rebindSession = async (): Promise<void> => { const session = runtime.session; await session.bindExtensions({ commandContextActions: { waitForIdle: () => session.agent.waitForIdle(), newSession: async (options) => runtime.newSession(options), fork: async (entryId, options) => ({ cancelled: (await runtime.fork(entryId, options)).cancelled }), navigateTree: async (targetId, options) => ({ cancelled: (await session.navigateTree(targetId, { ...options })).cancelled, }), switchSession: async (sessionPath, options) => runtime.switchSession(sessionPath, options), reload: async () => { await session.reload(); }, }, }); }; runtime.setRebindSession(rebindSession); await rebindSession(); }
function conversation(runtime: AgentSessionRuntime): string[] { return conversationMessages(runtime.session).map((message) => `${message.role}:${getMessageText(message)}`); }
it("rebinds before withSession, targets the replacement session, and invalidates stale pi/ctx", async () => { const events: string[] = []; let staleCtxThrows = false; let stalePiThrows = false; let oldSessionFile: string | undefined; let replacementSessionFile: string | undefined; let instanceId = 0; const { runtime, faux } = await createRuntimeForTest((pi: ExtensionAPI) => { const currentInstance = ++instanceId; pi.on("session_start", () => { events.push(`start:${currentInstance}`); }); pi.on("session_shutdown", () => { events.push(`shutdown:${currentInstance}`); }); pi.registerCommand("repro", { description: "repro", handler: async (_args, ctx) => { oldSessionFile = ctx.sessionManager.getSessionFile(); await ctx.newSession({ parentSession: oldSessionFile, withSession: async (replacedCtx) => { events.push(`with:${currentInstance}`); replacementSessionFile = replacedCtx.sessionManager.getSessionFile(); try { ctx.sessionManager.getSessionFile(); } catch { staleCtxThrows = true; } try { pi.sendUserMessage("stale message"); } catch { stalePiThrows = true; } await replacedCtx.sendUserMessage("Hello from the new session!"); }, }); }, }); }); faux.setResponses([fauxAssistantMessage("hello reply")]); await bindCommandContextActions(runtime); // The shared harness binds extensions once before the rebind wiring; start from that point. events.length = 0;
await runtime.session.prompt("/repro");
expect(events).toEqual(["shutdown:1", "start:2", "with:1"]); expect(replacementSessionFile).toBeDefined(); expect(replacementSessionFile).not.toBe(oldSessionFile); expect([staleCtxThrows, stalePiThrows]).toEqual([true, true]); expect(conversation(runtime)).toEqual(["user:Hello from the new session!", "assistant:hello reply"]); });
it("supports withSession for fork", async () => { const { runtime, faux } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.registerCommand("fork-it", { description: "fork-it", handler: async (_args, ctx) => { const leafId = ctx.sessionManager.getLeafId(); if (!leafId) throw new Error("Missing leaf id"); await ctx.fork(leafId, { position: "at", withSession: async (replacedCtx) => { await replacedCtx.sendUserMessage("fork callback message"); }, }); }, }); }); faux.setResponses([fauxAssistantMessage("seed reply"), fauxAssistantMessage("fork reply")]); await bindCommandContextActions(runtime);
await runtime.session.prompt("seed"); await runtime.session.prompt("/fork-it");
expect(conversation(runtime)).toEqual([ "user:seed", "assistant:seed reply", "user:fork callback message", "assistant:fork reply", ]); });
it("supports withSession for switchSession", async () => { let targetSessionPath = ""; const { runtime, faux } = await createRuntimeForTest((pi: ExtensionAPI) => { pi.registerCommand("switch-it", { description: "switch-it", handler: async (_args, ctx) => { await ctx.switchSession(targetSessionPath, { withSession: async (replacedCtx) => { await replacedCtx.sendUserMessage("switch callback message"); }, }); }, }); }); faux.setResponses([ fauxAssistantMessage("root reply"), fauxAssistantMessage("target reply"), fauxAssistantMessage("switch reply"), ]); await bindCommandContextActions(runtime);
await runtime.session.prompt("root"); const originalSessionPath = runtime.session.sessionFile; expect((await runtime.newSession()).cancelled).toBe(false); await runtime.session.prompt("target"); targetSessionPath = runtime.session.sessionFile!; await runtime.switchSession(originalSessionPath!);
await runtime.session.prompt("/switch-it");
expect(runtime.session.sessionFile).toBe(targetSessionPath); expect(conversation(runtime)).toEqual([ "user:target", "assistant:target reply", "user:switch callback message", "assistant:switch reply", ]); }); });});
describe("ENG-4620 fast mode settings", () => { let harness: Harness | undefined; const fastModeSessions: AgentSession[] = [];
afterEach(() => { for (const session of fastModeSessions.splice(0)) { session.dispose(); } harness?.cleanup(); harness = undefined; });
/** Codex models support the priority tier; gpt-5.3 and gpt-4-turbo do not. */ async function createFastModeHarness( models: string[], options: { api?: string; provider?: string; persistSession?: boolean } = {}, ): Promise<Harness> { harness = await createHarness({ api: options.api ?? "openai-codex-responses", provider: options.provider ?? "openai-codex", models: models.map((id) => ({ id })), persistSession: options.persistSession, }); return harness; }
function createFastModeSession( current: Harness, modelId?: string, sessionManager = SessionManager.inMemory(current.tempDir), ) { return createAgentSession({ cwd: current.tempDir, authStorage: current.authStorage, model: modelId === undefined ? current.getModel() : current.getModel(modelId), resourceLoader: createTestResourceLoader(), sessionManager, settingsManager: current.settingsManager, }); }
it.each([ { name: "explicit model selection clamps an unsupported model and restores the preference", api: "openai-codex-responses", provider: "openai-codex", models: ["gpt-5.4", "gpt-5.3"], switchTo: async (current: Harness, modelId: string) => { await current.session.setModel(current.getModel(modelId)!); return current.session.serviceTier; }, }, { name: "cycling models returns the effective service tier", api: "openai-codex-responses", provider: "openai-codex", models: ["gpt-5.4", "gpt-5.3"], switchTo: async (current: Harness, modelId: string) => { const result = await current.session.cycleModel(); expect(result?.model.id).toBe(modelId); return result?.serviceTier; }, }, { name: "OpenAI API-key models admit fast mode and clamp unsupported ones", api: "openai-responses", provider: "openai", models: ["gpt-5.5", "gpt-4-turbo"], switchTo: async (current: Harness, modelId: string) => { await current.session.setModel(current.getModel(modelId)!); return current.session.serviceTier; }, }, ])("$name", async ({ api, provider, models, switchTo }) => { const [supported, unsupported] = models as [string, string]; const current = await createFastModeHarness(models, { api, provider });
current.session.setServiceTier("priority"); expect(current.session.serviceTier).toBe("priority");
expect(await switchTo(current, unsupported)).toBe("default"); // The clamp is transient: the saved preference is untouched. expect(current.settingsManager.getDefaultServiceTier()).toBe("priority");
expect(await switchTo(current, supported)).toBe("priority"); });
it("persists the preference across settings manager restarts", async () => { const current = await createFastModeHarness(["gpt-5.4"]); const agentDir = join(current.tempDir, "agent"); const settingsManager = SettingsManager.create(current.tempDir, agentDir);
settingsManager.setDefaultServiceTier("priority"); await settingsManager.flush();
expect(SettingsManager.create(current.tempDir, agentDir).getDefaultServiceTier()).toBe("priority"); });
it("uses the saved preference for new sessions and preserves it across tree navigation", async () => { const current = await createFastModeHarness(["gpt-5.4"]); const targetId = current.sessionManager.appendMessage(userMsg("first")); current.sessionManager.appendMessage(assistantMsg("reply")); current.session.setServiceTier("priority"); current.sessionManager.appendMessage(userMsg("second")); expect(current.settingsManager.getDefaultServiceTier()).toBe("priority");
await current.session.navigateTree(targetId, { summarize: false }); expect(current.session.serviceTier).toBe("priority");
const { session } = await createFastModeSession(current); fastModeSessions.push(session); expect(session.serviceTier).toBe("priority");
session.setServiceTier("default"); const { session: nextSession } = await createFastModeSession(current); fastModeSessions.push(nextSession); expect(nextSession.serviceTier).toBe("default"); });
it.each([ { name: "a temporary clamp is not persisted into a resumed session", persistSession: true, setup: async (current: Harness) => { current.sessionManager.appendMessage(userMsg("hello")); current.session.setServiceTier("priority"); await current.session.setModel(current.getModel("gpt-5.3")!); expect(current.session.serviceTier).toBe("default"); current.session.dispose(); return current.sessionManager; }, }, { name: "a new session starting on an unsupported model still stores the preference", persistSession: false, setup: async (current: Harness) => { const sessionManager = SessionManager.inMemory(current.tempDir); current.settingsManager.setDefaultServiceTier("priority"); const { session } = await createFastModeSession(current, "gpt-5.3", sessionManager); fastModeSessions.push(session); expect(session.serviceTier).toBe("default"); session.dispose(); return sessionManager; }, }, ])("$name", async ({ persistSession, setup }) => { const current = await createFastModeHarness(["gpt-5.4", "gpt-5.3"], { persistSession });
const sessionManager = await setup(current);
expect(sessionManager.buildSessionContext().serviceTier).toBe("priority"); const { session: supportedSession } = await createFastModeSession(current, "gpt-5.4", sessionManager); fastModeSessions.push(supportedSession); expect(supportedSession.serviceTier).toBe("priority"); });});