/** * @file * * Launches and manages a harness-owned, isolated Obsidian desktop instance. * * The instance runs against a dedicated `--user-data-dir` (so it never touches * the user's Obsidian config, registry, or running window) and exposes CDP on a * dedicated `--remote-debugging-port`. Because Electron's single-instance lock * is keyed to the user-data dir, the owned instance runs in parallel with the * user's own Obsidian. Confirmed by the Phase 0 spike (see the project plan). * * Winning that lock is also why every owned instance takes Obsidian's * first-instance path and tries to serve the CLI on a pipe whose name is * machine-global per user, and so logs `CLI server error: ... EADDRINUSE` when * anything else — normally the developer's own Obsidian — got there first. That * line is expected on every such run, not a sign of two runs contending, and * nothing the harness does goes through the pipe. AGENTS.md L59 has the * measurement. */ import type { ProcessExitInfo } from './process-exit-message.cjs'; /** * Parameters for {@link launchOwnedObsidianInstance}. */ export interface LaunchOwnedObsidianInstanceParams { /** * CDP host to bind/poll. * * @default `'127.0.0.1'` */ readonly cdpHost?: string; /** Absolute path to the Obsidian executable (shell) to launch. */ readonly exePath: string; /** * Extra command-line arguments appended after `--user-data-dir` and * `--remote-debugging-port` — used to pass the keep-alive Chromium flags when * the instance is launched hidden (off-screen). Empty by default. */ readonly extraArguments?: readonly string[]; /** Absolute path to the isolated user-data dir to pass via `--user-data-dir`. */ readonly userDataDirectory: string; } /** * A running, harness-owned Obsidian instance. */ export interface OwnedObsidianInstance { /** Base CDP URL, e.g. `http://127.0.0.1:51888`. */ readonly cdpUrl: string; /** Kills the instance and its entire process tree. */ kill: () => void; /** * Loopback port the instance's renderer connects back to so it destroys * itself if this harness process dies without cleaning up. See * `parent-liveness.ts` for why the parent/child relationship alone is not * enough. */ readonly parentLivenessPort: number; /** The CDP remote-debugging port the instance was launched with. */ readonly port: number; /** * Returns how the instance died once it is no longer running, otherwise * `undefined`. This is the in-process half of the answer; a test worker in * another process reads the same facts from the exit marker * (`owned-instance-exit-marker.ts`). */ readExitInfo: () => ProcessExitInfo | undefined; /** Returns the tail of everything the instance wrote to stdout/stderr. */ readOutput: () => string; } /** * Launches an isolated, harness-owned Obsidian instance and waits until its CDP * endpoint is serving page targets. * * @param params - Launch parameters. * @returns The running owned instance. * @throws Error if CDP does not become reachable within the timeout. */ export declare function launchOwnedObsidianInstance(params: LaunchOwnedObsidianInstanceParams): Promise; /** * Picks a free TCP port by binding to port `0` and reading the assigned port. * * There is a small time-of-check/time-of-use window between releasing the port * here and Obsidian binding it; in practice it is negligible for test runs. * * @returns A free TCP port. */ export declare function pickFreePort(): Promise;