mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 08:04:05 +00:00
fix(runtime): drop command-finished generation on prune
Pruning can remove a PTY without onPtyExit, so the settle-generation Map leaked one entry per disconnected record. Pin overlapping 133;D so a stale shell-foreground read cannot retire a later nested settle.
This commit is contained in:
@@ -13290,6 +13290,64 @@ describe('OrcaRuntimeService', () => {
|
||||
expect(retireAuthority).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('ignores a stale command-finished settle after a later nested 133;D', async () => {
|
||||
const spawn = vi.fn().mockResolvedValue({ id: 'pty-stale-d', incarnationId: 'process-stale' })
|
||||
const retireAuthority = vi.fn()
|
||||
let foregroundProcess = 'zsh'
|
||||
const confirmForegroundProcess = vi.fn(async () => foregroundProcess)
|
||||
const runtime = new OrcaRuntimeService(store, undefined, {
|
||||
attestAgentHookCompatibilityAuthority: (candidate) => ({
|
||||
paneKey: candidate.paneKey,
|
||||
source: 'current_hook'
|
||||
}),
|
||||
retireAgentHookCompatibilityAuthority: retireAuthority
|
||||
})
|
||||
runtime.setPtyController({
|
||||
spawn,
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: confirmForegroundProcess,
|
||||
confirmForegroundProcess
|
||||
})
|
||||
runtime.setNotifier({
|
||||
worktreesChanged: vi.fn(),
|
||||
reposChanged: vi.fn(),
|
||||
activateWorktree: vi.fn(),
|
||||
createTerminal: vi.fn(),
|
||||
revealTerminalSession: vi.fn().mockResolvedValue({ tabId: 'tab-stale-d' }),
|
||||
splitTerminal: vi.fn(),
|
||||
renameTerminal: vi.fn(),
|
||||
focusTerminal: vi.fn(),
|
||||
closeTerminal: vi.fn(),
|
||||
sleepWorktree: vi.fn(),
|
||||
terminalFitOverrideChanged: vi.fn(),
|
||||
terminalDriverChanged: vi.fn()
|
||||
})
|
||||
runtime.attachWindow(1)
|
||||
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
|
||||
|
||||
await runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, {
|
||||
command: 'omp',
|
||||
launchConfig: { agentCommand: 'omp', agentArgs: '', agentEnv: {} }
|
||||
})
|
||||
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
runtime.onPtyData('pty-stale-d', '\x1b]133;D;0\x07', 100)
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
runtime.onPtyData('pty-stale-d', '\x1b]133;D;0\x07', 200)
|
||||
await vi.advanceTimersByTimeAsync(COMMAND_FINISHED_LAUNCH_AUTHORITY_SETTLE_MS - 100)
|
||||
expect(retireAuthority).not.toHaveBeenCalled()
|
||||
foregroundProcess = 'omp'
|
||||
await vi.advanceTimersByTimeAsync(100)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
|
||||
expect(retireAuthority).not.toHaveBeenCalled()
|
||||
expect(confirmForegroundProcess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not treat an unverifiable command-finished foreground read as agent exit', async () => {
|
||||
const spawn = vi.fn().mockResolvedValue({ id: 'pty-unverified', incarnationId: 'process-u' })
|
||||
const retireAuthority = vi.fn()
|
||||
@@ -13503,16 +13561,19 @@ describe('OrcaRuntimeService', () => {
|
||||
issuePtyHandle: (pty: unknown) => string
|
||||
dropDisconnectedPtyRecord: (ptyId: string) => void
|
||||
syntheticTerminalHandles: Set<string>
|
||||
launchAuthorityCommandFinishedGenerationByPtyId: Map<string, number>
|
||||
}
|
||||
const pty = internals.recordPtyWorktree('pty-pruned', TEST_WORKTREE_ID, {
|
||||
connected: false
|
||||
})
|
||||
const handle = internals.issuePtyHandle(pty)
|
||||
internals.launchAuthorityCommandFinishedGenerationByPtyId.set('pty-pruned', 3)
|
||||
expect(internals.syntheticTerminalHandles.has(handle)).toBe(true)
|
||||
|
||||
internals.dropDisconnectedPtyRecord('pty-pruned')
|
||||
|
||||
expect(internals.syntheticTerminalHandles.has(handle)).toBe(false)
|
||||
expect(internals.launchAuthorityCommandFinishedGenerationByPtyId.has('pty-pruned')).toBe(false)
|
||||
})
|
||||
|
||||
it('drops an out-of-order aggregate inventory after a newer SSH inventory', async () => {
|
||||
|
||||
@@ -32946,6 +32946,7 @@ export class OrcaRuntimeService {
|
||||
private dropDisconnectedPtyRecord(ptyId: string): void {
|
||||
// Why: pruning can remove a PTY without the normal exit callback.
|
||||
this.advancePtyLifecycleGeneration(ptyId)
|
||||
this.launchAuthorityCommandFinishedGenerationByPtyId.delete(ptyId)
|
||||
this.pairedRendererSessionOwnedPtyIds.delete(ptyId)
|
||||
this.ptysById.delete(ptyId)
|
||||
this.recentPtyOutputById.delete(ptyId)
|
||||
|
||||
Reference in New Issue
Block a user