mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* fix(orchestration): stop retried failed worker starts from piling up live agents A failed worker-start keeps the terminal it created (a pane the user typed into is theirs), so a coordinator retrying a start that can never succeed left one more live agent behind on every retry, with nothing to stop it. - A --retry-of start now releases the failed attempt's still-owned terminal through the normal worker-release path (output archived, user takeover kept, recovery finishes it after a restart). - A failed start now counts toward the Task's existing three-failure circuit breaker, carried across retries; a retry of a circuit-broken Dispatch is refused with a plain error. Linear: STA-7113 * test(orchestration): type the failed-start retry fixtures without casts * fix(orchestration): carry failed-start count only along --retry-of; plain breaker refusal - Only a --retry-of start inherits its predecessor's failure count, so worker exits across separate starts keep the old behaviour. - The circuit-broken refusal carries failureCount and next steps that say to stop retrying, read the last attempt, and that its pane stays open. - A repeated failed report on a circuit-broken Dispatch is a duplicate. Linear: STA-7113 * fix(orchestration): breaker refusal only points at a terminal that is still open Linear: STA-7113 * fix(orchestration): log a retry's release that could not prove the old terminal closed Linear: STA-7113 * Preserve worker diagnostics before terminal release and mask wrapped tokens
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import type { TerminalCursorContext } from '../../shared/terminal-composer-draft'
|
||
import { projectTerminalTailLines } from './orca-runtime-terminal-projection'
|
||
|
||
function rowsOf(lines: string[]) {
|
||
return lines.map((text) => ({ text, metadata: { wrapped: false, trailingSpaces: 0 } }))
|
||
}
|
||
|
||
describe('projectTerminalTailLines', () => {
|
||
it('does not splice a scrolled viewport into unrelated tail rows', () => {
|
||
const context: TerminalCursorContext = {
|
||
rows: ['────────', '❯ proceed'],
|
||
typedRows: ['────────', '❯'],
|
||
promptGlyphBoldRows: [false, false],
|
||
rowsBelow: [],
|
||
typedRowsBelow: [],
|
||
beforeCursor: '❯ ',
|
||
afterCursor: '',
|
||
rawAfterCursor: 'proceed',
|
||
cursorHidden: false,
|
||
cursorViewportRow: 1
|
||
}
|
||
const emulator = {
|
||
getBufferTailRows: () => rowsOf(['old output', 'tail output']),
|
||
getVisibleRows: () => rowsOf(['────────', '❯ proceed']),
|
||
getVisibleBufferRange: () => ({ start: 0, endExclusive: 2, totalLength: 10 }),
|
||
getCursorLineContext: () => context
|
||
}
|
||
|
||
expect(projectTerminalTailLines(emulator, 2)).toEqual({
|
||
lines: ['old output', 'tail output'],
|
||
tailRows: rowsOf(['old output', 'tail output']).map((row) => row.metadata),
|
||
draft: 'proceed'
|
||
})
|
||
})
|
||
|
||
it('backfills the prompt row when a small limit contains only draft continuations', () => {
|
||
const context: TerminalCursorContext = {
|
||
rows: ['────────', '❯ proceed'],
|
||
typedRows: ['────────', '❯'],
|
||
promptGlyphBoldRows: [false, false],
|
||
rowsBelow: [' with', ' release'],
|
||
typedRowsBelow: ['', ''],
|
||
rowsBelowWrapped: [true, true],
|
||
beforeCursor: '❯ ',
|
||
afterCursor: '',
|
||
rawAfterCursor: 'proceed',
|
||
cursorHidden: false,
|
||
cursorViewportRow: 2
|
||
}
|
||
const emulator = {
|
||
getBufferTailRows: () => rowsOf([' with', ' release']),
|
||
getVisibleRows: () => rowsOf(['Build passed', '────────', '❯ proceed', ' with', ' release']),
|
||
getVisibleBufferRange: () => ({ start: 0, endExclusive: 5, totalLength: 5 }),
|
||
getCursorLineContext: () => context
|
||
}
|
||
|
||
expect(projectTerminalTailLines(emulator, 2)).toEqual({
|
||
lines: ['────────', '❯'],
|
||
tailRows: rowsOf(['────────', '❯']).map((row) => row.metadata),
|
||
draft: 'proceed with release'
|
||
})
|
||
})
|
||
})
|