mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
PR 9501 shipped real-home routing for the host system default, and the env override that could turn it back off was never a shipped control. The managed-account half of the shared runtime mirror has been unreachable since: every host account routes to its own self-contained CODEX_HOME before that code runs. Delete the flag module and its env plumbing plus the managed branch of syncForCurrentSelection and the six helpers only it called. The three lanes that still use the shared mirror -- Windows, a custom CODEX_HOME, and a hook-lane gate that reports unusable -- are untouched, as are every legacy migration and the WSL read-back helpers.
282 lines
10 KiB
TypeScript
282 lines
10 KiB
TypeScript
import { spawn, type ChildProcess } from 'node:child_process'
|
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
import { _electron as electron, type ElectronApplication } from '@stablyai/playwright-test'
|
|
import { test, expect, forwardElectronProcessLogs } from './helpers/orca-app'
|
|
import { TEST_REPO_PATH_FILE } from './global-setup'
|
|
import { getE2ECompletedOnboardingProfile } from './helpers/e2e-completed-onboarding-profile'
|
|
import { getOrcaElectronLaunchArgs } from './helpers/electron-launch-args'
|
|
import { cleanupE2EDaemons, closeElectronAppForE2E } from './helpers/electron-process-shutdown'
|
|
import {
|
|
assertElectronResolvedIsolatedHome,
|
|
createElectronHomeIsolation,
|
|
type ElectronHomeIsolation
|
|
} from './helpers/electron-home-isolation'
|
|
import {
|
|
discoverActivePtyId,
|
|
execInTerminal,
|
|
getTerminalContent,
|
|
waitForActiveTerminalManager,
|
|
waitForPaneCount,
|
|
waitForTerminalOutput
|
|
} from './helpers/terminal'
|
|
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
|
import { RuntimeClient } from '../../src/cli/runtime/client'
|
|
import { RuntimeClientError } from '../../src/cli/runtime/types'
|
|
import type {
|
|
RuntimeStatus,
|
|
RuntimeTerminalCreate,
|
|
RuntimeTerminalRead
|
|
} from '../../src/shared/runtime-types'
|
|
import { PROTOCOL_VERSION } from '../../src/main/daemon/types'
|
|
import { parsePaneKey } from '../../src/shared/stable-pane-id'
|
|
import { DEFAULT_LOCAL_ORCA_PROFILE_ID } from '../../src/shared/orca-profiles'
|
|
|
|
const electronPackageDir = path.join(process.cwd(), 'node_modules', 'electron')
|
|
const electronPath = path.join(
|
|
electronPackageDir,
|
|
'dist',
|
|
readFileSync(path.join(electronPackageDir, 'path.txt'), 'utf8').trim()
|
|
)
|
|
|
|
function createHeadlessLaunchIsolation(userDataDir: string): ElectronHomeIsolation {
|
|
const { ELECTRON_RUN_AS_NODE: _unused, ...cleanEnv } = process.env
|
|
void _unused
|
|
return createElectronHomeIsolation({
|
|
inheritedEnv: cleanEnv,
|
|
launchEnv: {
|
|
NODE_ENV: 'development',
|
|
ORCA_E2E_HEADLESS: '1',
|
|
// Why: production builds always use the lock; this opt-in makes the dev
|
|
// E2E bundle exercise the same second-instance ownership path.
|
|
ORCA_E2E_ENFORCE_SINGLE_INSTANCE_LOCK: '1'
|
|
},
|
|
extraEnv: {},
|
|
userDataDir
|
|
})
|
|
}
|
|
|
|
function readDaemonPid(userDataDir: string): number {
|
|
const raw = readFileSync(
|
|
path.join(userDataDir, 'daemon', `daemon-v${PROTOCOL_VERSION}.pid`),
|
|
'utf8'
|
|
)
|
|
const parsed = JSON.parse(raw) as { pid?: unknown }
|
|
if (typeof parsed.pid !== 'number') {
|
|
throw new Error(`Daemon pid file did not contain a numeric pid: ${raw}`)
|
|
}
|
|
return parsed.pid
|
|
}
|
|
|
|
function readPersistedPromotionBinding(
|
|
userDataDir: string,
|
|
worktreeId: string,
|
|
tabId: string,
|
|
leafId: string
|
|
): { tabId: string; leafId: string; ptyId: string } | null {
|
|
try {
|
|
const persisted = JSON.parse(
|
|
readFileSync(
|
|
path.join(userDataDir, 'profiles', DEFAULT_LOCAL_ORCA_PROFILE_ID, 'orca-data.json'),
|
|
'utf8'
|
|
)
|
|
) as {
|
|
workspaceSession?: {
|
|
tabsByWorktree?: Record<string, { id?: string; ptyId?: string | null }[]>
|
|
terminalLayoutsByTabId?: Record<string, { ptyIdsByLeafId?: Record<string, string | null> }>
|
|
}
|
|
}
|
|
const tab = persisted.workspaceSession?.tabsByWorktree?.[worktreeId]?.find(
|
|
(candidate) => candidate.id === tabId
|
|
)
|
|
const ptyId =
|
|
persisted.workspaceSession?.terminalLayoutsByTabId?.[tabId]?.ptyIdsByLeafId?.[leafId]
|
|
return tab && ptyId ? { tabId, leafId, ptyId } : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
async function waitForProcessExit(child: ChildProcess, timeoutMs: number): Promise<boolean> {
|
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
return true
|
|
}
|
|
return await new Promise((resolve) => {
|
|
const onExit = (): void => {
|
|
clearTimeout(timeout)
|
|
resolve(true)
|
|
}
|
|
const timeout = setTimeout(() => {
|
|
child.off('exit', onExit)
|
|
resolve(false)
|
|
}, timeoutMs)
|
|
child.once('exit', onExit)
|
|
})
|
|
}
|
|
|
|
test.describe.configure({ mode: 'serial' })
|
|
|
|
test('promotes the headless owner without replacing its daemon terminal', async (// oxlint-disable-next-line no-empty-pattern -- This lifecycle test owns both launches and intentionally opts out of the default app fixture.
|
|
{}, testInfo) => {
|
|
const repoPath = readFileSync(TEST_REPO_PATH_FILE, 'utf8').trim()
|
|
if (!repoPath || !existsSync(repoPath)) {
|
|
test.skip(true, 'Global setup did not produce a seeded test repo')
|
|
return
|
|
}
|
|
|
|
const mainPath = path.join(process.cwd(), 'out', 'main', 'index.js')
|
|
const userDataDir = mkdtempSync(path.join(os.tmpdir(), 'orca-e2e-serve-promotion-'))
|
|
const homeIsolation = createHeadlessLaunchIsolation(userDataDir)
|
|
const env = homeIsolation.env
|
|
let serveApp: ElectronApplication | null = null
|
|
let activatingProcess: ChildProcess | null = null
|
|
|
|
writeFileSync(
|
|
path.join(userDataDir, 'orca-data.json'),
|
|
`${JSON.stringify(getE2ECompletedOnboardingProfile(), null, 2)}\n`
|
|
)
|
|
|
|
try {
|
|
serveApp = await electron.launch({
|
|
args: [...getOrcaElectronLaunchArgs(mainPath, false), '--serve', '--serve-no-pairing'],
|
|
env
|
|
})
|
|
const resolvedHome = await serveApp.evaluate(({ app }) => app.getPath('home'))
|
|
assertElectronResolvedIsolatedHome(resolvedHome, homeIsolation)
|
|
// Why: this spec bypasses the app fixture, so opt into its gated Electron log capture for CI failures.
|
|
forwardElectronProcessLogs(serveApp, testInfo)
|
|
const ownerPid = serveApp.process().pid
|
|
const client = new RuntimeClient(userDataDir, 5_000)
|
|
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
try {
|
|
return (await client.getCliStatus()).result.app.desktopWindowStatus
|
|
} catch (error) {
|
|
if (error instanceof RuntimeClientError && error.code === 'runtime_unavailable') {
|
|
return 'starting'
|
|
}
|
|
throw error
|
|
}
|
|
},
|
|
{
|
|
timeout: 60_000,
|
|
message: 'headless serve never became safely openable'
|
|
}
|
|
)
|
|
.toBe('openable')
|
|
|
|
const beforeStatus = await client.call<RuntimeStatus>('status.get')
|
|
const daemonPidBefore = readDaemonPid(userDataDir)
|
|
await client.call('repo.add', { path: repoPath, kind: 'git' })
|
|
const created = await client.call<{ terminal: RuntimeTerminalCreate }>('terminal.create', {
|
|
worktree: `path:${repoPath}`,
|
|
title: 'Serve promotion continuity'
|
|
})
|
|
const terminal = created.result.terminal
|
|
const originalPtyId = terminal.ptyId
|
|
const originalTabId = terminal.tabId
|
|
const paneIdentity = terminal.paneKey ? parsePaneKey(terminal.paneKey) : null
|
|
if (!originalPtyId || !originalTabId || !paneIdentity) {
|
|
throw new Error('Headless terminal did not expose its durable pane and daemon PTY identity')
|
|
}
|
|
|
|
const beforeMarker = `SERVE_PROMOTION_BEFORE_${Date.now()}`
|
|
await client.call('terminal.send', {
|
|
terminal: terminal.handle,
|
|
text: `echo ${beforeMarker}`,
|
|
enter: true
|
|
})
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const response = await client.call<{ terminal: RuntimeTerminalRead }>('terminal.read', {
|
|
terminal: terminal.handle,
|
|
limit: 200
|
|
})
|
|
return response.result.terminal.tail.join('\n')
|
|
},
|
|
{ timeout: 15_000 }
|
|
)
|
|
.toContain(beforeMarker)
|
|
|
|
const forwardAppLogs = process.env.ORCA_E2E_FORWARD_APP_LOGS === '1'
|
|
activatingProcess = spawn(electronPath, getOrcaElectronLaunchArgs(mainPath, false), {
|
|
env,
|
|
stdio: forwardAppLogs ? 'pipe' : 'ignore'
|
|
})
|
|
activatingProcess.on('error', (error) => {
|
|
console.error('[e2e] activating process failed to spawn:', error)
|
|
})
|
|
if (forwardAppLogs) {
|
|
const prefix = '[e2e] activating process'
|
|
activatingProcess.stdout?.on('data', (chunk: Buffer) => {
|
|
console.log(`${prefix} stdout: ${chunk.toString().trimEnd()}`)
|
|
})
|
|
activatingProcess.stderr?.on('data', (chunk: Buffer) => {
|
|
console.error(`${prefix} stderr: ${chunk.toString().trimEnd()}`)
|
|
})
|
|
activatingProcess.on('exit', (code, signal) => {
|
|
console.log(`${prefix} exit: code=${code ?? 'null'} signal=${signal ?? 'null'}`)
|
|
})
|
|
}
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
readPersistedPromotionBinding(
|
|
userDataDir,
|
|
terminal.worktreeId,
|
|
originalTabId,
|
|
paneIdentity.leafId
|
|
),
|
|
{
|
|
timeout: 15_000,
|
|
message: 'headless terminal binding was not persisted during desktop promotion'
|
|
}
|
|
)
|
|
.toEqual({
|
|
tabId: originalTabId,
|
|
leafId: paneIdentity.leafId,
|
|
ptyId: originalPtyId
|
|
})
|
|
|
|
const page = await serveApp.firstWindow({ timeout: 60_000 })
|
|
await page.waitForLoadState('domcontentloaded')
|
|
await page.waitForFunction(() => Boolean(window.__store), null, { timeout: 30_000 })
|
|
await waitForSessionReady(page)
|
|
await waitForActiveWorktree(page)
|
|
await ensureTerminalVisible(page)
|
|
await waitForActiveTerminalManager(page, 30_000)
|
|
await waitForPaneCount(page, 1, 30_000)
|
|
|
|
const promotedPtyId = await discoverActivePtyId(page)
|
|
const afterStatus = await client.call<RuntimeStatus>('status.get')
|
|
expect(serveApp.process().pid).toBe(ownerPid)
|
|
expect(afterStatus.result.runtimeId).toBe(beforeStatus.result.runtimeId)
|
|
expect(afterStatus.result.desktopWindowStatus).toBe('available')
|
|
expect(promotedPtyId).toBe(terminal.ptyId)
|
|
expect(readDaemonPid(userDataDir)).toBe(daemonPidBefore)
|
|
expect(await waitForProcessExit(activatingProcess, 10_000)).toBe(true)
|
|
await waitForTerminalOutput(page, beforeMarker, 30_000)
|
|
|
|
const afterMarker = `SERVE_PROMOTION_AFTER_${Date.now()}`
|
|
await execInTerminal(page, promotedPtyId, `echo ${afterMarker}`)
|
|
await waitForTerminalOutput(page, afterMarker, 15_000)
|
|
await expect(page.locator('.xterm:visible').first()).toBeVisible()
|
|
expect(await getTerminalContent(page)).toContain(beforeMarker)
|
|
} finally {
|
|
if (activatingProcess && activatingProcess.exitCode === null) {
|
|
activatingProcess.kill('SIGKILL')
|
|
await waitForProcessExit(activatingProcess, 5_000)
|
|
}
|
|
if (serveApp) {
|
|
await closeElectronAppForE2E(serveApp)
|
|
}
|
|
await cleanupE2EDaemons(userDataDir)
|
|
rmSync(userDataDir, { recursive: true, force: true, maxRetries: 20, retryDelay: 100 })
|
|
}
|
|
})
|