Files
orca/mobile/src/session/mobile-structured-composer-command.test.ts
T
Brennan BensonandMerge Sim f1d8545024 feat(chat): support structured /clear and /compact commands (#19164)
* feat(chat): support structured clear and compact commands

* fix(chat): authorize mobile commands and bound clear-chain projection

* fix(chat): localize conversation command send errors

* fix(chat): retain clear pane identity with reopened history

* test: account for combined structured session RPC additions

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-06 21:17:00 -07:00

96 lines
3.5 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import type { RpcClient } from '../transport/rpc-client'
import { dispatchMobileStructuredCommand } from './mobile-structured-composer-command'
function setup() {
const sendRequest = vi.fn(async (_method: string, _params: unknown, _options: unknown) => ({
ok: true,
result: { ok: true, value: { command: 'compact', state: 'completed' } }
}))
const input: Parameters<typeof dispatchMobileStructuredCommand>[0] = {
text: '/compact',
hasAttachments: false,
client: { sendRequest } as unknown as RpcClient,
sessionId: 'session',
fence: 1,
sessionKey: 'session:1',
pending: { current: false },
operationIds: new Map(),
controller: {
agent: 'codex',
snapshot: [],
invokeAction: vi.fn(async () => true),
setOption: vi.fn(async () => true),
conversationCommands: ['clear', 'compact']
},
canRun: () => true,
onError: vi.fn(),
timeoutMs: 15000
}
return { input, sendRequest }
}
describe('mobile structured conversation commands', () => {
it.each(['/clear', '/compact'])(
'uses the command RPC for %s without an ordinary send',
async (text) => {
const { input, sendRequest } = setup()
expect(await dispatchMobileStructuredCommand({ ...input, text })).toBe('accepted')
expect(sendRequest).toHaveBeenCalledWith(
'agentSession.conversationCommand',
expect.objectContaining({ command: text.slice(1) }),
expect.anything()
)
expect(input.operationIds.size).toBe(0)
}
)
it('retains the exact operation ID after an unknown response', async () => {
const { input, sendRequest } = setup()
sendRequest.mockResolvedValueOnce({
ok: true,
result: { ok: true, value: { command: 'compact', state: 'unknown' } }
})
expect(await dispatchMobileStructuredCommand(input)).toBe('unknown')
expect(await dispatchMobileStructuredCommand(input)).toBe('accepted')
expect(sendRequest.mock.calls[0]?.[1]).toEqual(sendRequest.mock.calls[1]?.[1])
})
it('retains operation identity when the host explicitly reports an unknown ledger outcome', async () => {
const { input, sendRequest } = setup()
sendRequest.mockResolvedValueOnce({
ok: true,
result: {
ok: false,
refusal: { code: 'agent_session_operation_unknown', message: 'unconfirmed' }
}
} as never)
expect(await dispatchMobileStructuredCommand(input)).toBe('unknown')
expect(await dispatchMobileStructuredCommand(input)).toBe('accepted')
expect(sendRequest.mock.calls[0]?.[1]).toEqual(sendRequest.mock.calls[1]?.[1])
})
it.each(['attachments', 'old host', 'arguments', 'pending work'])(
'guards %s without provider dispatch',
async (reason) => {
const { input, sendRequest } = setup()
if (reason === 'attachments') {
input.hasAttachments = true
}
if (reason === 'old host') {
input.controller.conversationCommands = undefined
}
if (reason === 'arguments') {
input.text = '/compact instructions'
}
if (reason === 'pending work') {
input.canRun = () => false
}
expect(await dispatchMobileStructuredCommand(input)).toBe('rejected')
expect(sendRequest).not.toHaveBeenCalled()
expect(input.onError).toHaveBeenCalled()
}
)
it('keeps ordinary messages on the existing send path', async () => {
const { input, sendRequest } = setup()
expect(await dispatchMobileStructuredCommand({ ...input, text: 'hello' })).toBeNull()
expect(sendRequest).not.toHaveBeenCalled()
})
})