diff --git a/apps/web/tests/reference-composer.e2e.ts b/apps/web/tests/reference-composer.e2e.ts index d11977ee7c..ba02023143 100644 --- a/apps/web/tests/reference-composer.e2e.ts +++ b/apps/web/tests/reference-composer.e2e.ts @@ -133,8 +133,9 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through beforeAll(async () => { scaffold = await launchWebScaffold({}) - await seedSession(scaffold, sourceSessionFixture(), SOURCE_SESSION_ID) - await seedSession(scaffold, targetSessionFixture(), TARGET_SESSION_ID) + const targetCreatedAt = Date.now() - 60_000 + await seedSession(scaffold, sourceSessionFixture(), SOURCE_SESSION_ID, undefined, undefined, { createdAt: targetCreatedAt - 1 }) + await seedSession(scaffold, targetSessionFixture(), TARGET_SESSION_ID, undefined, undefined, { createdAt: targetCreatedAt }) browser = await chromium.launch() page = await newEnglishPage(browser) tripwire = watchConsole(page) @@ -377,7 +378,11 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through const group = page.getByRole('treeitem', { name: /Ungrouped/ }) await group.waitFor({ timeout: 15_000 }) if (await group.getAttribute('aria-expanded') !== 'true') await group.click() - const target = page.getByRole('treeitem', { name: /Reference order target/ }) + // Both logs were written behind the running Host and have no cache rows, so + // cold listing uses their shared Workspace fallback. Explicit creation + // times keep the target first without opening either body for a title. + const groupSection = group.locator('xpath=ancestor::*[contains(@class, "groupSection")][1]') + const target = groupSection.locator('[role="treeitem"]').nth(1) await target.waitFor({ timeout: 15_000 }) await target.click() await page.getByRole('button', { name: /^Session recall\s*Research notes$/ }).waitFor({ timeout: 15_000 }) diff --git a/apps/web/tests/scaffold.ts b/apps/web/tests/scaffold.ts index c38d1bdf04..dadb8efc72 100644 --- a/apps/web/tests/scaffold.ts +++ b/apps/web/tests/scaffold.ts @@ -1227,6 +1227,7 @@ export async function seedSession( id: string, agentPreset?: string, fixturePath?: string, + options: { readonly createdAt?: number } = {}, ): Promise { const decoded = parseSeedFixture(realizeSeedFixture(scaffold, fixtureText, id), fixturePath) const events = decoded.events @@ -1235,10 +1236,11 @@ export async function seedSession( // An open final turn would be mutated by resume's crash repair on first // open; a committed seed must be a closed recording. if (last.type !== 'turn/end') throw new Error(`seed fixture must end in turn/end, got ${last.type}`) + const createdAt = options.createdAt ?? Date.now() - 60_000 const meta: SessionHeader = { version: SESSION_FORMAT_VERSION, id: SessionId(id), - createdAt: Date.now() - 60_000, + createdAt, isSeeded: false, cwd: scaffold.workspaceCwd, delegationDepth: 0, @@ -1248,7 +1250,7 @@ export async function seedSession( if (typeof fixtureCreatedAt !== 'number') { throw new Error('seed fixture requires a numeric createdAt header') } - const timeAnchor = fixtureCreatedAt === 0 ? meta.createdAt : fixtureCreatedAt + const timeAnchor = fixtureCreatedAt === 0 ? createdAt : fixtureCreatedAt let nextTime = timeAnchor const materializedEvents: SessionEvent[] = events.map((event) => { const time = nextTime diff --git a/packages/api/session-controller/tests/session-cold.host.spec.ts b/packages/api/session-controller/tests/session-cold.host.spec.ts index 6da0420814..ee5c41f4e2 100644 --- a/packages/api/session-controller/tests/session-cold.host.spec.ts +++ b/packages/api/session-controller/tests/session-cold.host.spec.ts @@ -172,42 +172,39 @@ describe('sessions.list cold merge', () => { expect(inspect).not.toHaveBeenCalled() }) - it('prefers a live row attached during the query without folding its seed', async () => { + it('prefers a live row attached during cache lookup without folding its seed', async () => { const ctx = new Context() await ctx.plugin(SessionStore) await ctx.plugin(AgentRegistry) const meta = header('attached-during-list', 100) - const started = Promise.withResolvers() - const release = Promise.withResolvers() providePersistence(ctx, { - list: async () => { - started.resolve(undefined) - await release.promise - return [meta] - }, + list: () => Promise.resolve([meta]), }) + const cacheLookup = vi.fn(() => { + const session = ctx.sessions.create(meta.id, { + seed: [ + { type: 'turn/start', seq: SessionSeq(0), time: 200, data: { turn: 1 } }, + { + type: 'user/message', seq: SessionSeq(1), time: 300, + data: createUserMessage({ content: [{ type: 'text', text: 'live' }], source: { kind: 'user' } }), + surfaceOp: 'append', + }, + ], + meta: { + ...meta.cwd === undefined ? {} : { cwd: meta.cwd }, + createdAt: meta.createdAt, + }, + }) + ctx.agents.register({ id: session.id, session, status: 'running', ctx } as Agent) + return undefined + }) + ctx.provide('sessionProjectionCache', { + cachedSnapshot: cacheLookup, + cachedPredecessorTitle: () => undefined, + } as never) const remote = createSessionTestRemote(ctx, { defaultModelSelection: () => ({ provider: 'p', model: 'm' }), cwd: '/tmp' }) - const listing = remote.list(request({})) - await started.promise - const session = ctx.sessions.create(meta.id, { - seed: [ - { type: 'turn/start', seq: SessionSeq(0), time: 200, data: { turn: 1 } }, - { - type: 'user/message', seq: SessionSeq(1), time: 300, - data: createUserMessage({ content: [{ type: 'text', text: 'live' }], source: { kind: 'user' } }), - surfaceOp: 'append', - }, - ], - meta: { - ...meta.cwd === undefined ? {} : { cwd: meta.cwd }, - createdAt: meta.createdAt, - }, - }) - ctx.agents.register({ id: session.id, session, status: 'running', ctx } as Agent) - release.resolve(undefined) - - const response = await listing + const response = await remote.list(request({})) if (!response.ok) throw new Error('list failed') expect(response.value.items).toEqual([ expect.objectContaining({ @@ -217,6 +214,7 @@ describe('sessions.list cold merge', () => { updatedAt: 100, }), ]) + expect(cacheLookup).toHaveBeenCalledOnce() }) })