diff --git a/src/main/ipc/pty/runtime/spawn-preflight-wsl-background-hold.test.ts b/src/main/ipc/pty/runtime/spawn-preflight-wsl-background-hold.test.ts index 738631fbfec..2e1be2667e9 100644 --- a/src/main/ipc/pty/runtime/spawn-preflight-wsl-background-hold.test.ts +++ b/src/main/ipc/pty/runtime/spawn-preflight-wsl-background-hold.test.ts @@ -4,6 +4,7 @@ import { wslDistroHoldMessage } from '../../../../shared/wsl-distro-stopped-hold import { prepareRuntimePtySpawn } from './spawn-preflight' import { createRuntimePtySpawnState, type RuntimePtySpawnArgs } from './spawn-state' import { makeRuntimePtySpawnTestDeps } from './spawn-preflight-test-deps-fixture' +import { isWslUserActFor } from '../../../wsl/wsl-user-act-scope' const mocks = vi.hoisted(() => ({ readWslDistroRunningState: vi.fn<() => Promise<'running' | 'stopped' | 'unverifiable'>>() @@ -16,8 +17,11 @@ vi.mock('../../../wsl-background-distro-gate', async (importOriginal) => ({ const WSL_CWD = '\\\\wsl.localhost\\Ubuntu\\home\\me\\repo' const hostPlatform = process.platform -async function prepare(args: Partial): Promise { - const ctx = createRuntimePtySpawnState(makeRuntimePtySpawnTestDeps(), { +async function prepare( + args: Partial, + deps = makeRuntimePtySpawnTestDeps() +): Promise { + const ctx = createRuntimePtySpawnState(deps, { cols: 120, rows: 40, cwd: WSL_CWD, @@ -61,3 +65,26 @@ describe("main's background start in a stopped WSL distro (E9)", () => { await expect(prepare({})).resolves.toBeUndefined() }) }) + +describe('host env prep after the startup hold (E9)', () => { + // Prep runs after the hold read; the distro may stop in between, so only a user act may wake it. + async function userActDuringPrep(args: Partial): Promise { + mocks.readWslDistroRunningState.mockResolvedValue('running') + const seen: boolean[] = [] + const deps = makeRuntimePtySpawnTestDeps() + deps.prepareCodexResumeHome = () => { + seen.push(isWslUserActFor('Ubuntu')) + return null + } + await prepare({ command: 'codex', ...args }, deps) + return seen + } + + it('gives a background start no user-act scope', async () => { + expect(await userActDuringPrep({})).toEqual([false]) + }) + + it("keeps the user's start a user act", async () => { + expect(await userActDuringPrep({ wslUserAct: true })).toEqual([true]) + }) +}) diff --git a/src/main/ipc/pty/runtime/spawn-preflight.ts b/src/main/ipc/pty/runtime/spawn-preflight.ts index 91c54d93b2a..4eb8b07cf56 100644 --- a/src/main/ipc/pty/runtime/spawn-preflight.ts +++ b/src/main/ipc/pty/runtime/spawn-preflight.ts @@ -152,6 +152,8 @@ export async function prepareRuntimePtySpawn( mintedSessionId: ctx.requestedSessionId === undefined, resumeOnSelectedAccount: false, emptySessionIdSkipsHostEnv: true, + // The distro may stop after the hold read: a background start's prep must not wake it (E9). + wslPassiveRestore: args.wslUserAct !== true, requestedAgentTeamsPath: ctx.requestedAgentTeamsPath, provider: ctx.provider, deps: ctx.deps diff --git a/src/main/providers/local-pty-spawn-environment-wsl-background.test.ts b/src/main/providers/local-pty-spawn-environment-wsl-background.test.ts new file mode 100644 index 00000000000..545db6839e3 --- /dev/null +++ b/src/main/providers/local-pty-spawn-environment-wsl-background.test.ts @@ -0,0 +1,75 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest' +import { buildLocalPtySpawnEnvironment } from './local-pty-spawn-environment' +import type { LocalPtyLaunchPlan } from './local-pty-launch-plan' +import type { PtySpawnOptions } from './pty-provider-contract' +import { runAsWslLaunchUserAct } from '../wsl/wsl-background-exec-gate' +import { isWslUserActFor } from '../wsl/wsl-user-act-scope' + +const plan: LocalPtyLaunchPlan = { + startupAgentRecognition: null, + defaultCwd: '', + cwd: '\\\\wsl.localhost\\Ubuntu\\home\\me\\repo', + wslInfo: null, + worktreeWslContext: undefined, + preferredWslContext: undefined, + launchWslContext: undefined, + shellPath: 'wsl.exe', + shellArgs: [], + effectiveCwd: '', + validationCwd: '', + startupCommandDeliveredInShellArgs: false, + windowsFallbackAttempts: [], + shellReadyLaunch: null, + getFallbackShellReadyConfig: undefined, + primaryPreLaunchEnv: {}, + isWslShell: true, + launchWslDistro: 'Ubuntu' +} +const hostPlatform = process.platform + +beforeEach(() => { + Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' }) +}) + +afterEach(() => { + Object.defineProperty(process, 'platform', { configurable: true, value: hostPlatform }) +}) + +// The daemon-off builder wraps its env prep the way provider/local-configure.ts does. +async function userActDuringDaemonOffPrep(spawn: Partial): Promise { + const seen: boolean[] = [] + await buildLocalPtySpawnEnvironment({ + id: 'pane', + spawn: { cols: 80, rows: 24, ...spawn }, + getOptions: () => ({ + buildSpawnEnv: (_id, baseEnv, ctx) => + runAsWslLaunchUserAct( + { + isWsl: ctx?.isWsl === true, + passiveRestore: ctx?.wslPassiveRestore === true, + distro: ctx?.wslDistro + }, + async () => { + seen.push(isWslUserActFor('Ubuntu')) + return baseEnv + } + ) + }), + plan + }) + return seen +} + +describe("daemon-off env prep for main's background start (STA-12870, E9)", () => { + it('gives a background start no user-act scope', async () => { + expect(await userActDuringDaemonOffPrep({ wslBackgroundStart: true })).toEqual([false]) + }) + + it('gives a passive restore no user-act scope', async () => { + expect(await userActDuringDaemonOffPrep({ wslPassiveRestore: true })).toEqual([false]) + }) + + it("keeps the user's start a user act", async () => { + expect(await userActDuringDaemonOffPrep({})).toEqual([true]) + }) +}) diff --git a/src/main/providers/local-pty-spawn-environment.ts b/src/main/providers/local-pty-spawn-environment.ts index efcf7d661bc..14b99f33e7e 100644 --- a/src/main/providers/local-pty-spawn-environment.ts +++ b/src/main/providers/local-pty-spawn-environment.ts @@ -47,7 +47,8 @@ export function buildLocalPtySpawnEnvironment(args: { shellPath: plan.shellPath, isWsl: plan.isWslShell, wslDistro: plan.launchWslDistro, - wslPassiveRestore: spawn.wslPassiveRestore === true + // Main's background starts get the same E9 prep as a passive restore (STA-12870). + wslPassiveRestore: spawn.wslPassiveRestore === true || spawn.wslBackgroundStart === true }) }