mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
* fix(notifications): a host with no window pushes phones when a terminal agent finishes or needs input * test: name the cast rationale in the host-delivered notification test * fix(notifications): wait out a finish's quiet window and match the window's request by its news id * fix(notifications): announce a lead agent's finish while background work runs, not its all-clear * fix(notifications): announce a lead agent's finish at once and keep it through the all-clear * fix(notifications): the host pushes only while no window runs, with the window's rules; a session boundary is no finish * chore: drop formatter churn in files this change does not touch * test(e2e): record the window's own notification requests in main * fix(notifications): a desktop whose window has not shown stays silent; seed restored panes as the window does * fix(notifications): seed a restored pane fully, and read a listed worktree's own repo
93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
agentPhaseTransition,
|
|
hookRowAgentPhase,
|
|
hookRowTransition,
|
|
nextAgentPhase,
|
|
titleAgentPhase,
|
|
type AgentPhase
|
|
} from './agent-transitions'
|
|
import { AGENT_TRANSITION_SCENARIOS } from './agent-transitions.test-fixture'
|
|
|
|
const PHASES: readonly AgentPhase[] = ['rest', 'working', 'needs-you', 'done']
|
|
|
|
describe('hookRowAgentPhase', () => {
|
|
it('maps hook states to phases, and a stopped turn to Rest', () => {
|
|
expect(hookRowAgentPhase({ state: 'working' })).toBe('working')
|
|
expect(hookRowAgentPhase({ state: 'blocked' })).toBe('needs-you')
|
|
expect(hookRowAgentPhase({ state: 'waiting' })).toBe('needs-you')
|
|
expect(hookRowAgentPhase({ state: 'done' })).toBe('done')
|
|
expect(hookRowAgentPhase({ state: 'done', interrupted: true })).toBe('rest')
|
|
expect(
|
|
hookRowAgentPhase({ state: 'done', mainAgent: { state: 'done', outcome: 'cancellation' } })
|
|
).toBe('rest')
|
|
// An error is news: it ranks as Done, like a finish.
|
|
expect(
|
|
hookRowAgentPhase({ state: 'done', mainAgent: { state: 'done', outcome: 'failure' } })
|
|
).toBe('done')
|
|
})
|
|
})
|
|
|
|
describe('titleAgentPhase', () => {
|
|
it('reads the title heuristic Smart uses', () => {
|
|
expect(titleAgentPhase('⠋ Codex working')).toBe('working')
|
|
expect(titleAgentPhase('zsh')).toBe('rest')
|
|
})
|
|
})
|
|
|
|
describe('agentPhaseTransition', () => {
|
|
it('names the news in every phase change', () => {
|
|
const table = Object.fromEntries(
|
|
PHASES.flatMap((from) =>
|
|
PHASES.map((to) => [`${from} -> ${to}`, agentPhaseTransition(from, to)])
|
|
)
|
|
)
|
|
expect(table).toEqual({
|
|
'rest -> rest': null,
|
|
'rest -> working': 'prompt-submitted',
|
|
'rest -> needs-you': 'needs-input',
|
|
'rest -> done': 'turn-finished',
|
|
'working -> rest': null,
|
|
'working -> working': null,
|
|
'working -> needs-you': 'needs-input',
|
|
'working -> done': 'turn-finished',
|
|
'needs-you -> rest': null,
|
|
'needs-you -> working': null,
|
|
'needs-you -> needs-you': null,
|
|
'needs-you -> done': 'turn-finished',
|
|
'done -> rest': null,
|
|
'done -> working': 'prompt-submitted',
|
|
'done -> needs-you': 'needs-input',
|
|
'done -> done': null
|
|
})
|
|
})
|
|
|
|
it('tells a failed turn from a finish only by its row', () => {
|
|
const failed = {
|
|
state: 'done' as const,
|
|
mainAgent: { state: 'done' as const, outcome: 'failure' as const }
|
|
}
|
|
expect(hookRowTransition('working', 'done', failed)).toBe('errored')
|
|
expect(hookRowTransition('working', 'done', { state: 'done' })).toBe('turn-finished')
|
|
expect(hookRowTransition('done', 'done', failed)).toBeNull()
|
|
})
|
|
|
|
it('names no news for a session boundary, whose phase still reads done', () => {
|
|
const boundary = { state: 'done' as const, sessionBoundary: true }
|
|
expect(hookRowAgentPhase(boundary)).toBe('done')
|
|
expect(hookRowTransition('working', 'done', boundary)).toBeNull()
|
|
expect(hookRowTransition('needs-you', 'done', boundary)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('agent transition scenarios', () => {
|
|
it.each(AGENT_TRANSITION_SCENARIOS)('$name', ({ steps }) => {
|
|
let phase: AgentPhase = 'rest'
|
|
for (const { row, transition } of steps) {
|
|
const to = nextAgentPhase(phase, hookRowAgentPhase(row))
|
|
expect(hookRowTransition(phase, to, row)).toBe(transition)
|
|
phase = to
|
|
}
|
|
})
|
|
})
|