import { describe, it, expect, vi } from 'vitest'; import type { RequestPermissionRequest, CreateElicitationRequest, SessionNotification } from '@agentclientprotocol/sdk'; import { buildAcpClient, type AcpTurnContext } from '../acp-client.js'; /** * buildAcpClient (v2.7 audit reshape): the shared ACP `Client` closures. These * tests cover the pure routing decisions that don't require the permission-waiter * broker machinery — the auto-select/decline fallbacks and the between-turns drop. */ describe('buildAcpClient — sessionUpdate', () => { it('drops the update when no turn is active (resolveTurn → null)', async () => { const client = buildAcpClient('/wt', () => null); // Must resolve without throwing and without an onSessionUpdate to call. await expect(client.sessionUpdate({ sessionId: 's', update: {} } as unknown as SessionNotification)).resolves.toBeUndefined(); }); it('forwards the update to the active turn', async () => { const onSessionUpdate = vi.fn(); const turn: AcpTurnContext = { taskId: 't', sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate }; const client = buildAcpClient('/wt', () => turn); const note = { sessionId: 's', update: {} } as unknown as SessionNotification; await client.sessionUpdate(note); expect(onSessionUpdate).toHaveBeenCalledWith(note); }); }); describe('buildAcpClient — requestPermission fallback (no UI routing)', () => { function req(options: Array<{ optionId: string }>): RequestPermissionRequest { return { options } as unknown as RequestPermissionRequest; } it('auto-selects the first option when there is no turn', async () => { const client = buildAcpClient('/wt', () => null); const res = await client.requestPermission(req([{ optionId: 'allow' }, { optionId: 'deny' }])); expect(res).toEqual({ outcome: { outcome: 'selected', optionId: 'allow' } }); }); it('cancels when there is no turn and no options', async () => { const client = buildAcpClient('/wt', () => null); const res = await client.requestPermission(req([])); expect(res).toEqual({ outcome: { outcome: 'cancelled' } }); }); it('auto-selects when the turn has no taskId (UI routing gated off)', async () => { const turn: AcpTurnContext = { taskId: undefined, sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate: () => {} }; const client = buildAcpClient('/wt', () => turn); const res = await client.requestPermission(req([{ optionId: 'ok' }])); expect(res).toEqual({ outcome: { outcome: 'selected', optionId: 'ok' } }); }); }); describe('buildAcpClient — elicitation fallback', () => { it('declines when there is no turn', async () => { const client = buildAcpClient('/wt', () => null); const res = await client.unstable_createElicitation!({} as CreateElicitationRequest); expect(res).toEqual({ action: 'decline' }); }); it('declines when the turn has no taskId', async () => { const turn: AcpTurnContext = { taskId: undefined, sessionId: 's', modeId: undefined, agent: 'goose', onSessionUpdate: () => {} }; const client = buildAcpClient('/wt', () => turn); const res = await client.unstable_createElicitation!({} as CreateElicitationRequest); expect(res).toEqual({ action: 'decline' }); }); }); describe('buildAcpClient — createTerminal', () => { it('returns the noop terminal id', async () => { const client = buildAcpClient('/wt', () => null); const res = await client.createTerminal!({} as never); expect(res).toEqual({ terminalId: 'noop' }); }); });