refactor: retire the session/title frame and the todos history rider from the wire

This commit is contained in:
imccyu
2026-07-27 22:43:46 +08:00
parent e0577fe8c5
commit e78edd6ae4
9 changed files with 22 additions and 104 deletions

View File

@@ -154,39 +154,6 @@ describe('mux live view computation', () => {
expect('view' in (byKey.get('tool/result:h-plain') ?? {})).toBe(false)
})
it('tail page carries the full-log todo projection; older pages and todo-less sessions omit it', async () => {
const { ctx } = await harness()
const api = createApiProxy(ctx, { provider: 'p', model: 'm', cwd: '/tmp', workspaceRoot: '/tmp' })
const session = ctx.sessions.create()
ctx.agents.register({ id: session.id, session, status: 'idle', ctx } as Agent)
// Superseded write early in the log, latest write later; enough messages to page.
session.append('todo/write', { todos: [{ content: 'old', status: 'pending' }] })
for (let turn = 0; turn < 6; turn++) {
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('user/message', { content: [{ type: 'text', text: `q${turn}` }], source: { kind: 'user' } }, { surfaceOp: 'append' })
session.append('assistant/message', { turn, step: 0, content: [{ type: 'text', text: `a${turn}` }], provenance: { provider: 'p', model: 'm' } }, { surfaceOp: 'append' })
session.append('turn/end', { turn, reason: { kind: 'completed' } })
}
session.append('todo/write', { todos: [{ content: 'current', status: 'in_progress' }] })
// Tail page limited to 2 messages: the latest todo/write may or may not sit
// in the window — the projection must come from the FULL log either way.
const tail = await api.sessions.history({ rpcId: RpcId('t-todos'), payload: { sessionId: session.id, maxMessages: 2 } })
if (!tail.result.ok) throw new Error('history failed')
expect(tail.result.value.todos).toEqual([{ content: 'current', status: 'in_progress' }])
// An older page omits the projection (session-level, tail-page-only).
const boundary = tail.result.value.events[0]?.event.seq ?? 0
const older = await api.sessions.history({ rpcId: RpcId('t-todos-2'), payload: { sessionId: session.id, beforeSeq: boundary, maxMessages: 2 } })
if (!older.result.ok) throw new Error('older failed')
expect('todos' in older.result.value).toBe(false)
// A session with no todo/write anywhere omits the field.
const bare = ctx.sessions.create()
ctx.agents.register({ id: bare.id, session: bare, status: 'idle', ctx } as Agent)
const bareTail = await api.sessions.history({ rpcId: RpcId('t-todos-3'), payload: { sessionId: bare.id } })
if (!bareTail.result.ok) throw new Error('bare failed')
expect('todos' in bareTail.result.value).toBe(false)
})
it('drops a disposed session from the live open-call table (result after dispose gets no view)', async () => {
const { ctx } = await harness()
const api = createApiProxy(ctx, { provider: 'p', model: 'm', cwd: '/tmp', workspaceRoot: '/tmp' })

View File

@@ -25,10 +25,10 @@ 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)) {
if (request.payload.sessionId === ('with-projections' as never)) {
return {
rpcId: request.rpcId,
result: { ok: true, value: { events: [], hasMore: false, todos: [{ content: 'current', status: 'in_progress' as const }] } },
result: { ok: true, value: { events: [], hasMore: false, projections: { asOfSeq: 9, values: { todos: [{ content: 'current', status: 'in_progress' as const }] } } } },
}
}
return {
@@ -128,10 +128,14 @@ 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 })
it('carries the tail-page projections block through the wire schema (Zod must not strip it)', async () => {
const response = await client().sessions.history({ sessionId: 'with-projections' as never })
expect(response.result.ok).toBe(true)
if (response.result.ok) expect(response.result.value.todos).toEqual([{ content: 'current', status: 'in_progress' }])
if (response.result.ok) {
expect(response.result.value.projections).toEqual(
{ asOfSeq: 9, values: { todos: [{ content: 'current', status: 'in_progress' }] } },
)
}
})
it('carries a business error as 200 + error result', async () => {

View File

@@ -246,23 +246,21 @@ describe('events frame schemas', () => {
const frames = [
{ type: 'session/event', sessionId: 's', event: { type: 't', seq: 0, time: 1, data: null } },
{ type: 'session/subscribed', sessionId: 's', lastSeq: -1 },
{ type: 'session/title', sessionId: 's', title: 'Durable title', eventSeq: 2, updatedAt: 3 },
{ type: 'approval/requested', sessionId: 's', approvalId: 'a', toolName: 'bash', callId: 'c', reason: 'r' },
{ type: 'approval/resolved', sessionId: 's', approvalId: 'a', outcome: 'allowed-once' },
{ type: 'question/requested', sessionId: 's', questions: [{ id: 'q', question: 'Q?', options: [{ label: 'L' }], multiSelect: true }] },
{ type: 'question/resolved', sessionId: 's', questionRpcId: 'r', outcome: 'answered' },
{ type: 'session/queued', sessionId: 's', content: [{ type: 'text', text: 'queued prompt' }], source: { kind: 'user', rpcId: 'r9' }, steering: false },
{ type: 'session/queued', sessionId: 's', content: [{ type: 'text', text: 'steer' }], source: { kind: 'user' }, steering: true },
{ type: 'session/projection', sessionId: 's', key: 'todos', value: [{ content: 'x', status: 'pending' }], seq: 7 },
{ type: 'stream/error', error: { code: 'internal', message: 'm', details: {} } },
]
for (const frame of frames) expect(muxFrameSchema.parse(frame)).toMatchObject({ type: frame.type })
expect(() => muxFrameSchema.parse({ type: 'unknown/frame' })).toThrow()
for (const invalid of [
{ type: 'session/title', sessionId: 's', title: '', eventSeq: 0, updatedAt: 1 },
{ type: 'session/title', sessionId: 's', title: 'x', eventSeq: -1, updatedAt: 1 },
{ type: 'session/title', sessionId: 's', title: 'x', eventSeq: 0.5, updatedAt: 1 },
{ type: 'session/title', sessionId: 's', title: 'x', eventSeq: 0, updatedAt: 'now' },
{ type: 'session/title', sessionId: 's', title: 'x', eventSeq: 0, updatedAt: Number.NaN },
{ type: 'session/projection', sessionId: 's', key: '', value: null, seq: 0 },
{ type: 'session/projection', sessionId: 's', key: 'todos', value: null, seq: -1 },
{ type: 'session/projection', sessionId: 's', key: 'todos', value: null, seq: 0.5 },
]) expect(() => muxFrameSchema.parse(invalid)).toThrow()
expect(askUserQuestionItemSchema.parse({ id: 'q', question: 'Q?' }).id).toBe('q')
})