Merge remote-tracking branch 'origin/master' into worktree/web-session-model-selector
# Conflicts: # packages/client/connection/src/client/fixture.ts # packages/client/runtime/README.i18n.yaml # packages/client/runtime/tests/fake-api.ts # packages/client/runtime/tests/session.spec.ts # packages/client/ui-conversation/README.i18n.yaml # packages/client/ui-conversation/README.md # packages/client/ui-conversation/README.zh.md # packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
@@ -154,6 +154,39 @@ 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' })
|
||||
|
||||
@@ -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 } } },
|
||||
@@ -138,6 +144,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)
|
||||
|
||||
Reference in New Issue
Block a user