Browse Source

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.
Chinesezjc 2 months ago
parent
commit
beb191d87f

+ 7 - 0
packages/host/apiproxy/src/api/sessions.schema.ts

@@ -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. */

+ 12 - 0
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)