diff --git a/src/main/ipc/pty/host-env/assembly.ts b/src/main/ipc/pty/host-env/assembly.ts index 3c65796b250..dcf7f3fad29 100644 --- a/src/main/ipc/pty/host-env/assembly.ts +++ b/src/main/ipc/pty/host-env/assembly.ts @@ -160,7 +160,10 @@ export function buildPtyHostEnv( if (shouldPrepareOmpShadow) { const ompEnv = piTitlebarExtensionService.buildPtyEnv(id, preexistingOmpAgentDir, 'omp', { - materializeDefaultHome: explicitPiAgentKind === 'omp' + materializeDefaultHome: explicitPiAgentKind === 'omp', + ...(baseEnv.PI_CONFIG_DIR !== undefined && !opts.isWsl + ? { configDirName: baseEnv.PI_CONFIG_DIR } + : {}) }) Object.assign(baseEnv, ompEnv) exposePiManagedExtensionEnv(baseEnv, 'omp', ompEnv) diff --git a/src/main/ipc/pty/host-env/omp-launch-environment.test.ts b/src/main/ipc/pty/host-env/omp-launch-environment.test.ts new file mode 100644 index 00000000000..3700ccf7287 --- /dev/null +++ b/src/main/ipc/pty/host-env/omp-launch-environment.test.ts @@ -0,0 +1,77 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { inheritOmpLaunchEnvironment } from './omp-launch-environment' +import { resolveLoginShellEnvironment } from '../../../startup/login-shell-environment' + +vi.mock('../../../startup/login-shell-environment', () => ({ + resolveLoginShellEnvironment: vi.fn() +})) + +beforeEach(() => { + vi.stubGlobal('process', { ...process, platform: 'darwin', env: {} }) + vi.mocked(resolveLoginShellEnvironment).mockReset().mockResolvedValue({ + XDG_DATA_HOME: '/login/data', + XDG_STATE_HOME: '/login/state', + XDG_CACHE_HOME: '/login/cache', + PI_CONFIG_DIR: '.config/omp', + PI_CODING_AGENT_DIR: '/pi/override', + UNRELATED: 'ignore' + }) +}) +afterEach(() => vi.unstubAllGlobals()) + +describe('OMP launch directory environment', () => { + it.each([{ launchAgent: 'omp' }, { launchCommand: 'omp' }, {}])( + 'inherits login-shell category roots for %j', + async (options) => { + const env = {} + await inheritOmpLaunchEnvironment(env, options) + expect(env).toEqual({ + XDG_DATA_HOME: '/login/data', + XDG_STATE_HOME: '/login/state', + XDG_CACHE_HOME: '/login/cache', + PI_CONFIG_DIR: '.config/omp' + }) + } + ) + + it('uses the same login roots for merged local env and daemon pane deltas', async () => { + const local = { XDG_DATA_HOME: '/old/data', PI_CONFIG_DIR: '.old-omp' } + const daemon = {} + await inheritOmpLaunchEnvironment(local, { launchAgent: 'omp', explicitEnv: {} }) + await inheritOmpLaunchEnvironment(daemon, { launchAgent: 'omp' }) + expect(local).toEqual(daemon) + expect(local.XDG_DATA_HOME).toBe('/login/data') + const overridden = { XDG_DATA_HOME: '/old/data' } + await inheritOmpLaunchEnvironment(overridden, { + launchAgent: 'omp', + explicitEnv: { XDG_DATA_HOME: '/pane/data' } + }) + expect(overridden.XDG_DATA_HOME).toBe('/pane/data') + }) + + it('preserves explicit pane roots and intentionally empty values', async () => { + const env = { XDG_DATA_HOME: '/pane/data', XDG_STATE_HOME: '' } + await inheritOmpLaunchEnvironment(env, { launchAgent: 'omp' }) + expect(env.XDG_DATA_HOME).toBe('/pane/data') + expect(env.XDG_STATE_HOME).toBe('') + }) + + it.each([ + { isWsl: true, launchAgent: 'omp' }, + { launchAgent: 'pi' }, + { launchAgent: 'claude' }, + { launchCommand: 'pi' }, + { launchCommand: 'npm test' } + ])('does not probe a different execution environment for %j', async (options) => { + const env = {} + await inheritOmpLaunchEnvironment(env, options) + expect(env).toEqual({}) + expect(resolveLoginShellEnvironment).not.toHaveBeenCalled() + }) + + it('does not import POSIX roots into native Windows', async () => { + vi.stubGlobal('process', { ...process, platform: 'win32' }) + await inheritOmpLaunchEnvironment({}, { launchAgent: 'omp' }) + expect(resolveLoginShellEnvironment).not.toHaveBeenCalled() + }) +}) diff --git a/src/main/ipc/pty/host-env/omp-launch-environment.ts b/src/main/ipc/pty/host-env/omp-launch-environment.ts new file mode 100644 index 00000000000..55408e94c39 --- /dev/null +++ b/src/main/ipc/pty/host-env/omp-launch-environment.ts @@ -0,0 +1,38 @@ +import { detectExplicitPiAgentKindFromCommand } from '../../../../shared/pi-agent-kind' +import { resolveSetupAgentSequenceLaunchCommand } from '../../../../shared/setup-agent-sequencing' +import { resolveLoginShellEnvironment } from '../../../startup/login-shell-environment' + +const OMP_DIRECTORY_ENV_KEYS = [ + 'XDG_CONFIG_HOME', + 'XDG_DATA_HOME', + 'XDG_STATE_HOME', + 'XDG_CACHE_HOME', + 'PI_CONFIG_DIR' +] as const + +export async function inheritOmpLaunchEnvironment( + env: Record, + options: { + isWsl?: boolean + launchAgent?: string + launchCommand?: string + explicitEnv?: Record + } +): Promise { + if (options.isWsl || process.platform === 'win32') { + return + } + const command = resolveSetupAgentSequenceLaunchCommand(env, options.launchCommand) + const agent = options.launchAgent ?? detectExplicitPiAgentKindFromCommand(command) + if (agent !== 'omp' && (options.launchAgent !== undefined || command?.trim())) { + return + } + const shellEnv = await resolveLoginShellEnvironment() + for (const key of OMP_DIRECTORY_ENV_KEYS) { + // Explicit pane values, including empty values, take precedence over the login shell. + const value = (options.explicitEnv ?? env)[key] ?? shellEnv[key] ?? process.env[key] + if (value !== undefined) { + env[key] = value + } + } +} diff --git a/src/main/ipc/pty/ipc/spawn-env-codex.ts b/src/main/ipc/pty/ipc/spawn-env-codex.ts index b01dfa997e2..e17e21cf5cd 100644 --- a/src/main/ipc/pty/ipc/spawn-env-codex.ts +++ b/src/main/ipc/pty/ipc/spawn-env-codex.ts @@ -1,3 +1,4 @@ +import { inheritOmpLaunchEnvironment } from '../host-env/omp-launch-environment' import { getAppEnvironment } from '../../../../shared/app-environment' import { isTuiAgent } from '../../../../shared/tui-agent-config' import { isAgentStatusHooksEnabled } from '../../../agent-hooks/managed-agent-hook-controls' @@ -133,6 +134,11 @@ export async function assemblePtyIpcSpawnCodexEnv(ctx: PtyIpcSpawnState): Promis // Why: clone before mutating so injections don't leak back into args.env (renderer may reuse it). ctx.env = { ...ctx.baseEnv } try { + await inheritOmpLaunchEnvironment(ctx.env, { + isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.effectiveShellOverride, ctx.cwd), + launchAgent: args.launchAgent, + launchCommand: ctx.launchCommand + }) buildPtyHostEnv(sessionIdForEnv, ctx.env, { isPackaged: getAppEnvironment().isPackaged(), resourcesPath: process.resourcesPath, diff --git a/src/main/ipc/pty/provider/local-configure.ts b/src/main/ipc/pty/provider/local-configure.ts index 68f3b7c0723..f0f1bc78f67 100644 --- a/src/main/ipc/pty/provider/local-configure.ts +++ b/src/main/ipc/pty/provider/local-configure.ts @@ -1,3 +1,4 @@ +import { inheritOmpLaunchEnvironment } from '../host-env/omp-launch-environment' import { getAppEnvironment } from '../../../../shared/app-environment' import type { OrcaRuntimeService } from '../../../runtime/orca-runtime' import type { GlobalSettings } from '../../../../shared/global-settings-types' @@ -55,6 +56,12 @@ export function configureLocalPtyProvider(args: { ) const skipCodexHomeEnv = ctx?.isWsl === true && !selectedCodexHomePath const ptySettings = getSettings?.() + await inheritOmpLaunchEnvironment(baseEnv, { + explicitEnv: ctx?.explicitEnv, + isWsl: ctx?.isWsl, + launchAgent: ctx?.launchAgent, + launchCommand: ctx?.command + }) const env = buildPtyHostEnv(id, baseEnv, { isPackaged: getAppEnvironment().isPackaged(), resourcesPath: process.resourcesPath, diff --git a/src/main/ipc/pty/runtime/spawn-preflight.ts b/src/main/ipc/pty/runtime/spawn-preflight.ts index a37ede4b3e8..1209730ed6d 100644 --- a/src/main/ipc/pty/runtime/spawn-preflight.ts +++ b/src/main/ipc/pty/runtime/spawn-preflight.ts @@ -1,3 +1,4 @@ +import { inheritOmpLaunchEnvironment } from '../host-env/omp-launch-environment' import { getAppEnvironment } from '../../../../shared/app-environment' import type { PtySpawnResult } from '../../../providers/types' import { LocalPtyProvider } from '../../../providers/local-pty-provider' @@ -258,6 +259,12 @@ export async function prepareRuntimePtySpawn( throw new Error('Invalid PTY session id') } try { + ctx.env ??= {} + await inheritOmpLaunchEnvironment(ctx.env, { + isWsl: shouldSkipCodexHomeEnvForWindowsShell(ctx.daemonShellOverride, ctx.cwd), + launchAgent: args.launchAgent, + launchCommand: ctx.launchCommand + }) ctx.env = buildPtyHostEnv(ctx.sessionId, ctx.env ?? {}, { isPackaged: getAppEnvironment().isPackaged(), resourcesPath: process.resourcesPath, diff --git a/src/main/pi/titlebar-extension-service.test.ts b/src/main/pi/titlebar-extension-service.test.ts index 69884d6591b..435fb5ac91f 100644 --- a/src/main/pi/titlebar-extension-service.test.ts +++ b/src/main/pi/titlebar-extension-service.test.ts @@ -98,6 +98,59 @@ describe('PiTitlebarExtensionService', () => { rmSync(join(userDataDir, 'omp-managed-status-extension'), { recursive: true, force: true }) }) + it.each([ + ['pi', '.pi', 'ORCA_PI_SOURCE_AGENT_DIR'], + ['omp', '.omp', 'ORCA_OMP_SOURCE_AGENT_DIR'], + ['prime-agent', '.prime', 'ORCA_PRIME_AGENT_SOURCE_AGENT_DIR'] + ] as const)('does not reinterpret XDG data as %s configuration', (kind, root, sourceKey) => { + const fakeHome = mkdtempSync(join(tmpdir(), 'orca-agent-xdg-')) + const dataHome = join(fakeHome, 'data') + const dataAgentDir = join(dataHome, root.slice(1), 'agent') + mkdirSync(dataAgentDir, { recursive: true }) + homedirOverride.current = fakeHome + vi.stubEnv('XDG_DATA_HOME', dataHome) + try { + const env = new PiTitlebarExtensionService().buildPtyEnv('pty-xdg', undefined, kind) + expect(env[sourceKey]).toBe(join(fakeHome, root, 'agent')) + expect(readdirSync(dataAgentDir)).toEqual([]) + expect(env.PI_CODING_AGENT_DIR).toBeUndefined() + } finally { + vi.unstubAllEnvs() + homedirOverride.current = '' + rmSync(fakeHome, { recursive: true, force: true }) + } + }) + + it('materializes OMP extensions under the launch config root without overriding data routing', () => { + const fakeHome = mkdtempSync(join(tmpdir(), 'orca-omp-config-')) + homedirOverride.current = fakeHome + try { + const service = new PiTitlebarExtensionService() + const environment = { PI_CONFIG_DIR: '.config/omp', XDG_DATA_HOME: join(fakeHome, 'data') } + const env = service.buildPtyEnv('pty-config', undefined, 'omp', { + configDirName: environment.PI_CONFIG_DIR + }) + const agentDir = join(fakeHome, '.config', 'omp', 'agent') + expect(env.ORCA_OMP_SOURCE_AGENT_DIR).toBe(agentDir) + expect(existsSync(join(agentDir, 'extensions', 'orca-agent-status.ts'))).toBe(true) + expect(env.PI_CODING_AGENT_DIR).toBeUndefined() + expect(existsSync(join(fakeHome, '.omp'))).toBe(false) + expect(existsSync(environment.XDG_DATA_HOME)).toBe(false) + expect( + service.buildPtyEnv('pty-explicit', piHome, 'omp', { + configDirName: environment.PI_CONFIG_DIR + }).ORCA_OMP_SOURCE_AGENT_DIR + ).toBe(piHome) + expect( + service.buildPtyEnv('pty-pi', undefined, 'pi', { configDirName: environment.PI_CONFIG_DIR }) + .ORCA_PI_SOURCE_AGENT_DIR + ).toBe(join(fakeHome, '.pi', 'agent')) + } finally { + homedirOverride.current = '' + rmSync(fakeHome, { recursive: true, force: true }) + } + }) + function expectPiHomeIntact(): void { expect(readFileSync(join(piHome, 'auth.json'), 'utf-8')).toBe('secret token') expect(readFileSync(join(piHome, 'skills', 'my-skill', 'SKILL.md'), 'utf-8')).toBe( diff --git a/src/main/pi/titlebar-extension-service.ts b/src/main/pi/titlebar-extension-service.ts index 8a093096f4e..3ab0a253ad4 100644 --- a/src/main/pi/titlebar-extension-service.ts +++ b/src/main/pi/titlebar-extension-service.ts @@ -58,8 +58,9 @@ const AGENT_HOME_DIR_NAME: Record = { 'prime-agent': '.prime' } -function getDefaultPiAgentDir(kind: PiAgentKind): string { - return join(homedir(), AGENT_HOME_DIR_NAME[kind], PI_AGENT_SUBDIR) +function getDefaultPiAgentDir(kind: PiAgentKind, configDirName: string | undefined): string { + const root = kind === 'omp' ? configDirName || AGENT_HOME_DIR_NAME.omp : AGENT_HOME_DIR_NAME[kind] + return join(homedir(), root, PI_AGENT_SUBDIR) } function toSafeOverlayDirName(ptyId: string): string { @@ -177,9 +178,11 @@ export class PiTitlebarExtensionService { ptyId: string, existingAgentDir: string | undefined, kind: PiAgentKind, - options?: { materializeDefaultHome?: boolean } + options?: { materializeDefaultHome?: boolean; configDirName?: string } ): Record { - const sourceAgentDir = existingAgentDir || getDefaultPiAgentDir(kind) + const sourceAgentDir = + existingAgentDir || + getDefaultPiAgentDir(kind, options?.configDirName ?? process.env.PI_CONFIG_DIR) if (kind !== 'prime-agent') { try { this.safeRemoveOverlay(this.getPtyOverlayDir(ptyId, kind), kind) diff --git a/src/main/providers/local-pty-provider-spawn-env.test.ts b/src/main/providers/local-pty-provider-spawn-env.test.ts index 8dc45d40aa8..daf6286b56d 100644 --- a/src/main/providers/local-pty-provider-spawn-env.test.ts +++ b/src/main/providers/local-pty-provider-spawn-env.test.ts @@ -179,6 +179,18 @@ describe('LocalPtyProvider', () => { ) }) + it('passes explicit pane environment separately from inherited process values', async () => { + const buildSpawnEnv = vi.fn((_id: string, env: Record) => env) + provider.configure({ buildSpawnEnv }) + const env = { XDG_DATA_HOME: '/pane/data' } + await provider.spawn({ cols: 80, rows: 24, env }) + expect(buildSpawnEnv).toHaveBeenCalledWith( + expect.any(String), + expect.any(Object), + expect.objectContaining({ explicitEnv: env }) + ) + }) + it('invokes buildSpawnEnv callback to customize environment', async () => { const buildSpawnEnv = vi.fn((_id: string, env: Record) => { env.CUSTOM_VAR = 'custom-value' diff --git a/src/main/providers/local-pty-provider-types.ts b/src/main/providers/local-pty-provider-types.ts index 0bebff54237..ebe90bbb049 100644 --- a/src/main/providers/local-pty-provider-types.ts +++ b/src/main/providers/local-pty-provider-types.ts @@ -7,6 +7,7 @@ export type LocalPtyProviderOptions = { id: string, baseEnv: Record, ctx?: { + explicitEnv?: PtySpawnOptions['env'] command?: string launchAgent?: PtySpawnOptions['launchAgent'] codexHomePathOverride?: PtySpawnOptions['codexHomePathOverride'] diff --git a/src/main/providers/local-pty-spawn-environment.ts b/src/main/providers/local-pty-spawn-environment.ts index 39b56c4d99f..2413dd12b10 100644 --- a/src/main/providers/local-pty-spawn-environment.ts +++ b/src/main/providers/local-pty-spawn-environment.ts @@ -61,6 +61,7 @@ export function buildLocalPtySpawnEnvironment(args: { return awaitCancelableLocalPtySpawn( id, getOptions().buildSpawnEnv!(id, spawnEnv, { + explicitEnv: spawn.env ?? {}, command: spawn.command, launchAgent: spawn.launchAgent, codexHomePathOverride: spawn.codexHomePathOverride, diff --git a/tests/e2e/omp-launch-environment.spec.ts b/tests/e2e/omp-launch-environment.spec.ts new file mode 100644 index 00000000000..63f352a8391 --- /dev/null +++ b/tests/e2e/omp-launch-environment.spec.ts @@ -0,0 +1,231 @@ +import { chmod, mkdir, readFile, readdir, writeFile } from 'node:fs/promises' +import { dirname, delimiter, join } from 'node:path' +import { test as base, expect } from './helpers/orca-app' +import { buildShellCommandFromArgv } from '../../src/shared/tui-agent-startup-shell' +import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store' +import { + execInTerminal, + waitForActivePanePtyId, + waitForActiveTerminalManager +} from './helpers/terminal' + +const test = base.extend({ + launchEnv: async ({ seedTestRepo }, run, testInfo) => { + void seedTestRepo + const shell = testInfo.outputPath('login-shell') + const profileDir = testInfo.outputPath('profile') + await mkdir(profileDir, { recursive: true }) + await writeFile( + join(profileDir, '.zprofile'), + ` +export PI_CONFIG_DIR="\${PI_CONFIG_DIR:-.omp-profile-proof}" +export XDG_DATA_HOME="\${XDG_DATA_HOME:-$HOME/xdg-data}" +export XDG_STATE_HOME="\${XDG_STATE_HOME:-$HOME/xdg-state}" +export XDG_CACHE_HOME="\${XDG_CACHE_HOME:-$HOME/xdg-cache}" +mkdir -p "$HOME/$PI_CONFIG_DIR/agent" "$XDG_DATA_HOME/omp" "$XDG_STATE_HOME/omp" "$XDG_CACHE_HOME/omp" +` + ) + await writeFile( + shell, + `#!/bin/sh +case "$ORCA_E2E_HOME_DIR" in + */orca-e2e-userdata-*/home) ;; + *) echo 'Refusing profile proof outside isolated E2E home' >&2; exit 73 ;; +esac +export HOME="$ORCA_E2E_HOME_DIR" +export ZDOTDIR=${buildShellCommandFromArgv([profileDir], 'posix')} +exec /bin/zsh "$@" +` + ) + await chmod(shell, 0o700) + await run({ + PATH: [dirname(process.env.ORCA_OMP_PROOF_BINARY ?? '/usr/bin/omp'), process.env.PATH] + .filter(Boolean) + .join(delimiter), + SHELL: shell, + XDG_DATA_HOME: undefined, + XDG_STATE_HOME: undefined, + XDG_CACHE_HOME: undefined, + XDG_CONFIG_HOME: undefined, + PI_CONFIG_DIR: undefined + }) + } +}) + +test('OMP launched by Orca uses login-profile data and config roots', async ({ + orcaPage, + electronApp +}, testInfo) => { + test.skip( + !process.env.ORCA_OMP_PROOF_BINARY || process.platform !== 'darwin', + 'Opt-in macOS OMP runtime proof' + ) + await waitForSessionReady(orcaPage) + const worktreeId = await waitForActiveWorktree(orcaPage) + await ensureTerminalVisible(orcaPage) + await waitForActiveTerminalManager(orcaPage) + const ptyId = await waitForActivePanePtyId(orcaPage) + const home = await electronApp.evaluate(({ app }) => app.getPath('home')) + expect(await electronApp.evaluate(() => process.env.XDG_DATA_HOME)).toBeUndefined() + const result = testInfo.outputPath('omp-paths.json') + const probe = testInfo.outputPath('path-probe.ts') + await writeFile( + probe, + `import { writeFileSync } from 'node:fs' +export default function (api) { + api.on('session_start', (_event, ctx) => { + writeFileSync(process.env.ORCA_PATH_PROBE_OUTPUT || ${JSON.stringify(result)}, JSON.stringify({ + data: process.env.XDG_DATA_HOME, config: process.env.PI_CONFIG_DIR, + source: process.env.ORCA_OMP_SOURCE_AGENT_DIR, + status: process.env.ORCA_OMP_STATUS_EXTENSION, + session: ctx.sessionManager.getSessionFile(), pid: process.pid + })) + }) +}` + ) + await execInTerminal( + orcaPage, + ptyId, + buildShellCommandFromArgv(['omp', '--no-extensions', '--extension', probe], 'posix') + ) + await expect + .poll( + async () => { + try { + return JSON.parse(await readFile(result, 'utf8')) + } catch { + return null + } + }, + { timeout: 45_000 } + ) + .toEqual( + expect.objectContaining({ + data: join(home, 'xdg-data'), + config: '.omp-profile-proof', + source: join(home, '.omp-profile-proof', 'agent'), + status: expect.stringContaining(join('.omp-profile-proof', 'agent', 'extensions')), + session: expect.stringContaining(join('xdg-data', 'omp', 'sessions')) + }) + ) + await expect(orcaPage.locator('.xterm-screen').first()).toBeVisible() + await orcaPage.screenshot({ path: testInfo.outputPath('omp-profile-root.png') }) + expect(await readdir(join(home, 'xdg-data', 'omp'))).toContain('agent.db') + const overrideData = join(home, 'pane-data') + await mkdir(join(overrideData, 'omp'), { recursive: true }) + const overrideResult = testInfo.outputPath('omp-pane-paths.json') + const overridePty = await orcaPage.evaluate( + async ({ command, worktreeId, home, overrideData, overrideResult }) => { + const pane = await window.api.pty.spawn({ + cols: 100, + rows: 30, + cwd: home, + worktreeId, + initiallyHidden: true, + launchAgent: 'omp', + command, + env: { + XDG_DATA_HOME: overrideData, + PI_CONFIG_DIR: '.omp-pane-config', + ORCA_PATH_PROBE_OUTPUT: overrideResult + } + }) + return pane.id + }, + { + command: buildShellCommandFromArgv(['omp', '--no-extensions', '--extension', probe], 'posix'), + worktreeId, + home, + overrideData, + overrideResult + } + ) + try { + await expect + .poll( + async () => { + try { + return JSON.parse(await readFile(overrideResult, 'utf8')) + } catch { + return null + } + }, + { timeout: 45_000 } + ) + .toEqual( + expect.objectContaining({ + data: overrideData, + config: '.omp-pane-config', + source: join(home, '.omp-pane-config', 'agent'), + session: expect.stringContaining(join('pane-data', 'omp', 'sessions')) + }) + ) + expect(await readdir(join(overrideData, 'omp'))).toContain('agent.db') + } finally { + await orcaPage.evaluate((id) => window.api.pty.kill(id), overridePty) + } + const baselineResult = testInfo.outputPath('omp-login-baseline.json') + const baselineCommand = buildShellCommandFromArgv( + [ + 'env', + ...[ + 'XDG_DATA_HOME', + 'XDG_STATE_HOME', + 'XDG_CACHE_HOME', + 'PI_CONFIG_DIR', + 'ORCA_OMP_SOURCE_AGENT_DIR', + 'ORCA_OMP_STATUS_EXTENSION', + 'ORCA_PI_STATUS_OWNED', + 'ZDOTDIR', + 'ORCA_ORIG_ZDOTDIR' + ].flatMap((key) => ['-u', key]), + `ORCA_PATH_PROBE_OUTPUT=${baselineResult}`, + `HOME=${home}`, + `ZDOTDIR=${testInfo.outputPath('profile')}`, + '/bin/zsh', + '-ilc', + buildShellCommandFromArgv( + [process.env.ORCA_OMP_PROOF_BINARY ?? '', '--no-extensions', '--extension', probe], + 'posix' + ) + ], + 'posix' + ) + const baselinePty = await orcaPage.evaluate( + async ({ command, worktreeId, home }) => { + return ( + await window.api.pty.spawn({ + cols: 100, + rows: 30, + cwd: home, + worktreeId, + initiallyHidden: true, + command + }) + ).id + }, + { command: baselineCommand, worktreeId, home } + ) + try { + await expect + .poll( + async () => { + try { + return JSON.parse(await readFile(baselineResult, 'utf8')) + } catch { + return null + } + }, + { timeout: 45_000 } + ) + .toEqual( + expect.objectContaining({ + data: join(home, 'xdg-data'), + config: '.omp-profile-proof', + session: expect.stringContaining(join('xdg-data', 'omp', 'sessions')) + }) + ) + } finally { + await orcaPage.evaluate((id) => window.api.pty.kill(id), baselinePty) + } +})