diff --git a/packages/host/apiproxy/src/api/sessions.schema.ts b/packages/host/apiproxy/src/api/sessions.schema.ts index 3edf0e6014..bb77383388 100644 --- a/packages/host/apiproxy/src/api/sessions.schema.ts +++ b/packages/host/apiproxy/src/api/sessions.schema.ts @@ -78,10 +78,17 @@ export const historyEntrySchema = z.object({ view: toolEventViewSchema.optional(), }) satisfies z.ZodType> +/** One todo item of the tail page's session-level projection (the todo/write payload shape). */ +export const todoItemSchema = z.object({ + content: z.string(), + status: z.union([z.literal('pending'), z.literal('in_progress'), z.literal('completed')]), +}) + /** session.history response value. */ export const sessionHistoryValueSchema = z.object({ events: z.array(historyEntrySchema), hasMore: z.boolean(), + todos: z.array(todoItemSchema).optional(), }) satisfies z.ZodType>> /** ContentBlock passthrough: core is merge-extensible — the type discriminant envelope is strict, the rest stays wide. */ diff --git a/packages/host/apiproxy/tests/fetch-carrier.spec.ts b/packages/host/apiproxy/tests/fetch-carrier.spec.ts index d097daecef..3c2260a2ae 100644 --- a/packages/host/apiproxy/tests/fetch-carrier.spec.ts +++ b/packages/host/apiproxy/tests/fetch-carrier.spec.ts @@ -25,6 +25,12 @@ function fakeApi(overrides: Partial<{ muxFrames: MuxFrame[]; hostFrames: HostFra return { rpcId: request.rpcId, result: { ok: true, value: { sessionId: 's-new' as never } } } }, async history(request) { + if (request.payload.sessionId === ('with-todos' as never)) { + return { + rpcId: request.rpcId, + result: { ok: true, value: { events: [], hasMore: false, todos: [{ content: 'current', status: 'in_progress' as const }] } }, + } + } return { rpcId: request.rpcId, result: { ok: false, error: { code: 'session-not-found', message: 'nope', details: { sessionId: request.payload.sessionId } } }, @@ -69,6 +75,12 @@ describe('unary round trip (handler ⇄ client, no network)', () => { expect(response.rpcId).toMatch(/[0-9a-f-]{36}/) }) + it('carries the tail-page todos projection through the wire schema (Zod must not strip it)', async () => { + const response = await client().sessions.history({ sessionId: 'with-todos' as never }) + expect(response.result.ok).toBe(true) + if (response.result.ok) expect(response.result.value.todos).toEqual([{ content: 'current', status: 'in_progress' }]) + }) + it('carries a business error as 200 + error result', async () => { const response = await client().sessions.history({ sessionId: 'missing' as never }) expect(response.result.ok).toBe(false)