Files
orca/src/renderer/src/runtime/structured-agent-session-client.test.ts
T
Brennan BensonandMerge Sim f55b7ba680 fix(native-chat): cancel pending prompts precisely (#20601)
* fix(native-chat): hide activity while awaiting input

* fix(native-chat): keep approval turns cancellable

* test(native-chat): satisfy split PR quality gate

* fix(native-chat): catalog approval cancellation label

* fix(native-chat): include approval cancellation runtime label

* fix(codex): settle prompts when cancelled turns complete

* fix(codex): settle prompt registry fallbacks

* test(native-chat): cover pending interaction fallbacks

* test(native-chat): split prompt state coverage

* test(native-chat): keep prompt state isolated

* fix(native-chat): bound prompt turn backfill

* refactor(codex): centralize prompt registry bounds

* fix(native-chat): cancel pending prompts precisely

* fix(native-chat): consolidate capability imports

* fix(native-chat): harden precise prompt cancellation

* fix claude cancellation teardown races

* retry claude prompt lifecycle admission

* bound claude prompt cancellation retry work

* fix(codex): bound prompt turn identity on registration

* fix(native-chat): route rejected late dispatch settlements

* fix(codex): retain exact cancellable prompt turn ids

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-14 14:59:03 -07:00

152 lines
5.4 KiB
TypeScript

// @vitest-environment happy-dom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
AGENT_SESSION_PROMPT_CANCEL_RUNTIME_CAPABILITY,
AGENT_SESSION_REWIND_RUNTIME_CAPABILITY
} from '../../../shared/protocol-version'
const mocks = vi.hoisted(() => ({
subscribe: vi.fn(),
call: vi.fn(),
supportsCapability: vi.fn(),
readLocalCapabilities: vi.fn(),
ensureLocalCapabilities: vi.fn()
}))
vi.mock('./runtime-environment-revision', () => ({
getRuntimeEnvironmentRevision: () => 7
}))
vi.mock('./runtime-rpc-client', () => ({
callRuntimeRpc: mocks.call,
runtimeEnvironmentSupportsCapability: mocks.supportsCapability
}))
vi.mock('./local-runtime-capabilities', () => ({
readLocalRuntimeCapabilitiesOrUnknown: mocks.readLocalCapabilities,
ensureLocalRuntimeCapabilities: mocks.ensureLocalCapabilities
}))
import {
callStructuredAgentSession,
subscribeStructuredAgentSession,
supportsStructuredAgentSessionPromptCancel
} from './structured-agent-session-client'
describe('structured prompt cancellation capability', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.readLocalCapabilities.mockReturnValue(null)
mocks.ensureLocalCapabilities.mockResolvedValue(null)
})
it('uses the local status cache and fails closed until the host answers', async () => {
const target = { kind: 'local' } as const
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(false)
mocks.ensureLocalCapabilities.mockResolvedValue([
AGENT_SESSION_PROMPT_CANCEL_RUNTIME_CAPABILITY
])
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(true)
mocks.readLocalCapabilities.mockReturnValue([AGENT_SESSION_PROMPT_CANCEL_RUNTIME_CAPABILITY])
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(true)
expect(mocks.ensureLocalCapabilities).toHaveBeenCalledTimes(2)
})
it('checks the selected remote runtime and downgrades on absent or failed capability', async () => {
const target = { kind: 'environment', environmentId: 'ssh-env-1' } as const
mocks.supportsCapability.mockResolvedValueOnce(true).mockResolvedValueOnce(false)
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(true)
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(false)
mocks.supportsCapability.mockRejectedValue(new Error('Disconnected'))
await expect(supportsStructuredAgentSessionPromptCancel(target)).resolves.toBe(false)
expect(mocks.supportsCapability).toHaveBeenCalledWith(
'ssh-env-1',
AGENT_SESSION_PROMPT_CANCEL_RUNTIME_CAPABILITY
)
})
})
describe('callStructuredAgentSession rewind capability', () => {
const target = { kind: 'environment', environmentId: 'env-1' } as const
const params = { itemId: 'item-1', expectedEpoch: 'epoch-1' }
beforeEach(() => {
vi.resetAllMocks()
mocks.call.mockResolvedValue({ ok: true })
mocks.supportsCapability.mockResolvedValue(true)
})
it('refuses an older host before dispatching rewind', async () => {
mocks.supportsCapability.mockResolvedValue(false)
await expect(callStructuredAgentSession(target, 'agentSession.rewind', params)).rejects.toThrow(
'Rewinding requires a newer Orca server'
)
expect(mocks.supportsCapability).toHaveBeenCalledExactlyOnceWith(
'env-1',
AGENT_SESSION_REWIND_RUNTIME_CAPABILITY
)
expect(mocks.call).not.toHaveBeenCalled()
})
it('dispatches rewind once the host advertises the method', async () => {
await expect(
callStructuredAgentSession(target, 'agentSession.rewind', params)
).resolves.toEqual({
ok: true
})
expect(mocks.supportsCapability).toHaveBeenCalledWith(
'env-1',
AGENT_SESSION_REWIND_RUNTIME_CAPABILITY
)
expect(mocks.call).toHaveBeenCalledExactlyOnceWith(target, 'agentSession.rewind', params)
})
it('does not dispatch rewind when host capability cannot be verified', async () => {
mocks.supportsCapability.mockRejectedValue(new Error('Host unreachable'))
await expect(callStructuredAgentSession(target, 'agentSession.rewind', params)).rejects.toThrow(
'Host unreachable'
)
expect(mocks.call).not.toHaveBeenCalled()
})
it('uses the local build directly and leaves existing remote methods available', async () => {
await callStructuredAgentSession({ kind: 'local' }, 'agentSession.rewind', params)
await callStructuredAgentSession(target, 'agentSession.send', params)
expect(mocks.supportsCapability).not.toHaveBeenCalled()
expect(mocks.call).toHaveBeenCalledWith({ kind: 'local' }, 'agentSession.rewind', params)
expect(mocks.call).toHaveBeenCalledWith(target, 'agentSession.send', params)
})
})
describe('subscribeStructuredAgentSession', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.subscribe.mockResolvedValue({ unsubscribe: vi.fn() })
Object.assign(window, {
api: {
runtimeEnvironments: { subscribe: mocks.subscribe }
}
})
})
it('forwards graceful remote closes to the reconnect owner', async () => {
const onClose = vi.fn()
await subscribeStructuredAgentSession(
{ kind: 'environment', environmentId: 'env-1' },
{ sessionId: 'session-1' },
vi.fn(),
vi.fn(),
onClose
)
const callbacks = mocks.subscribe.mock.calls[0]?.[1] as { onClose?: () => void }
expect(callbacks.onClose).toBe(onClose)
callbacks.onClose?.()
expect(onClose).toHaveBeenCalledOnce()
})
})