await provider proof for remote terminal close

This commit is contained in:
Brennan Benson
2026-07-12 21:03:31 -07:00
parent c426bc3f74
commit 94b1ffee99
3 changed files with 43 additions and 7 deletions
+1
View File
@@ -3883,6 +3883,7 @@
"file": "src/main/runtime/orca-runtime.test.ts",
"assertions": [
"terminal.stop awaits stopAndWait and fresh post-stop liveness before acknowledging provider shutdown",
"remote terminal.close rejects until provider stopAndWait verifies shutdown",
"provider shutdown rejection fails terminal.stop closed",
"project removal fences paired terminal admission through provider stop and repo mutation",
"project removal drains create-first headless, renderer-backed mobile, renderer split, and selector-less renderer admissions before its liveness snapshot",
+26 -3
View File
@@ -17690,12 +17690,13 @@ describe('OrcaRuntimeService', () => {
it('operates PTY-backed mobile session terminals without a renderer graph', async () => {
const spawn = vi.fn().mockResolvedValue({ id: 'laptop-created-pty' })
const kill = vi.fn(() => true)
const stopAndWait = vi.fn(async () => true)
const runtime = new OrcaRuntimeService(store)
runtime.setPtyController({
spawn,
write: () => true,
kill,
kill: () => false,
stopAndWait,
getForegroundProcess: async () => null
})
@@ -17719,7 +17720,29 @@ describe('OrcaRuntimeService', () => {
tabId: 'laptop-tab',
ptyKilled: true
})
expect(kill).toHaveBeenCalledWith('laptop-created-pty')
expect(stopAndWait).toHaveBeenCalledWith('laptop-created-pty')
})
it('rejects remote terminal close until provider shutdown is verified', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.setPtyController({
spawn: vi.fn().mockResolvedValue({ id: 'laptop-created-pty' }),
write: () => true,
kill: () => true,
stopAndWait: async () => false,
getForegroundProcess: async () => null
})
const terminal = await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, {
tabId: 'laptop-tab',
leafId: HEADLESS_LEAF_ID
})
await expect(runtime.closeTerminal(terminal.handle)).rejects.toThrow(
'terminal_verified_stop_failed'
)
await expect(runtime.readTerminal(terminal.handle)).resolves.toMatchObject({
status: 'running'
})
})
it('lists PTY-backed mobile session terminals without a renderer graph', async () => {
+16 -4
View File
@@ -18913,17 +18913,29 @@ export class OrcaRuntimeService {
async closeTerminal(handle: string): Promise<RuntimeTerminalClose> {
const pty = this.getLivePtyForHandle(handle)
this.claudeAgentTeams.removeTeamForLeaderHandle(handle)
if (pty) {
const ptyKilled = this.ptyController?.kill(pty.pty.ptyId) ?? false
return { handle, tabId: pty.pty.tabId ?? pty.record.tabId, ptyKilled }
if (!this.ptyController?.stopAndWait) {
throw new Error('terminal_verified_stop_unavailable')
}
if (!(await this.ptyController.stopAndWait(pty.pty.ptyId))) {
throw new Error('terminal_verified_stop_failed')
}
this.claudeAgentTeams.removeTeamForLeaderHandle(handle)
return { handle, tabId: pty.pty.tabId ?? pty.record.tabId, ptyKilled: true }
}
this.assertGraphReady()
const { leaf } = this.getLiveLeafForHandle(handle)
let ptyKilled = false
if (leaf.ptyId) {
ptyKilled = this.ptyController?.kill(leaf.ptyId) ?? false
if (!this.ptyController?.stopAndWait) {
throw new Error('terminal_verified_stop_unavailable')
}
ptyKilled = await this.ptyController.stopAndWait(leaf.ptyId)
if (!ptyKilled) {
throw new Error('terminal_verified_stop_failed')
}
}
this.claudeAgentTeams.removeTeamForLeaderHandle(handle)
// Why: killing the PTY in a multi-pane tab is sufficient — the renderer's
// PTY exit handler already calls PaneManager.closePane() for split layouts.
// Sending an additional IPC close would race with the exit handler and