diff --git a/packages/client/connection/src/client/fixture.ts b/packages/client/connection/src/client/fixture.ts index 82b3925aac..062a39a2cd 100644 --- a/packages/client/connection/src/client/fixture.ts +++ b/packages/client/connection/src/client/fixture.ts @@ -133,8 +133,12 @@ function buildAlphaLog(): SessionEvent[] { ] const todoArgs = JSON.stringify({ todos: fixtureTodos }) toolTurn(64, 'todo_write', todoArgs, 'Updated todo list: 1 pending, 1 in progress, 1 completed.') - // The tool appends the snapshot event inside its own turn; splice it before the trailing turn/end. - events.splice(events.length - 1, 0, { type: 'todo/write', time: time += 800, data: { todos: fixtureTodos } }) + // The real tool appends the snapshot mid-execution — between tool/call and + // tool/result — so the fixture reproduces that exact ordering (the last + // toolTurn events run ... tool/call, tool/result, step/end, turn/end). + const callIndex = events.length - 4 + const callTime = events[callIndex]?.time as number + events.splice(callIndex + 1, 0, { type: 'todo/write', time: callTime + 400, data: { todos: fixtureTodos } }) events.forEach((e, i) => { e.seq = i }) return events as unknown as SessionEvent[] } diff --git a/packages/client/connection/tests/fixture.spec.ts b/packages/client/connection/tests/fixture.spec.ts index ac32955c36..ac363334bd 100644 --- a/packages/client/connection/tests/fixture.spec.ts +++ b/packages/client/connection/tests/fixture.spec.ts @@ -71,6 +71,21 @@ describe('createFixtureApi', () => { expect(empty.result.value).toEqual({ events: [], hasMore: false }) }) + it('emits the todo/write snapshot at the real tool boundary: between tool/call and tool/result, timestamps monotonic', async () => { + const api = createFixtureApi() + const tail = await api.sessions.history(req({ sessionId: sid('fx-alpha'), maxMessages: 10 })) + if (!tail.result.ok) throw new Error('history failed') + const events = tail.result.value.events.map(e => e.event) + const todoAt = events.findIndex(e => e.type === 'todo/write') + expect(todoAt).toBeGreaterThan(0) + // Production ordering (the tool appends mid-execution): call → snapshot → result. + expect(events[todoAt - 1]?.type).toBe('tool/call') + expect(events[todoAt + 1]?.type).toBe('tool/result') + const times = events.slice(todoAt - 1, todoAt + 2).map(e => e.time) + expect(times[0]).toBeLessThanOrEqual(times[1] ?? 0) + expect(times[1]).toBeLessThanOrEqual(times[2] ?? 0) + }) + it('create adds a session and pushes host/session-added to open host streams', async () => { const api = createFixtureApi() const abort = new AbortController()