import { type ChildProcess } from 'node:child_process';
import type { FrameworkEvent } from './events.js';
import type { StartAgentKind, StartAgentOptions, StartAgentResult, AddProjectResult } from './dashboard/index.js';
import type { EventsSource, RemoteAgents } from './dashboard/rpc-serve.js';
import { type PreflightResult } from './preflight.js';
import { type DriverName } from './driver-names.js';
/**
 * The daemon's per-project business logic (#393/#736): spawning runs into worktrees, installing
 * projects, and app previews, plus the spawn/terminate plumbing those need. Split from daemon.ts
 * so that file reads as the daemon's lifecycle (state file, ports, boot, shutdown) and this reads
 * as what the daemon does for a project -- the split createProjectRuntime's own doc always
 * claimed, finished.
 */
/**
 * Locate the CLI entry to re-invoke for a detached child, refusing to re-exec a test
 * file. Under `node --test` (or a direct `node foo.test.js`) `process.argv[1]` is the test
 * file, which re-runs the whole suite instead of the daemon/run body — and that suite calls
 * back here, so each spawn spawns another: a fork bomb. A real agent passes the compiled bin
 * (or an explicit `binPath`), so the guard only ever trips in tests.
 */
export declare function resolveSpawnBin(explicitBinPath: string | undefined): string;
/**
 * Clean up after a `git worktree add` that was SIGTERMed mid-write (#997). Observed behavior: git
 * removes its own administrative entry on the way out but leaves the partial checkout it had
 * already written, so `git worktree prune` finds nothing to do and the directory stays.
 *
 * Only a timeout kill is cleaned up. Any other rejection may be git refusing a path that was
 * already on disk before this agent asked for it, and that is not ours to delete.
 */
export declare function cleanupTimedOutWorktree(repo: string, agentId: string, err: unknown): Promise<void>;
/** Spawn a detached, unref'd framework child (`node <binPath> --agent <specPath>`) that outlives us. */
export declare function spawnDetached(binPath: string, specPath: string, stderrFile?: string): ChildProcess;
/** Where a spawned agent's stderr lands (#1261), so a child that dies at boot leaves a trace. */
export declare function agentStderrPath(cwd: string): string;
/**
 * Leave a `failed` marker behind a child that died before writing its own lifecycle (#1261).
 *
 * A healthy agent's first act is opening its store (`agent.json` + `events.jsonl`); a child that
 * exited without one never booted — a module resolution error being the observed case — and with
 * stdio detached the crash went nowhere, so the session page polled "Waiting for the session to
 * start" forever. The daemon's exit handler is the one place that knows, so it writes the minimal
 * meta the page needs and surfaces the child's stderr tail in the agent log. A child that wrote its
 * own meta is left alone: its lifecycle is its own to report.
 */
export declare function markFailedStart(cwd: string, agentId: string, intent: string, detail: string): Promise<boolean>;
/** Whether an agent's failure detail names a transient transport error (#1281). */
export declare function isTransientAgentFailure(detail: string | undefined): boolean;
/**
 * The detail the agent's own `end` event failed with, off its archived event log (#1281), or
 * undefined when the agent did not fail by its own report. Only a child-written `end` counts: a
 * boot death (#1261) never writes one, and retrying an agent that cannot boot would just re-crash.
 */
export declare function lastAgentFailureDetail(eventsJsonl: string): string | undefined;
/** How many times a transiently-dead agent is continued before its failure stands (#1281). */
export declare const MAX_TRANSIENT_RETRIES = 2;
export declare function delay(ms: number): Promise<void>;
/**
 * Stop a process and wait for it to actually go: SIGTERM, then SIGKILL if the grace period lapses.
 * Returns whether it was alive to begin with.
 *
 * Both callers need the escalation for the same reason and had their own copy of it: a process
 * that ignores SIGTERM (or wedges in shutdown) must not be left holding a port or a worktree.
 */
export declare function terminate(pid: number, graceMs: number): Promise<boolean>;
/**
 * Wait until the daemon is finished with the agent slots it just stopped — the child gone *and* its
 * teardown done — or the timeout lapses.
 *
 * Killing a pid is not letting go of the repo. The child's `exit` event lands a turn after the
 * process disappears, and the teardown that event starts (archive the agent, commit its work, keep
 * or remove its checkout) runs well past that. Without this wait, shutdown's archive commit fires
 * while an agent is still being archived, and it misses that agent's ending (#912/#1179).
 *
 * A slot is settled when it is in neither map: `settle` drops it from `activeAgents` and parks its
 * teardown in `retiring`, and the teardown clears itself on the way out. An agent with no worktree
 * never enters `retiring` at all, and needs no special case — it is simply already gone from both.
 *
 * Bounded, because a wedged teardown must cost the shutdown its grace period, not the exit.
 */
export declare function waitOutSlots(keys: readonly string[], slots: {
    activeAgents: Map<string, number>;
    retiring: Map<string, Promise<void>>;
}, timeoutMs: number): Promise<void>;
/**
 * What the previous leg of an agent says about itself, for {@link waitOutFinishedLeg}. `unknown` is
 * the honest third answer — the leg has no readable state *this instant* — and is deliberately
 * not folded into either of the other two.
 */
export type FinishedLegState = 'ended' | 'running' | 'unknown';
/**
 * Wait out the previous leg of the agent a continuation is aimed at (#1529). A Resume clicked the
 * instant an agent's row flips `done` can land while the child that wrote that ending is still
 * mid-exit: the agent's slot then still holds a live pid, and the busy guard read "already active"
 * off a session that is over by its own account — a spurious refusal the E2E settings story
 * caught on a slow runner. A finished child's exit is imminent and its retirement is queued
 * right behind it (see `retiring` in {@link createProjectRuntime}), so wait for both, bounded by
 * `graceMs`, and let the reuse read a settled archive. A leg still calling itself `running` is a
 * genuine collision: not waited on, so the guard's refusal stands.
 *
 * `readLegState` is asked until it commits, rather than sampled once (#1540). A leg's state is
 * read off a `agent.json` its own process rewrites in place, so a single read can come back
 * `unknown` for reasons that have nothing to do with the leg — a torn read, or the beat between
 * the archive being written and the worktree going. Taking one such sample for "still running"
 * skipped the wait entirely and handed the continuation to the busy guard mid-exit: #1529's
 * refusal back as a rarer race, and the flake that sent this here. Only a leg that positively
 * reports `running` short-circuits; `unknown` re-asks on the next tick, and the loop still ends
 * the moment the slot clears, so the common path costs exactly one read as before.
 */
export declare function waitOutFinishedLeg(key: string, slots: {
    starting: Set<string>;
    activeAgents: Map<string, number>;
    retiring: Map<string, Promise<void>>;
}, readLegState: () => Promise<FinishedLegState>, graceMs: number): Promise<void>;
/** Inputs to {@link createProjectRuntime}. */
export interface ProjectRuntimeOptions {
    /** The daemon's home workspace; a run/preview with no project id targets it. */
    cwd: string;
    /** Env for the registry lookups (#393). */
    env: NodeJS.ProcessEnv;
    /** The CLI entry to spawn runs with (#345); undefined uses `process.argv[1]`. */
    binPath?: string | undefined;
    /** The pause before a transient-death retry (#1281); undefined uses {@link TRANSIENT_RETRY_DELAY_MS}. A test seam. */
    retryDelayMs?: number | undefined;
    /** How a start checks the agent can run (#1326); undefined runs the real {@link preflight}. A test seam. */
    driverPreflight?: ((driver: DriverName) => Promise<PreflightResult>) | undefined;
}
/** The per-project agent + preview surface the dashboard drives, plus its teardown. */
export interface ProjectRuntime {
    onStart: (prompt: string, kind: StartAgentKind, options?: StartAgentOptions, targetProjectId?: string) => Promise<StartAgentResult>;
    onAddProject: (path: string, directory: boolean) => Promise<AddProjectResult>;
    /** The live event stream for an agent this daemon is relaying from a device (#1067), else undefined
     *  so `onEvents` falls back to tailing the on-disk log. Wired as the dashboard's events source. */
    remoteEventsSource: EventsSource;
    /** Tail a relay-started agent's on-disk events (#1067): the daemon's `/_relay/events` endpoint uses
     *  it to stream one agent back to whichever daemon relayed it here. */
    tailRelayEvents: (agentId: string, onEvent: (event: FrameworkEvent) => void) => () => void;
    /** The relayed-agent lookup the dashboard's read RPCs consult (#1067 slice 2): which device a remote
     *  run runs on, so a run-scoped RPC forwards there instead of resolving a local checkout. */
    remoteAgents: RemoteAgents;
    /** The device side of the relay (#1067 slice 2): run one whitelisted read/steer/handoff RPC against
     *  this daemon's own home checkout, for a daemon that relayed an agent here. */
    onRelayRpc: (fn: string, args: unknown[]) => Promise<unknown>;
    /** Live agents on a project (#685), so a background job can tell an idle project from a busy one. */
    activeAgentCount: (targetProjectId: string) => number;
    /**
     * The agent ids this daemon is still responsible for: spawning, running, or mid-retirement.
     *
     * The background worktree sweep asks, because an agent's meta flips to `done` a beat *before* its
     * teardown archives and reclaims the checkout — so a sweep landing in that window would race
     * the teardown for the same directory. "Not live" on disk is not the same as "the daemon is
     * finished with it", and this is the difference.
     */
    busyAgentIds: () => ReadonlySet<string>;
    /**
     * Stop the agents this daemon spawned. Returns how many were stopped. Called on shutdown, before
     * the previews go.
     */
    stopAgents: (graceMs?: number) => Promise<number>;
    /** Stop every live preview so their dev servers do not outlive the daemon (#475). */
    dispose: () => Promise<void>;
}
/**
 * The daemon's per-project runtime (#393): the agent and preview state keyed by project id,
 * plus the RPCs the dashboard invokes over `POST /_rpc/<name>`. A project runs any number of
 * concurrent agents (each in its own worktree, #736) and one preview. The home `cwd` is the default target — a request
 * with no project id (or the home id) resolves to it without a registry lookup. Split out of
 * {@link runDaemon} so the daemon body reads as lifecycle and this reads as business logic.
 */
export declare function createProjectRuntime({ cwd, env, binPath, retryDelayMs, driverPreflight }: ProjectRuntimeOptions): ProjectRuntime;
//# sourceMappingURL=daemon-runtime.d.ts.map