diff --git a/config/main-layout-writer-baseline.txt b/config/main-layout-writer-baseline.txt index d37e9657755..825c26e2053 100644 --- a/config/main-layout-writer-baseline.txt +++ b/config/main-layout-writer-baseline.txt @@ -10,7 +10,7 @@ 1 src/main/ipc/renderer-shutdown-checkpoint.ts 1 src/main/ipc/runtime-environment-removal-cleanup.ts 1 src/main/ipc/runtime-environment-session-reconcile.ts -3 src/main/ipc/session.ts +2 src/main/ipc/session.ts 6 src/main/orca-profiles/profile-project-session-state.ts 4 src/main/orca-profiles/profile-project-source-removal.ts 2 src/main/orca-profiles/profile-project-state-file.ts diff --git a/config/renderer-layout-writer-baseline.txt b/config/renderer-layout-writer-baseline.txt index bb875d2d399..94fae07dfb2 100644 --- a/config/renderer-layout-writer-baseline.txt +++ b/config/renderer-layout-writer-baseline.txt @@ -102,7 +102,6 @@ 6 src/renderer/src/store/slices/terminal-orphan-helpers.ts 4 src/renderer/src/store/slices/terminal-tab-title-batch.ts 6 src/renderer/src/store/slices/terminals.ts -5 src/renderer/src/store/slices/worktrees/session/set-active-folder-workspace.ts 7 src/renderer/src/store/slices/worktrees/session/set-active-worktree.ts 15 src/renderer/src/store/slices/worktrees/session/worktree-identity-rename-state.ts 2 src/renderer/src/store/slices/worktrees/session/worktree-slice-lookups.ts diff --git a/src/main/ipc/preflight-runnable-local-cli.test.ts b/src/main/ipc/preflight-runnable-local-cli.test.ts index 94836e83d2f..90775ed94f1 100644 --- a/src/main/ipc/preflight-runnable-local-cli.test.ts +++ b/src/main/ipc/preflight-runnable-local-cli.test.ts @@ -265,6 +265,7 @@ describe.runIf(process.platform === 'win32')('Windows preflight batch shims', () } }) + // Include the probe, tree-cleanup deadline, and recovery probe on a busy Windows runner. it('recovers after a hung batch launcher and terminates its Node child', async () => { const first = path.join(root, 'hung') const second = path.join(root, 'working') @@ -273,7 +274,7 @@ describe.runIf(process.platform === 'win32')('Windows preflight batch shims', () const name = `${COMMAND}.CMD` await writeFile( path.join(first, 'hang.js'), - `require('node:fs').writeFileSync(require('node:path').join(__dirname, 'child.pid'), String(process.pid)); setTimeout(function () {}, 30000);` + `require('node:fs').writeFileSync(require('node:path').join(__dirname, 'child.pid'), String(process.pid)); setTimeout(function () {}, 60000);` ) await writeFile(path.join(first, name), `@echo off\r\n"${process.execPath}" "%~dp0hang.js"\r\n`) await writeFile( @@ -290,5 +291,5 @@ describe.runIf(process.platform === 'win32')('Windows preflight batch shims', () const childPid = Number(await readFile(path.join(first, 'child.pid'), 'utf8')) expect(Number.isSafeInteger(childPid) && childPid > 0).toBe(true) expect(() => process.kill(childPid, 0)).toThrow() - }, 15_000) + }, 30_000) }) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-message-options.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-message-options.ts index 95473c9df6e..838ab746388 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-message-options.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-message-options.ts @@ -19,6 +19,7 @@ export async function writeStructuredAgentSessionOption( const ctx = { sessionId, fence, + now: () => context.now(), adapter: context.deps.adapter, now: context.now, persistOptions: (options: Readonly>) => diff --git a/src/packages/process-host/src/process-tree-termination.test.ts b/src/packages/process-host/src/process-tree-termination.test.ts index 55933a8b3be..195fbd0ac56 100644 --- a/src/packages/process-host/src/process-tree-termination.test.ts +++ b/src/packages/process-host/src/process-tree-termination.test.ts @@ -105,6 +105,46 @@ describe('forceTerminateProcessTree', () => { }) }) + it('allows a slow Windows taskkill to verify tree termination', async () => { + await withWindows(async () => { + vi.useFakeTimers() + const child = mockProcess(1234) + const taskkill = mockProcess(5678) + spawnMock.mockReturnValue(taskkill) + let settled = false + const pending = forceTerminateProcessTree(child) + void pending.then(() => { + settled = true + }) + + await vi.advanceTimersByTimeAsync(3_000) + + expect(settled).toBe(false) + expect(taskkill.kill).not.toHaveBeenCalled() + expect(child.kill).not.toHaveBeenCalled() + taskkill.emit('close', 0) + await expect(pending).resolves.toBe(true) + }) + }) + + it('bounds a hung Windows taskkill and leaves tree termination unverified', async () => { + await withWindows(async () => { + vi.useFakeTimers() + const child = mockProcess(1234) + const taskkill = mockProcess(5678) + spawnMock.mockReturnValue(taskkill) + const pending = forceTerminateProcessTree(child) + + await vi.advanceTimersByTimeAsync(5_000) + + await expect(pending).resolves.toBe(false) + expect(taskkill.kill).toHaveBeenCalledOnce() + expect(child.kill).toHaveBeenCalledWith('SIGKILL') + taskkill.emit('close', 0) + expect(child.kill).toHaveBeenCalledOnce() + }) + }) + it.skipIf(process.platform === 'win32')( 'stops waiting when a POSIX process group cannot become quiescent', async () => { diff --git a/src/packages/process-host/src/process-tree-termination.ts b/src/packages/process-host/src/process-tree-termination.ts index 6be61924ad1..14fe89f1de9 100644 --- a/src/packages/process-host/src/process-tree-termination.ts +++ b/src/packages/process-host/src/process-tree-termination.ts @@ -4,6 +4,8 @@ import { windowsSystem32Binary } from './windows-system-binary' const PROBE_INTERVAL_MS = 25 const SUBPROCESS_TIMEOUT_MS = 2_000 +// Allow the same taskkill startup budget as the other Windows tree-cleanup paths. +const WINDOWS_TASKKILL_TIMEOUT_MS = 5_000 const MAX_PS_OUTPUT_BYTES = 8 * 1024 * 1024 /** @@ -131,7 +133,7 @@ function taskkillTree( const timer = setTimeout(() => { killer.kill() finish(true) - }, SUBPROCESS_TIMEOUT_MS) + }, WINDOWS_TASKKILL_TIMEOUT_MS) timer.unref?.() }) }