import { DaemonFleet } from '../../../daemon/fleet.js'; import { type FleetExitStatus } from '../../runtime/fleet.js'; import type { NodeMeta, NodeStatus, Mode, Lifecycle, ExitIntent } from '../../canvas/types.js'; import type { InboxEntry } from '../../feed/inbox.js'; export declare const TMUX_TIMEOUT_MS = 10000; /** True when a usable tmux is on PATH — tests gate on this and SKIP otherwise. */ export declare function hasTmux(): boolean; export interface WaitOpts { timeoutMs?: number; intervalMs?: number; label?: string; } export interface Injected { content: string; deliverAs?: string; } export interface BootProof { pid: number; nodeId: string; resuming: boolean; prompt: string | null; extPaths: string[]; loaded: string[]; failedExt: string[]; env: Record; [k: string]: unknown; } export interface CliResult { code: number; stdout: string; stderr: string; json?: unknown; } export interface HarnessOpts { sessionPrefix?: string; /** HEADLESS mode: drive ONLY broker-hosted (paneless) nodes. When set the * harness does NOT gate on tmux, does NOT eagerly create the isolated tmux * session, and skips the per-pane `set-environment` seeding — nothing in a * headless run opens a window. CRTR_NODE_SESSION/CRTR_ROOT_SESSION are still * scrubbed/restored for env hygiene, but no longer route placement: a managed * child opens NO viewer (it boots a detached broker, supervised by pid), and a * --root opens one viewer in the caller's current session. Headless tests must * use `spawnHeadlessChild`, never `spawnChild`. See createHeadlessHarness(). */ headless?: boolean; } export interface SpawnOpts { kind?: string; mode?: Mode; lifecycle?: Lifecycle; model?: string; id?: string; } /** Fabricate a broker-hosted node row directly — tmux-free, boot-free, NO * subprocess. Builds a full NodeMeta (identity ∪ runtime) and calls createNode, * which seeds BOTH halves in one statement (seedRow writes status/intent/pi_pid * from the meta; writeMeta persists pi_session_id). The result is a row the * daemon supervises by pid exactly as if a real broker had booted — so a test * can drive superviseTick(now) liveness decisions against arbitrary liveness * state (alive/dead pid, with/without session, any intent) with ZERO real boot. * See foundation-spec §E.2. */ export interface FabricateOpts { parent?: string | null; kind?: string; mode?: Mode; lifecycle?: Lifecycle; status?: NodeStatus; intent?: ExitIntent; pi_pid?: number | null; pi_session_id?: string | null; pi_session_file?: string | null; id?: string; } export interface Harness { home: string; /** Isolated HOME inherited by fake broker processes (provider state lives here). */ agentHome: string; session: string; spawnRoot(task: string, o?: SpawnOpts): string; fabricateBrokerNode(o?: FabricateOpts): string; spawnChild(parentId: string, task: string, o?: SpawnOpts): Promise; cli(nodeId: string | null, args: string[]): CliResult; spawnHeadlessChild(parentId: string, task: string, o?: SpawnOpts): Promise; spawnHeadlessChildNoBoot(parentId: string, task: string, o?: SpawnOpts): Promise; turn(nodeId: string, text?: string): Promise; stop(nodeId: string, reason?: 'stop' | 'length' | 'aborted' | 'error', errorMessage?: string): Promise; finish(nodeId: string, finalText: string): Promise; tick(now?: number): Promise; fleet: DaemonFleet; deliverExit(nodeId: string, status?: FleetExitStatus): Promise; drainFleet(): Promise; awaitFleetExit(nodeId: string, timeoutMs?: number): Promise; awaitBoot(nodeId: string, o?: { minCount?: number; timeoutMs?: number; }): Promise; awaitWake(nodeId: string, o?: { sinceCount?: number; timeoutMs?: number; match?: RegExp; }): Promise; waitForStatus(nodeId: string, status: NodeStatus, timeoutMs?: number): Promise; waitForPaneGone(nodeId: string, timeoutMs?: number): Promise; waitFor(probe: () => T | undefined | null | false, o?: WaitOpts): Promise; node(nodeId: string): NodeMeta | null; status(nodeId: string): NodeStatus | null; paneAlive(nodeId: string): boolean; inbox(nodeId: string): InboxEntry[]; injected(nodeId: string): Injected[]; fakeCmd(nodeId: string, cmd: Record): void; dialogResults(nodeId: string): { resolved: unknown; ms: number; }[]; brokerSock(nodeId: string): string; bootCount(nodeId: string): number; subscribers(nodeId: string): { node_id: string; active: boolean; }[]; subscriptions(nodeId: string): { node_id: string; active: boolean; }[]; dispose(): Promise; } /** Thin wrapper: a headless-only harness (broker-hosted, paneless nodes). */ export declare function createHeadlessHarness(opts?: HarnessOpts): Promise; export declare function createHarness(opts?: HarnessOpts): Promise;