Files
orca/src/cli/terminal-format.test.ts
T

296 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest'
import type {
RuntimeTerminalShow,
RuntimeTerminalWait,
RuntimeTerminalWaitBlockedReason
} from '../shared/runtime-terminal-contracts'
import {
formatTerminalClose,
formatTerminalFocus,
formatTerminalRead,
formatTerminalSend,
formatTerminalShow,
formatTerminalWait
} from './terminal-format'
describe('formatTerminalFocus', () => {
it('distinguishes superseded navigation from a winning focus', () => {
expect(
formatTerminalFocus({
focus: {
handle: 'term_stale',
tabId: 'tab-stale',
worktreeId: 'worktree-1',
navigated: false
}
})
).toBe(
'Focus request for terminal term_stale was superseded or host navigation was skipped (tab tab-stale).'
)
expect(
formatTerminalFocus({
focus: { handle: 'term_winner', tabId: 'tab-winner', worktreeId: 'worktree-1' }
})
).toBe('Focused terminal term_winner (tab tab-winner).')
})
})
describe('formatTerminalClose', () => {
it('prints "PTY killed." only for a confirmed kill', () => {
expect(
formatTerminalClose({ close: { handle: 'term_local', tabId: 'tab-1', ptyKilled: true } })
).toBe('Closed terminal term_local. PTY killed.')
})
it('says the remote process was not confirmed stopped instead of claiming a kill', () => {
expect(
formatTerminalClose({
close: {
handle: 'term_remote',
tabId: 'tab-1',
ptyKilled: false,
ptyStopVerdict: 'unverifiable',
ptyStopReason: 'its SSH provider is no longer registered'
}
})
).toBe(
'Closed terminal term_remote. The PTY was not confirmed stopped: its SSH provider is no longer registered.'
)
})
it('promises the reconnect replay only when the host recorded the kill for it', () => {
const close = {
handle: 'term_remote',
tabId: 'tab-1',
ptyKilled: false,
ptyStopVerdict: 'unverifiable' as const,
ptyStopReason: 'SSH connection lost, reconnecting...'
}
expect(formatTerminalClose({ close: { ...close, pendingKillRecorded: true } })).toBe(
'Closed terminal term_remote. The PTY was not confirmed stopped: SSH connection lost, reconnecting... The kill retries when the host reconnects.'
)
expect(formatTerminalClose({ close })).toBe(
'Closed terminal term_remote. The PTY was not confirmed stopped: SSH connection lost, reconnecting...'
)
})
it('names a PTY known to be live', () => {
expect(
formatTerminalClose({
close: {
handle: 'term_live',
tabId: 'tab-1',
ptyKilled: false,
ptyStopVerdict: 'live'
}
})
).toBe('Closed terminal term_live. The PTY is live.')
})
})
describe('formatTerminalSend', () => {
it('exposes the provider and healthy delivery observation', () => {
expect(
formatTerminalSend({
send: {
handle: 'term_worker',
accepted: true,
bytesWritten: 8,
prompt: {
requestId: 'prompt-healthy',
stages: ['input_accepted', 'turn_started'],
provider: 'codex',
observation: 'supported',
processIncarnation: 'inc-1',
generation: 1,
baselineWorkingSequence: 0
}
}
})
).toBe(
[
'Prompt prompt-healthy on term_worker: input_accepted -> turn_started.',
'provider: codex',
'delivery observation: supported'
].join('\n')
)
})
it.each([
{
observation: 'permission' as const,
warning: 'Resolve the permission prompt in the terminal',
nextStep: '--retry-request prompt-unhealthy'
},
{
observation: 'incarnation_replaced' as const,
warning: 'the terminal process was replaced',
nextStep: 'Inspect the current terminal before sending a new prompt'
}
])('warns and gives a next step for $observation', ({ observation, warning, nextStep }) => {
const output = formatTerminalSend({
send: {
handle: 'term_worker',
accepted: true,
bytesWritten: 8,
prompt: {
requestId: 'prompt-unhealthy',
stages: ['input_accepted'],
provider: 'codex',
observation,
processIncarnation: 'inc-1',
generation: 1,
baselineWorkingSequence: 0
}
}
})
expect(output).toContain(`provider: codex`)
expect(output).toContain(`delivery observation: ${observation}`)
expect(output).toContain(`warning: delivery was not observed`)
expect(output).toContain(warning)
expect(output).toContain(nextStep)
})
it.each([
{ provider: 'claude' as const, expected: 'no turn start was observed' },
{ provider: 'unsupported' as const, expected: 'this provider cannot report delivery' },
{ provider: 'old-host' as const, expected: 'predates durable prompt receipts' }
])('warns per provider when delivery was not observed ($provider)', ({ provider, expected }) => {
const output = formatTerminalSend({
send: {
handle: 'term_worker',
accepted: true,
bytesWritten: 8,
prompt: {
requestId: 'prompt-unobserved',
stages: ['input_accepted'],
provider,
observation: 'unsupported',
processIncarnation: 'inc-1',
generation: 1,
baselineWorkingSequence: 0
}
}
})
expect(output).toContain(expected)
})
it('names the next command when a supported send never reached turn_started', () => {
const output = formatTerminalSend({
send: {
handle: 'term_worker',
accepted: true,
bytesWritten: 8,
prompt: {
requestId: 'prompt-swallowed',
stages: ['input_accepted'],
provider: 'claude',
observation: 'supported',
processIncarnation: 'inc-1',
generation: 1,
baselineWorkingSequence: 0
}
}
})
expect(output).toContain('no turn start was observed')
expect(output).toContain('--retry-request prompt-swallowed --wait-submit <seconds>')
})
})
function showResult(reason?: RuntimeTerminalWaitBlockedReason): {
terminal: RuntimeTerminalShow
} {
return {
terminal: {
handle: 'term_agy',
ptyId: 'pty-1',
paneRuntimeId: 1,
rendererGraphEpoch: 1,
worktreeId: 'worktree-1',
worktreePath: '/tmp/w',
branch: 'main',
tabId: 'tab-1',
leafId: 'leaf-1',
title: 'Antigravity',
connected: true,
writable: true,
lastOutputAt: null,
preview: 'Do you trust the files in this folder?',
agentWait: { source: 'prompt-text', reason }
}
}
}
describe('formatTerminalShow orphaned marker', () => {
it('prints orphaned only for a terminal with no pane binding', () => {
expect(
formatTerminalShow({ terminal: { ...showResult().terminal, orphaned: true } })
).toContain('orphaned: true\nconnected: true')
expect(formatTerminalShow(showResult())).not.toContain('orphaned')
})
})
// Older hosts can mislabel other providers' dialogs with codex-* tokens; include the neutral names.
describe('blocked-reason rendering against a mixed-version host', () => {
function waitResult(blockedReason: RuntimeTerminalWaitBlockedReason): {
wait: RuntimeTerminalWait
} {
return {
wait: {
handle: 'term_agy',
condition: 'tui-idle',
satisfied: false,
status: 'running',
exitCode: null,
blockedReason
}
}
}
// Pair alias additions with unchanged tokens so missing alias logic cannot pass on absence alone.
it.each([
['codex-trust-workspace', 'codex-trust-workspace (agent-trust-workspace)'],
['codex-update-prompt', 'codex-update-prompt (agent-update-prompt)'],
['codex-cwd-prompt', 'codex-cwd-prompt (agent-cwd-prompt)'],
['codex-hooks-review-prompt', 'codex-hooks-review-prompt (agent-hooks-review-prompt)'],
['codex-interactive-prompt', 'codex-interactive-prompt (agent-interactive-prompt)'],
['agent-trust-workspace', 'agent-trust-workspace'],
['codex-model-migration-prompt', 'codex-model-migration-prompt']
] as const)('renders %s as %s on both wait and show', (reason, rendered) => {
expect(formatTerminalWait(waitResult(reason)).split('\n').at(-1)).toBe(
`blockedReason: ${rendered}`
)
expect(formatTerminalShow(showResult(reason))).toContain(
`agentWait: ${rendered} (via prompt-text)`
)
})
it('still describes a wait with no reason at all', () => {
expect(formatTerminalShow(showResult(undefined))).toContain(
'agentWait: interactive prompt (via prompt-text)'
)
})
})
describe('formatTerminalRead draft separation', () => {
it('labels composer drafts outside terminal output', () => {
const output = formatTerminalRead({
terminal: {
handle: 'term_1',
status: 'running',
tail: ['Build passed', '❯'],
truncated: false,
nextCursor: null,
source: 'screen',
draft: 'proceed with the release'
}
})
expect(output).toContain('draft: "proceed with the release"')
expect(output).toContain('\n\nBuild passed\n❯')
})
})