/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor, executor/no-promise-reject -- e2e boundary: drives launchd, a packaged Electron process, CDP, and optional ffmpeg recording */import { type ChildProcess, execFile, execFileSync, spawn } from "node:child_process";import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync,} from "node:fs";import { homedir, tmpdir } from "node:os";import { dirname, join, resolve } from "node:path";import { promisify } from "node:util"; import { expect, it } from "@effect/vitest";import { Effect } from "effect"; import { scenario } from "../src/scenario";import { RunDir } from "../src/services"; const execFileAsync = promisify(execFile);const SERVICE_LABEL = "sh.executor.daemon";const SCENARIO_NAME = "Desktop packaged supervised attach: stale launchd registration self-heals without a dead boot wait";const RECORDING_FILE = resolve("recordings/stale-launchd-fast-boot.mp4");const RECORDING_WIDTH = 1280;const RECORDING_HEIGHT = 720;const STALE_PLIST_PORT = 58_251; interface PackagedExecutorBridge { readonly getServerConnection: () => Promise<{ readonly kind: string; readonly origin: string; } | null>;} interface PackagedApp { readonly child: ChildProcess; cdp: CdpPage; readonly debugPort: string; readonly output: () => string;} interface CdpResponse<T> { readonly id: number; readonly result?: T; readonly error?: { readonly message?: string; readonly data?: string };} interface CdpEvaluateResult { readonly result: { readonly value?: unknown }; readonly exceptionDetails?: unknown;} interface CdpTarget { readonly type: string; readonly url: string; readonly webSocketDebuggerUrl?: string;} interface ServerManifest { readonly kind: string; readonly pid: number;} interface LaunchTiming { readonly app: PackagedApp; readonly pageCdpMs: number;} interface BootResult { readonly pageCdpMs: number; readonly startupWindowMs: number; readonly sidecarReadyMs: number; readonly connectionKind: string; readonly origin: string; readonly manifestKind: string; readonly manifestPid: number; readonly kickstartFailureLine: string | null; readonly recoveryLogLine: string | null; readonly launchdPrintSucceeded: boolean;} declare global { interface Window { readonly executor: PackagedExecutorBridge; }} class CdpPage { private nextId = 1; private readonly pending = new Map< number, { readonly resolve: (value: unknown) => void; readonly reject: (error: Error) => void; } >(); private constructor(private readonly socket: WebSocket) { socket.addEventListener("message", (event) => { const data = event.data; if (typeof data !== "string") return; const message = JSON.parse(data) as CdpResponse<unknown>; if (!message.id) return; const pending = this.pending.get(message.id); if (!pending) return; this.pending.delete(message.id); if (message.error) { pending.reject(new Error(message.error.message ?? "CDP command failed")); return; } pending.resolve(message.result); }); socket.addEventListener("close", () => { for (const [, pending] of this.pending) { pending.reject(new Error("CDP socket closed")); } this.pending.clear(); }); } static connect = (url: string): Promise<CdpPage> => new Promise((resolveConnect, rejectConnect) => { const socket = new WebSocket(url); const timer = setTimeout(() => { socket.close(); rejectConnect(new Error(`Timed out connecting to page CDP target ${url}`)); }, 30_000); socket.addEventListener( "open", () => { clearTimeout(timer); resolveConnect(new CdpPage(socket)); }, { once: true }, ); socket.addEventListener( "error", () => { clearTimeout(timer); rejectConnect(new Error(`Failed to connect to page CDP target ${url}`)); }, { once: true }, ); }); command = async <T>(method: string, params: Record<string, unknown> = {}): Promise<T> => { const id = this.nextId; this.nextId += 1; const result = new Promise<T>((resolveCommand, rejectCommand) => { this.pending.set(id, { resolve: (value) => resolveCommand(value as T), reject: rejectCommand, }); }); this.socket.send(JSON.stringify({ id, method, params })); return result; }; evaluate = async <T>(expression: string): Promise<T> => { const result = await this.command<CdpEvaluateResult>("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true, }); if (result.exceptionDetails) { throw new Error(`CDP evaluation failed: ${JSON.stringify(result.exceptionDetails)}`); } return result.result.value as T; }; waitForText = async (text: string, timeoutMs: number): Promise<void> => { const deadline = Date.now() + timeoutMs; const expression = `document.body?.innerText.includes(${JSON.stringify(text)}) ?? false`; for (;;) { if (await this.evaluate<boolean>(expression).catch(() => false)) return; if (Date.now() >= deadline) throw new Error(`Timed out waiting for text: ${text}`); await new Promise((resolveWait) => setTimeout(resolveWait, 250)); } }; setViewport = async (width: number, height: number): Promise<void> => { await this.command("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false, }); }; screenshot = async (path: string): Promise<void> => { const result = await this.command<{ readonly data: string }>("Page.captureScreenshot", { format: "png", fromSurface: true, }); writeFileSync(path, Buffer.from(result.data, "base64")); }; close = (): void => { this.socket.close(); };} const appExe = process.env.E2E_DESKTOP_APP_EXE;const executorBin = process.env.E2E_DESKTOP_EXECUTOR_BIN; const macAquaAvailable = (): boolean => { if (process.platform !== "darwin") return false; try { return execFileSync("launchctl", ["managername"], { encoding: "utf8" }).trim() === "Aqua"; } catch { return false; }}; const packagedSingleInstanceAvailable = (): boolean => { if (process.platform !== "darwin" || !appExe) return true; try { const lines = execFileSync("pgrep", ["-fl", "Executor.app/Contents/MacOS/Executor"], { encoding: "utf8", }) .split("\n") .filter(Boolean); return !lines.some((line) => !line.includes(appExe)); } catch { return true; }}; const requireBundle = (): { readonly app: string; readonly executor: string } => { if (!appExe || !executorBin) { throw new Error( "E2E_DESKTOP_APP_EXE / E2E_DESKTOP_EXECUTOR_BIN not set, did desktop-packaged.globalsetup run?", ); } return { app: appExe, executor: executorBin };}; const currentUid = (): number => { const getuid = (process as { readonly getuid?: () => number }).getuid; return typeof getuid === "function" ? getuid.call(process) : 0;}; const serviceTarget = (): string => `gui/${currentUid()}/${SERVICE_LABEL}`;const launchAgentPath = (): string => join(homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);const isolatedDesktopSettingsDir = (home: string): string => join(home, ".executor-desktop-settings"); const packagedAppEnv = (home: string): NodeJS.ProcessEnv => ({ ...process.env, HOME: home, EXECUTOR_DESKTOP_SETTINGS_DIR: isolatedDesktopSettingsDir(home),}); const cleanBootEnv = (home: string): NodeJS.ProcessEnv => ({ ...packagedAppEnv(home), EXECUTOR_TEST_SKIP_BACKGROUND_SERVICE: "1", EXECUTOR_TEST_AUTO_CONFIRM_BACKGROUND_SERVICE: "0",}); interface LaunchdServiceSnapshot { readonly plist: string | null; readonly wasLoaded: boolean;} const launchctl = async (args: ReadonlyArray<string>): Promise<boolean> => { try { await execFileAsync("launchctl", [...args]); return true; } catch { return false; }}; const launchctlPrintService = async (): Promise<string | null> => { try { const { stdout, stderr } = await execFileAsync("launchctl", ["print", serviceTarget()], { encoding: "utf8", }); return `${stdout}${stderr}`.trim(); } catch { return null; }}; const captureLaunchdService = (): LaunchdServiceSnapshot | null => { if (process.platform !== "darwin") return null; const path = launchAgentPath(); const plist = existsSync(path) ? readFileSync(path, "utf8") : null; let wasLoaded = false; try { execFileSync("launchctl", ["print", serviceTarget()], { stdio: "ignore" }); wasLoaded = true; } catch { wasLoaded = false; } return { plist, wasLoaded };}; const restoreLaunchdService = async (snapshot: LaunchdServiceSnapshot | null): Promise<void> => { if (!snapshot) return; const target = serviceTarget(); await launchctl(["bootout", target]); const path = launchAgentPath(); if (snapshot.plist === null) { rmSync(path, { force: true }); return; } mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, snapshot.plist, { mode: 0o600 }); chmodSync(path, 0o600); await launchctl(["enable", target]); if (snapshot.wasLoaded) { const bootstrapped = await launchctl(["bootstrap", `gui/${currentUid()}`, path]); if (bootstrapped) await launchctl(["kickstart", "-k", target]); }}; const waitForPageWebSocket = async (debugPort: string): Promise<string> => { const deadline = Date.now() + 120_000; for (;;) { const targets = (await fetch(`http://127.0.0.1:${debugPort}/json/list`) .then((response) => (response.ok ? response.json() : [])) .catch(() => [])) as ReadonlyArray<CdpTarget>; const page = targets.find( (target) => target.type === "page" && target.webSocketDebuggerUrl && !target.url.startsWith("devtools://"), ); if (page?.webSocketDebuggerUrl) return page.webSocketDebuggerUrl; if (Date.now() >= deadline) { throw new Error("Timed out waiting for packaged app page CDP target"); } await new Promise((resolveWait) => setTimeout(resolveWait, 100)); }}; const launchPackaged = async ( env: NodeJS.ProcessEnv, recorder: BootRecorder | null,): Promise<LaunchTiming> => { const { app } = requireBundle(); const startedAt = Date.now(); let output = ""; let settled = false; const child = spawn(app, ["--remote-debugging-port=0"], { env, stdio: ["ignore", "pipe", "pipe"], }); recorder?.start(startedAt); try { const browserCdpUrl = await new Promise<string>((resolveLaunch, rejectLaunch) => { const timer = setTimeout(() => { rejectLaunch(new Error(`Timed out waiting for packaged app CDP URL\n${output}`)); }, 120_000); const settle = (fn: () => void) => { if (settled) return; settled = true; clearTimeout(timer); fn(); }; const collectOutput = (chunk: Buffer) => { const text = chunk.toString(); output = (output + text).slice(-16_384); const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/); if (match) settle(() => resolveLaunch(match[1])); }; child.stdout?.on("data", collectOutput); child.stderr?.on("data", collectOutput); child.once("error", (error) => settle(() => rejectLaunch(error))); child.once("exit", (code, signal) => settle(() => rejectLaunch( new Error(`Packaged app exited before CDP (code=${code} signal=${signal})\n${output}`), ), ), ); }); const debugPort = new URL(browserCdpUrl).port; const pageCdpUrl = await waitForPageWebSocket(debugPort); const cdp = await CdpPage.connect(pageCdpUrl); await cdp.command("Runtime.enable"); await cdp.command("Page.enable"); await cdp.setViewport(RECORDING_WIDTH, RECORDING_HEIGHT); recorder?.attachPage(cdp); return { app: { child, cdp, debugPort, output: () => output }, pageCdpMs: Date.now() - startedAt, }; } catch (error) { await stopProcess(child); throw error; }}; const stopProcess = async (child: ChildProcess | undefined): Promise<void> => { if (!child || child.exitCode !== null || child.signalCode !== null) return; await new Promise<void>((resolveStop) => { const timeout = setTimeout(() => { child.kill("SIGKILL"); resolveStop(); }, 5_000); child.once("exit", () => { clearTimeout(timeout); resolveStop(); }); child.kill("SIGTERM"); });}; const closePackaged = async (app: PackagedApp | undefined): Promise<void> => { app?.cdp.close(); await stopProcess(app?.child);}; const xmlEscape = (value: string): string => value .replaceAll("&", "&") .replaceAll('"', """) .replaceAll(">", ">") .replaceAll("<", "<"); const writeStaleLaunchAgentPlist = (home: string): void => { const { executor } = requireBundle(); const dataDir = join(home, ".executor"); const logs = join(dataDir, "logs"); const plistDir = join(home, "Library", "LaunchAgents"); mkdirSync(plistDir, { recursive: true }); mkdirSync(logs, { recursive: true }); mkdirSync(dataDir, { recursive: true }); const programArguments = [ executor, "daemon", "run", "--foreground", "--port", String(STALE_PLIST_PORT), "--hostname", "127.0.0.1", ]; const programArgs = programArguments .map((arg) => ` <string>${xmlEscape(arg)}</string>`) .join("\n"); const plist = `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>${SERVICE_LABEL}</string> <key>ProgramArguments</key> <array>${programArgs} </array> <key>EnvironmentVariables</key> <dict> <key>EXECUTOR_SUPERVISED</key> <string>1</string> <key>EXECUTOR_DATA_DIR</key> <string>${xmlEscape(dataDir)}</string> <key>EXECUTOR_SCOPE_DIR</key> <string>${xmlEscape(dataDir)}</string> <key>EXECUTOR_CLIENT</key> <string>desktop</string> </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>ProcessType</key> <string>Background</string> <key>WorkingDirectory</key> <string>${xmlEscape(dataDir)}</string> <key>StandardOutPath</key> <string>${xmlEscape(join(logs, "daemon.log"))}</string> <key>StandardErrorPath</key> <string>${xmlEscape(join(logs, "daemon.error.log"))}</string></dict></plist>`; writeFileSync(join(plistDir, `${SERVICE_LABEL}.plist`), plist, { mode: 0o600 });}; const readServerManifest = (home: string): ServerManifest => { const manifestPath = join(home, ".executor", "server-control", "server.json"); return JSON.parse(readFileSync(manifestPath, "utf8")) as ServerManifest;}; interface StaleRecoveryLog { readonly kickstartFailureLine: string | null;Lines 1–500 of 945. Load more
/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor, executor/no-promise-reject -- e2e boundary: drives launchd, a packaged Electron process, CDP, and optional ffmpeg recording */ import { type ChildProcess, execFile, execFileSync, spawn } from "node:child_process"; import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { promisify } from "node:util"; import { expect, it } from "@effect/vitest"; import { Effect } from "effect"; import { scenario } from "../src/scenario"; import { RunDir } from "../src/services"; const execFileAsync = promisify(execFile); const SERVICE_LABEL = "sh.executor.daemon"; const SCENARIO_NAME = "Desktop packaged supervised attach: stale launchd registration self-heals without a dead boot wait"; const RECORDING_FILE = resolve("recordings/stale-launchd-fast-boot.mp4"); const RECORDING_WIDTH = 1280; const RECORDING_HEIGHT = 720; const STALE_PLIST_PORT = 58_251; interface PackagedExecutorBridge { readonly getServerConnection: () => Promise<{ readonly kind: string; readonly origin: string; } | null>; } interface PackagedApp { readonly child: ChildProcess; cdp: CdpPage; readonly debugPort: string; readonly output: () => string; } interface CdpResponse<T> { readonly id: number; readonly result?: T; readonly error?: { readonly message?: string; readonly data?: string }; } interface CdpEvaluateResult { readonly result: { readonly value?: unknown }; readonly exceptionDetails?: unknown; } interface CdpTarget { readonly type: string; readonly url: string; readonly webSocketDebuggerUrl?: string; } interface ServerManifest { readonly kind: string; readonly pid: number; } interface LaunchTiming { readonly app: PackagedApp; readonly pageCdpMs: number; } interface BootResult { readonly pageCdpMs: number; readonly startupWindowMs: number; readonly sidecarReadyMs: number; readonly connectionKind: string; readonly origin: string; readonly manifestKind: string; readonly manifestPid: number; readonly kickstartFailureLine: string | null; readonly recoveryLogLine: string | null; readonly launchdPrintSucceeded: boolean; } declare global { interface Window { readonly executor: PackagedExecutorBridge; } } class CdpPage { private nextId = 1; private readonly pending = new Map< number, { readonly resolve: (value: unknown) => void; readonly reject: (error: Error) => void; } >(); private constructor(private readonly socket: WebSocket) { socket.addEventListener("message", (event) => { const data = event.data; if (typeof data !== "string") return; const message = JSON.parse(data) as CdpResponse<unknown>; if (!message.id) return; const pending = this.pending.get(message.id); if (!pending) return; this.pending.delete(message.id); if (message.error) { pending.reject(new Error(message.error.message ?? "CDP command failed")); return; } pending.resolve(message.result); }); socket.addEventListener("close", () => { for (const [, pending] of this.pending) { pending.reject(new Error("CDP socket closed")); } this.pending.clear(); }); } static connect = (url: string): Promise<CdpPage> => new Promise((resolveConnect, rejectConnect) => { const socket = new WebSocket(url); const timer = setTimeout(() => { socket.close(); rejectConnect(new Error(`Timed out connecting to page CDP target ${url}`)); }, 30_000); socket.addEventListener( "open", () => { clearTimeout(timer); resolveConnect(new CdpPage(socket)); }, { once: true }, ); socket.addEventListener( "error", () => { clearTimeout(timer); rejectConnect(new Error(`Failed to connect to page CDP target ${url}`)); }, { once: true }, ); }); command = async <T>(method: string, params: Record<string, unknown> = {}): Promise<T> => { const id = this.nextId; this.nextId += 1; const result = new Promise<T>((resolveCommand, rejectCommand) => { this.pending.set(id, { resolve: (value) => resolveCommand(value as T), reject: rejectCommand, }); }); this.socket.send(JSON.stringify({ id, method, params })); return result; }; evaluate = async <T>(expression: string): Promise<T> => { const result = await this.command<CdpEvaluateResult>("Runtime.evaluate", { expression, awaitPromise: true, returnByValue: true, }); if (result.exceptionDetails) { throw new Error(`CDP evaluation failed: ${JSON.stringify(result.exceptionDetails)}`); } return result.result.value as T; }; waitForText = async (text: string, timeoutMs: number): Promise<void> => { const deadline = Date.now() + timeoutMs; const expression = `document.body?.innerText.includes(${JSON.stringify(text)}) ?? false`; for (;;) { if (await this.evaluate<boolean>(expression).catch(() => false)) return; if (Date.now() >= deadline) throw new Error(`Timed out waiting for text: ${text}`); await new Promise((resolveWait) => setTimeout(resolveWait, 250)); } }; setViewport = async (width: number, height: number): Promise<void> => { await this.command("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 1, mobile: false, }); }; screenshot = async (path: string): Promise<void> => { const result = await this.command<{ readonly data: string }>("Page.captureScreenshot", { format: "png", fromSurface: true, }); writeFileSync(path, Buffer.from(result.data, "base64")); }; close = (): void => { this.socket.close(); }; } const appExe = process.env.E2E_DESKTOP_APP_EXE; const executorBin = process.env.E2E_DESKTOP_EXECUTOR_BIN; const macAquaAvailable = (): boolean => { if (process.platform !== "darwin") return false; try { return execFileSync("launchctl", ["managername"], { encoding: "utf8" }).trim() === "Aqua"; } catch { return false; } }; const packagedSingleInstanceAvailable = (): boolean => { if (process.platform !== "darwin" || !appExe) return true; try { const lines = execFileSync("pgrep", ["-fl", "Executor.app/Contents/MacOS/Executor"], { encoding: "utf8", }) .split("\n") .filter(Boolean); return !lines.some((line) => !line.includes(appExe)); } catch { return true; } }; const requireBundle = (): { readonly app: string; readonly executor: string } => { if (!appExe || !executorBin) { throw new Error( "E2E_DESKTOP_APP_EXE / E2E_DESKTOP_EXECUTOR_BIN not set, did desktop-packaged.globalsetup run?", ); } return { app: appExe, executor: executorBin }; }; const currentUid = (): number => { const getuid = (process as { readonly getuid?: () => number }).getuid; return typeof getuid === "function" ? getuid.call(process) : 0; }; const serviceTarget = (): string => `gui/${currentUid()}/${SERVICE_LABEL}`; const launchAgentPath = (): string => join(homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`); const isolatedDesktopSettingsDir = (home: string): string => join(home, ".executor-desktop-settings"); const packagedAppEnv = (home: string): NodeJS.ProcessEnv => ({ ...process.env, HOME: home, EXECUTOR_DESKTOP_SETTINGS_DIR: isolatedDesktopSettingsDir(home), }); const cleanBootEnv = (home: string): NodeJS.ProcessEnv => ({ ...packagedAppEnv(home), EXECUTOR_TEST_SKIP_BACKGROUND_SERVICE: "1", EXECUTOR_TEST_AUTO_CONFIRM_BACKGROUND_SERVICE: "0", }); interface LaunchdServiceSnapshot { readonly plist: string | null; readonly wasLoaded: boolean; } const launchctl = async (args: ReadonlyArray<string>): Promise<boolean> => { try { await execFileAsync("launchctl", [...args]); return true; } catch { return false; } }; const launchctlPrintService = async (): Promise<string | null> => { try { const { stdout, stderr } = await execFileAsync("launchctl", ["print", serviceTarget()], { encoding: "utf8", }); return `${stdout}${stderr}`.trim(); } catch { return null; } }; const captureLaunchdService = (): LaunchdServiceSnapshot | null => { if (process.platform !== "darwin") return null; const path = launchAgentPath(); const plist = existsSync(path) ? readFileSync(path, "utf8") : null; let wasLoaded = false; try { execFileSync("launchctl", ["print", serviceTarget()], { stdio: "ignore" }); wasLoaded = true; } catch { wasLoaded = false; } return { plist, wasLoaded }; }; const restoreLaunchdService = async (snapshot: LaunchdServiceSnapshot | null): Promise<void> => { if (!snapshot) return; const target = serviceTarget(); await launchctl(["bootout", target]); const path = launchAgentPath(); if (snapshot.plist === null) { rmSync(path, { force: true }); return; } mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, snapshot.plist, { mode: 0o600 }); chmodSync(path, 0o600); await launchctl(["enable", target]); if (snapshot.wasLoaded) { const bootstrapped = await launchctl(["bootstrap", `gui/${currentUid()}`, path]); if (bootstrapped) await launchctl(["kickstart", "-k", target]); } }; const waitForPageWebSocket = async (debugPort: string): Promise<string> => { const deadline = Date.now() + 120_000; for (;;) { const targets = (await fetch(`http://127.0.0.1:${debugPort}/json/list`) .then((response) => (response.ok ? response.json() : [])) .catch(() => [])) as ReadonlyArray<CdpTarget>; const page = targets.find( (target) => target.type === "page" && target.webSocketDebuggerUrl && !target.url.startsWith("devtools://"), ); if (page?.webSocketDebuggerUrl) return page.webSocketDebuggerUrl; if (Date.now() >= deadline) { throw new Error("Timed out waiting for packaged app page CDP target"); } await new Promise((resolveWait) => setTimeout(resolveWait, 100)); } }; const launchPackaged = async ( env: NodeJS.ProcessEnv, recorder: BootRecorder | null, ): Promise<LaunchTiming> => { const { app } = requireBundle(); const startedAt = Date.now(); let output = ""; let settled = false; const child = spawn(app, ["--remote-debugging-port=0"], { env, stdio: ["ignore", "pipe", "pipe"], }); recorder?.start(startedAt); try { const browserCdpUrl = await new Promise<string>((resolveLaunch, rejectLaunch) => { const timer = setTimeout(() => { rejectLaunch(new Error(`Timed out waiting for packaged app CDP URL\n${output}`)); }, 120_000); const settle = (fn: () => void) => { if (settled) return; settled = true; clearTimeout(timer); fn(); }; const collectOutput = (chunk: Buffer) => { const text = chunk.toString(); output = (output + text).slice(-16_384); const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/); if (match) settle(() => resolveLaunch(match[1])); }; child.stdout?.on("data", collectOutput); child.stderr?.on("data", collectOutput); child.once("error", (error) => settle(() => rejectLaunch(error))); child.once("exit", (code, signal) => settle(() => rejectLaunch( new Error(`Packaged app exited before CDP (code=${code} signal=${signal})\n${output}`), ), ), ); }); const debugPort = new URL(browserCdpUrl).port; const pageCdpUrl = await waitForPageWebSocket(debugPort); const cdp = await CdpPage.connect(pageCdpUrl); await cdp.command("Runtime.enable"); await cdp.command("Page.enable"); await cdp.setViewport(RECORDING_WIDTH, RECORDING_HEIGHT); recorder?.attachPage(cdp); return { app: { child, cdp, debugPort, output: () => output }, pageCdpMs: Date.now() - startedAt, }; } catch (error) { await stopProcess(child); throw error; } }; const stopProcess = async (child: ChildProcess | undefined): Promise<void> => { if (!child || child.exitCode !== null || child.signalCode !== null) return; await new Promise<void>((resolveStop) => { const timeout = setTimeout(() => { child.kill("SIGKILL"); resolveStop(); }, 5_000); child.once("exit", () => { clearTimeout(timeout); resolveStop(); }); child.kill("SIGTERM"); }); }; const closePackaged = async (app: PackagedApp | undefined): Promise<void> => { app?.cdp.close(); await stopProcess(app?.child); }; const xmlEscape = (value: string): string => value .replaceAll("&", "&") .replaceAll('"', """) .replaceAll(">", ">") .replaceAll("<", "<"); const writeStaleLaunchAgentPlist = (home: string): void => { const { executor } = requireBundle(); const dataDir = join(home, ".executor"); const logs = join(dataDir, "logs"); const plistDir = join(home, "Library", "LaunchAgents"); mkdirSync(plistDir, { recursive: true }); mkdirSync(logs, { recursive: true }); mkdirSync(dataDir, { recursive: true }); const programArguments = [ executor, "daemon", "run", "--foreground", "--port", String(STALE_PLIST_PORT), "--hostname", "127.0.0.1", ]; const programArgs = programArguments .map((arg) => ` <string>${xmlEscape(arg)}</string>`) .join("\n"); const plist = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>${SERVICE_LABEL}</string> <key>ProgramArguments</key> <array> ${programArgs} </array> <key>EnvironmentVariables</key> <dict> <key>EXECUTOR_SUPERVISED</key> <string>1</string> <key>EXECUTOR_DATA_DIR</key> <string>${xmlEscape(dataDir)}</string> <key>EXECUTOR_SCOPE_DIR</key> <string>${xmlEscape(dataDir)}</string> <key>EXECUTOR_CLIENT</key> <string>desktop</string> </dict> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <key>ProcessType</key> <string>Background</string> <key>WorkingDirectory</key> <string>${xmlEscape(dataDir)}</string> <key>StandardOutPath</key> <string>${xmlEscape(join(logs, "daemon.log"))}</string> <key>StandardErrorPath</key> <string>${xmlEscape(join(logs, "daemon.error.log"))}</string> </dict> </plist> `; writeFileSync(join(plistDir, `${SERVICE_LABEL}.plist`), plist, { mode: 0o600 }); }; const readServerManifest = (home: string): ServerManifest => { const manifestPath = join(home, ".executor", "server-control", "server.json"); return JSON.parse(readFileSync(manifestPath, "utf8")) as ServerManifest; }; interface StaleRecoveryLog { readonly kickstartFailureLine: string | null;