fix(gui): admit todos in the history wire schema

sessionHistoryValueSchema declared only events/hasMore, so the fetch
carrier's Zod parse stripped the tail page's todos projection — the
in-process and fixture paths carried it while a real WebApiClient lost it.
The fetch-carrier spec pins the field through the wire round trip.
This commit is contained in:
Chinesezjc
2026-07-24 11:47:29 +08:00
parent 1687c2c15c
commit beb191d87f
2 changed files with 19 additions and 0 deletions

View File

@@ -78,10 +78,17 @@ export const historyEntrySchema = z.object({
view: toolEventViewSchema.optional(),
}) satisfies z.ZodType<Wire<HistoryEntry>>
/** 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<Wire<ResponseValue<'session.history'>>>
/** ContentBlock passthrough: core is merge-extensible — the type discriminant envelope is strict, the rest stays wide. */

View File

@@ -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)