diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc index 4ae97e28d6f..52fdbe54808 100644 --- a/config/reliability-gates.jsonc +++ b/config/reliability-gates.jsonc @@ -712,7 +712,7 @@ "ssh", "runtime" ], - "coverageNotes": "Live macOS Electron tests prove exact local PTY disappearance after parked-tab close and detached-pgid descendant death after agent close. Windows 2022 Actions red/green runs prove production store.removeWorktree(id, true) retires an agent PTY and its reparented native lock-holder before deleting the worktree, plain terminal close preserves an intentionally detached child, and natural agent-root exit publishes PTY exit only after its detached Job child is absent. Deterministic tests cover explicit and spawn-recognized native Windows agent Job selection, daemon session identity propagation, fail-closed Job teardown and bridge errors, safe failure when Job ownership is unavailable, adopted-v24 destructive-shutdown refusal, provider-scoped recovered routing, coalesced authoritative shutdown inventory and cached fallback/daemon duplicate refusal, duplicate-exit suppression, sleeping legacy ownership, daemon WSL exclusion, daemon-wide shutdown, daemon and SSH routing, local/daemon pending-snapshot ownership across natural exit, stale-root descendant-signal suppression, graceful-to-immediate kill upgrades, duplicate-kill completion sharing, locale-stable bounded/fresh/coalesced process-table reads, deadline-safe successor scans, cycle-safe linear descendant traversal, target-only escalation indexing, 32-wide bulk teardown, source-scan timestamp identity, same-second PID ambiguity, ordinary runtime close ownership, unified-only hydration, split ownership, pane detach transfer, restart alias hydration, and late-hook suppression; live Linux, WSL, SSH, and remote-runtime process evidence remains pending.", + "coverageNotes": "Live macOS Electron tests prove exact local PTY disappearance after parked-tab close and detached-pgid descendant death after agent close. Windows 2022 Actions red/green runs prove production store.removeWorktree(id, true) retires an agent PTY and its reparented native lock-holder before deleting the worktree, plain terminal close preserves an intentionally detached child, and natural agent-root exit publishes PTY exit only after its detached Job child is absent. Deterministic tests cover explicit and spawn-recognized native Windows agent Job selection, daemon session identity propagation, fail-closed Job teardown and bridge errors, safe failure when Job ownership is unavailable, adopted-v24 unclassified-shutdown refusal with known-WSL compatibility, provider-scoped recovered routing, coalesced authoritative shutdown inventory and cached fallback/daemon duplicate refusal, duplicate-exit suppression, sleeping legacy ownership, daemon WSL exclusion, daemon-wide shutdown, daemon and SSH routing, local/daemon pending-snapshot ownership across natural exit, stale-root descendant-signal suppression, graceful-to-immediate kill upgrades, duplicate-kill completion sharing, locale-stable bounded/fresh/coalesced process-table reads, deadline-safe successor scans, cycle-safe linear descendant traversal, target-only escalation indexing, 32-wide bulk teardown, source-scan timestamp identity, same-second PID ambiguity, ordinary runtime close ownership, unified-only hydration, split ownership, pane detach transfer, restart alias hydration, and late-hook suppression; live Linux, WSL, SSH, and remote-runtime process evidence remains pending.", "motivatingLinks": [ "https://github.com/stablyai/orca/pull/8628", "https://github.com/stablyai/orca/pull/8706", @@ -805,7 +805,7 @@ { "file": "src/main/daemon/daemon-pty-adapter.test.ts", "assertions": [ - "an adopted Windows v24 daemon rejects deadline-bearing agent, plain, and WSL shutdown before connecting or sending kill while ordinary close remains compatible" + "an adopted Windows v24 daemon rejects unclassified ordinary, sleeping, and deadline-bearing shutdown before root-only kill while preserving ordinary and deadline close for attached known WSL sessions" ] }, { diff --git a/src/main/daemon/daemon-pty-adapter.test.ts b/src/main/daemon/daemon-pty-adapter.test.ts index 049e5fb120e..ba2a37ba3f2 100644 --- a/src/main/daemon/daemon-pty-adapter.test.ts +++ b/src/main/daemon/daemon-pty-adapter.test.ts @@ -532,18 +532,33 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => { }) describe('shutdown', () => { - it('blocks every adopted Windows v24 session kind from deadline teardown but keeps ordinary close', async () => { + it('blocks unclassified Windows v24 close while preserving known WSL close', async () => { const ensureConnectedSpy = vi .spyOn(DaemonClient.prototype, 'ensureConnected') .mockResolvedValue() - const requestSpy = vi.spyOn(DaemonClient.prototype, 'request').mockResolvedValue(undefined) + const ensureConnectedWithinSpy = vi + .spyOn(DaemonClient.prototype, 'ensureConnectedWithin') + .mockResolvedValue() + const requestSpy = vi + .spyOn(DaemonClient.prototype, 'request') + .mockImplementation(async (method) => + method === 'createOrAttach' + ? { + isNew: false, + pid: 123, + shellState: 'unsupported', + snapshot: null, + wslDistro: 'Ubuntu' + } + : undefined + ) const platform = Object.getOwnPropertyDescriptor(process, 'platform') Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) const legacy = new DaemonPtyAdapter({ socketPath, tokenPath, protocolVersion: 24 }) try { - // Why: v24 did not record whether a session was agent, plain, or WSL, - // so migration safety must reject deadline teardown for all three. + // Why: inventory-only sessions lack attached WSL metadata, while v24 + // could miss native agent identity; unclassified close must fail safe. for (const id of ['legacy-agent', 'legacy-plain', 'legacy-wsl']) { await expect( legacy.shutdown(id, { @@ -551,22 +566,55 @@ describe('DaemonPtyAdapter (IPtyProvider)', () => { deadlineMs: Date.now() + 5_000 }) ).rejects.toThrow('requires daemon protocol 25 or newer') + await expect(legacy.shutdown(id, { immediate: true })).rejects.toThrow( + 'restart Orca before closing adopted terminals' + ) + await expect(legacy.shutdown(id, { immediate: true, keepHistory: true })).rejects.toThrow( + 'restart Orca before closing adopted terminals' + ) } expect(ensureConnectedSpy).not.toHaveBeenCalled() + expect(ensureConnectedWithinSpy).not.toHaveBeenCalled() expect(requestSpy).not.toHaveBeenCalled() - await expect(legacy.shutdown('legacy-plain', { immediate: true })).resolves.toBeUndefined() - expect(requestSpy).toHaveBeenCalledWith( + await legacy.spawn({ + sessionId: 'known-legacy-wsl', + cols: 80, + rows: 24, + terminalWindowsWslDistro: 'Ubuntu' + }) + await expect( + legacy.shutdown('known-legacy-wsl', { immediate: true }) + ).resolves.toBeUndefined() + expect(requestSpy).toHaveBeenLastCalledWith( 'kill', - { sessionId: 'legacy-plain', immediate: true }, + { sessionId: 'known-legacy-wsl', immediate: true }, undefined ) + await legacy.spawn({ + sessionId: 'known-legacy-wsl-delete', + cols: 80, + rows: 24, + terminalWindowsWslDistro: 'Ubuntu' + }) + await expect( + legacy.shutdown('known-legacy-wsl-delete', { + immediate: true, + deadlineMs: Date.now() + 5_000 + }) + ).resolves.toBeUndefined() + expect(requestSpy).toHaveBeenLastCalledWith( + 'kill', + { sessionId: 'known-legacy-wsl-delete', immediate: true }, + expect.any(Number) + ) } finally { legacy.dispose() if (platform) { Object.defineProperty(process, 'platform', platform) } requestSpy.mockRestore() + ensureConnectedWithinSpy.mockRestore() ensureConnectedSpy.mockRestore() } }) diff --git a/src/main/daemon/daemon-pty-adapter.ts b/src/main/daemon/daemon-pty-adapter.ts index f5d9e34ae3f..a30e3787493 100644 --- a/src/main/daemon/daemon-pty-adapter.ts +++ b/src/main/daemon/daemon-pty-adapter.ts @@ -588,13 +588,13 @@ export class DaemonPtyAdapter implements IPtyProvider { ): Promise { if ( process.platform === 'win32' && - opts.deadlineMs !== undefined && - this.protocolVersion < WINDOWS_AGENT_JOB_PROTOCOL_VERSION + this.protocolVersion < WINDOWS_AGENT_JOB_PROTOCOL_VERSION && + !this.wslDistrosBySessionId.has(id) ) { - // Why: an adopted v24 daemon could not own agent descendants at spawn; - // destructive deletion must not mistake its root-only exit for a safe drain. + // Why: v24 could miss native agent identity and never Job-owned descendants; + // only an attached, known WSL session is safe for ordinary root close. throw new DaemonProtocolError( - `Windows destructive PTY shutdown requires daemon protocol ${WINDOWS_AGENT_JOB_PROTOCOL_VERSION} or newer` + `Windows PTY shutdown requires daemon protocol ${WINDOWS_AGENT_JOB_PROTOCOL_VERSION} or newer; restart Orca before closing adopted terminals` ) } // Why: shutdown can be the first lazy-client operation after restart; connect