/**
 * Job execution engine for routines.
 *
 * Builds agent-specific CLI commands from job configs, spawns them with
 * sandboxed or unsandboxed environments, captures stdout to log files,
 * enforces timeouts, and extracts the final assistant report from the
 * agent's stream-JSON output.
 *
 * Version/account selection mirrors `agents run`: when a routine does not pin
 * `version:`, the runner uses the configured run strategy (default `balanced`)
 * to pick a healthy install, pins the absolute binary via `getBinaryPath`, and
 * arms same-agent failover across other healthy accounts when a rate/usage
 * limit is detected mid-run (foreground `executeJob` only — detached daemon
 * fires once with the pre-flight pick).
 */
import type { JobConfig, RunMeta } from '../scheduling/routines.js';
import { readMeta } from '../state.js';
import type { AgentId } from '../types.js';
import { type FallbackEntry } from '../exec.js';
import type { LoopDeps } from '../loop.js';
import { resolveRunVersion, resolveAccountVersion, type RotateResult } from '../accounting/rotate.js';
/** Result of a completed job execution, including metadata and optional report. */
export interface RunResult {
    meta: RunMeta;
    reportPath: string | null;
}
export declare class RoutineAlreadyRunningError extends Error {
    constructor(jobName: string, runId: string);
}
/**
 * Consecutive trailing `skipped`/`active_run` records — a routine that keeps
 * being refused because a prior run "still owns" the slot. After the slot is
 * released correctly on terminal states (see {@link activeRoutineRun}) this can
 * only accumulate for a genuine long-overlap or a new wedge; either way it means
 * the routine has stopped firing on schedule and should be surfaced, not left to
 * pile up silently. `runs` is oldest→newest (listRuns sorts by run id).
 */
export declare function activeRunSkipStreak(runs: RunMeta[]): number;
/**
 * The pid to stamp on a launcher's provisional active claim, or null when the
 * launcher is this box's routines daemon.
 *
 * The claim pid exists so a launcher that dies between claiming the slot and
 * spawning the child releases the slot quickly (`isPidOurs` goes false) instead
 * of wedging it for the full timeout. That fast recovery only helps a SHORT-LIVED
 * foreground launcher (`agents routines run`): the daemon never dies between
 * claim and spawn, and recording ITS pid is actively harmful — the daemon
 * outlives every run, so `isPidOurs` never goes false and a claim finalized to a
 * terminal state before its child spawned kept the slot and drew
 * `reapTerminalRoutineProcesses` at the daemon's own process group on every tick
 * (RUSH-2640, requirement: never record the daemon's own pid as a run's pid). So
 * the daemon records no claim pid; the timeout window bounds a daemon that
 * crashes mid-claim instead.
 */
export declare function launcherClaimPid(): number | null;
/** How a routine attempt was triggered, plus the schedule slot it belongs to. */
export interface RoutineTrigger {
    kind: NonNullable<RunMeta['triggerKind']>;
    /** UTC fire time for a schedule/catchup attempt; keys the single-fire slot claim. */
    scheduledFor?: Date | string;
}
/** A claimed attempt: the run id to use, plus the RunMeta fields to stamp on it. */
interface RoutineAttempt {
    runId: string;
    stamp: Partial<Pick<RunMeta, 'triggerKind' | 'scheduledFor' | 'project' | 'requestedCwd' | 'resolvedCwd'>>;
}
/**
 * Working directory for a routine's LOCAL child, resolved from its explicit
 * `project`/`cwd` execution anchor via {@link resolveJobExecutionContext} — never
 * inferred from `repo` (which is external repository identity only) and never the
 * daemon's launch cwd. A command routine with neither field lands in `$HOME`
 * (housekeeping); an unresolved/blocked agent context also falls back to `$HOME`
 * so a caller that reaches this (past the readiness gate) has a valid directory,
 * but the gate should have paused such a routine before it ever spawned.
 */
export declare function routineSpawnCwd(config: Pick<JobConfig, 'name' | 'project' | 'cwd' | 'agent' | 'workflow' | 'command'>): string;
/**
 * Bake the daemon-job argv skeleton from AGENT_COMMANDS.
 *
 * The two tables used to be independent copies of the same launch decision.
 * Argv now comes from AGENT_COMMANDS.base / promptFlag / jsonFlags / modeFlags.
 * Two documented exceptions, not a second table:
 *   - kimi uses `--prompt` (long form). `-p` is the exec alias; combining
 *     either with --plan/--auto/--yolo aborts, so routineModeArgs emits no
 *     mode flag. The daemon has always spawned the long form.
 *   - claude places `--verbose` (from jsonFlags) before the prompt and bakes
 *     modeFlags.plan so claudeAdapter.routineModeArgs can splice
 *     plan → acceptEdits / auto / skip.
 *
 * Returns undefined when `agent` is outside ROUTINE_AGENT_IDS (gemini, grok,
 * and every other harness the daemon does not fire locally).
 */
export declare function bakeRoutineArgv(agent: string): string[] | undefined;
/** Build the full CLI argv for executing a job, applying mode, model, and permission flags. */
export declare function buildJobCommand(config: JobConfig, resolvedPrompt: string, forwardAccount?: boolean): string[];
/**
 * Record every transcript file already present in the child's transcript dirs BEFORE
 * the run spawns. When the transcript lands in a shared per-version home, this baseline
 * is what lets the archiver copy only the file THIS run produced instead of sweeping in
 * every sibling session that home holds (which would mis-tag them all `origin='routine'`
 * under this run's name). Called once per run, before spawn.
 */
export declare function snapshotRoutineTranscriptBase(meta: Pick<RunMeta, 'jobName' | 'agent' | 'version'>, runDir: string, overlayHome?: string): void;
export declare function archiveRoutineTranscripts(meta: Pick<RunMeta, 'jobName' | 'runId' | 'agent' | 'version'>, runDir: string, overlayHome?: string): void;
/** Pre-flight version/account selection for a routine job. */
export interface RoutineLaunchPlan {
    /** Ordered attempts: primary first, then same-agent failover accounts. */
    chain: FallbackEntry[];
    /** Full rotation result when strategy selected among healthy accounts; null when pinned. */
    rotation: RotateResult | null;
    /** True when `config.version` pinned the target (no rotation). */
    pinned: boolean;
    /** False when `account` names a harness-native login rather than a durable credential. */
    forwardAccount?: boolean;
}
/** Claude's own local auth check; unlike account metadata it cannot mistake identity for a usable login. */
export declare function claudeVersionIsAuthenticated(version: string): boolean;
/**
 * Resolve the version/account chain for a routine the same way `agents run`
 * does: honor an explicit `version:` pin; otherwise use the configured run
 * strategy (default `balanced`) so credit-exhausted / rate-limited accounts
 * are skipped pre-flight, and synthesize a same-agent failover chain from the
 * other healthy accounts for mid-run rate limits.
 *
 * Workflows are left alone — `agents run <workflow>` owns selection.
 */
export declare function resolveRoutineLaunch(config: JobConfig, cwd?: string, deps?: {
    resolveRunVersion?: typeof resolveRunVersion;
    resolveAccountVersion?: typeof resolveAccountVersion;
    findCredentialAccount?: (name: string) => boolean;
    readMeta?: typeof readMeta;
    resolveCredentialAccount?: (name: string, host: AgentId) => {
        env: Record<string, string>;
    };
    claudeVersionIsAuthenticated?: typeof claudeVersionIsAuthenticated;
}): Promise<RoutineLaunchPlan>;
/**
 * Rewrite `cmd[0]` to the absolute binary for `agent@version` when installed.
 * Bypasses the bare-name shim so a sandboxed HOME / missing default pin cannot
 * surface as "agents: no version of X configured".
 */
export declare function pinJobBinary(cmd: string[], agent: AgentId, version: string | undefined): string[];
/**
 * Whether a job's command is dispatched through `agents run` (so `cmd[0] === 'agents'`)
 * rather than the agent binary directly. True for workflow jobs and for resume jobs.
 * Such commands must NOT be binary-pinned (pinning rewrites cmd[0] to the agent binary,
 * producing a broken `<binary> run …`) and must not receive a version-pinned spawn env.
 */
/**
 * Assert a routine's account can be dispatched to the resolved placement, BEFORE
 * any off-box dispatch (placement is resolved before {@link resolveRoutineLaunch}),
 * at the top of both the foreground and detached paths:
 *
 * - **native** account → rejected for host AND cloud: a native login is a
 *   device-local harness credential that cannot be forwarded off-box.
 * - **provider** account + **cloud** → rejected (fail loud): the cloud dispatch
 *   has no secure way to inject a device-local provider bundle yet.
 * - **provider** account + **host** → allowed: the host dispatch forwards the
 *   account NAME (the remote resolves its own local bundle — no secret copied).
 *
 * `account` is injectable so the guard is unit-tested for both modes without a
 * registry or a real dispatch.
 */
export declare function assertRoutineAccountLocalForPlacement(config: Pick<JobConfig, 'name' | 'account'>, mode: 'host' | 'cloud', deps?: {
    account?: import('../account-registry.js').UnifiedAccount | null;
    readMeta?: typeof readMeta;
}): Promise<void>;
export declare function dispatchPlacedJob(config: JobConfig, target: import('../routines-placement.js').PlacementTarget, attempt: RoutineAttempt, deps?: {
    account?: import('../account-registry.js').UnifiedAccount | null;
    host?: typeof executeJobOnHost;
    cloud?: typeof executeJobOnCloud;
}): Promise<RunResult | undefined>;
/**
 * Build the options passed to `dispatchPromptToHost` for a host routine. Split
 * out so the execution boundary is unit-testable: it MUST forward the routine's
 * `account` by name (the remote resolves its own local bundle; no secret copied)
 * — dropping it silently ran the remote under the wrong identity.
 */
export declare function buildHostDispatchOptions(config: JobConfig, ctx: {
    remoteCwd: string | undefined;
    runDir: string;
    detached: boolean;
}): import('../hosts/run-target.js').HostPromptRun;
export declare function dispatchesViaAgentsRun(config: Pick<JobConfig, 'workflow' | 'resume' | 'agent'>): boolean;
/**
 * Merge sandbox/base env with the canonical per-version exec env
 * (CLAUDE_CONFIG_DIR / CODEX_HOME / …) so routines share account isolation
 * with `agents run`.
 */
export declare function buildRoutineSpawnEnv(baseEnv: Record<string, string>, agent: AgentId, version: string | undefined, timezone?: string, overlayHome?: string): Record<string, string>;
export declare function executeJob(config: JobConfig, deps?: LoopDeps, trigger?: RoutineTrigger): Promise<RunResult>;
/**
 * Dispatch a routine to the agent's native cloud provider (or the configured
 * default). Writes a local run record with `cloudTaskId` so list/runs still
 * work; does not wait for cloud completion on the detached path.
 */
declare function executeJobOnCloud(config: JobConfig, opts: {
    detached: boolean;
}, attempt: RoutineAttempt): Promise<RunResult>;
declare function executeJobOnHost(config: JobConfig, opts: {
    detached: boolean;
}, attempt: RoutineAttempt): Promise<RunResult>;
/**
 * Optional lifecycle callbacks for a detached routine run. The daemon passes an
 * `onFinish` that fires the branded finish/output notification (RUSH-2030) — it
 * runs from the in-process settle() when the child exits, the only seam that
 * observes the live running→terminal transition (the monitor tick would already
 * see a finalized record and skip it). Never let a hook throw into finalization.
 */
export interface RoutineHooks {
    /** Called once with the finalized meta when the run reaches a terminal state. */
    onFinish?: (meta: RunMeta) => void;
}
/** Spawn a job as a detached process and return immediately with run metadata. */
export declare function executeJobDetached(config: JobConfig, hooks?: RoutineHooks, trigger?: RoutineTrigger): Promise<RunMeta>;
/** Extract the final assistant message from a stream-JSON log file as a markdown report. */
export declare function extractReport(stdoutPath: string, agentType: AgentId): string | null;
/** Derive the final status of a detached run by reading the agent's stream-json
 *  tail. Detached children fire-and-forget, so we never see their exit code
 *  directly — but Claude's stream-json terminates with a `type: result` line
 *  that carries `is_error`. If we find it, the run completed cleanly (modulo
 *  agent-reported error). If not, the process likely died mid-stream and the
 *  caller should treat the run as failed. */
export declare function inferFinalStatusFromLog(stdoutPath: string, agent: AgentId): {
    status: 'completed' | 'failed';
    exitCode: number;
} | null;
/**
 * PIDs of the in-flight detached routine children on THIS device — every local
 * run record still marked `running` whose spawned process is genuinely alive
 * (`isPidOurs`, so a dead-and-reused pid does not count). These are the `unref`'d
 * spawns that survive a daemon exit in their own process group (SING-11a): a
 * takeover must not kill them and `stopDaemon` reports them rather than pretending
 * the process tree is clean (SING-12). Scoped to `getRunsDir()`, which is under
 * this state dir's HOME, so a different state dir's children are invisible here.
 * `host:`-placed runs have no local pid and are excluded.
 */
export declare function listLiveRoutineChildren(): number[];
/**
 * Whether a run record marked `running` is genuinely in flight right now. A
 * `running` status alone is not enough: `writeActiveClaim` stamps a provisional
 * claim as `running` with `pid: null` BEFORE the child spawns, and
 * `monitorRunningJobs()` does not reap a null-pid record (it has no process to
 * probe, and ages it out only past the configured timeout — up to a week). So a
 * daemon crash in that spawn window leaves a `running` record for a run that is
 * not running (RUSH-2640). A caller answering "is one running now?" MUST gate on
 * this, mirroring `listLiveRoutineChildren`'s local-liveness test:
 * - a local child is in flight only while its pid is still ours (alive, same
 *   birth time — a dead-and-reused pid does not count);
 * - a `host:`-placed run has no local pid by design and is in flight until its
 *   remote `.exit` is reconciled (`finalizeHostRun`).
 */
export declare function isRunGenuinelyInFlight(meta: RunMeta): boolean;
/**
 * Scan all runs marked "running" and finalize any whose process has exited.
 *
 * SYNCHRONOUS — for OFF-loop callers only (the `agents routines list/status`
 * builders and CLI best-effort orphan reaps), which run in short-lived CLI
 * processes where a bounded synchronous `ps`/scan cannot starve a served event
 * loop. The daemon's own heartbeat tick MUST use {@link reapExitedRunningJobs}
 * instead — a sync scan there would freeze the shared event loop (PHNX-3695).
 */
export declare function monitorRunningJobs(): void;
/**
 * Async, non-blocking twin of {@link monitorRunningJobs} for the daemon's
 * heartbeat tick (PHNX-3695). Identical reconciliation via
 * {@link reconcileRunningRecord}, but the directory walk uses `fs/promises` and
 * the pid-identity probe uses the deadline-bounded {@link isPidOursAsync}, so
 * the every-tick scan never freezes the shared event loop — the freeze that
 * left the browser IPC `version` probe unanswered.
 */
export declare function reapExitedRunningJobs(): Promise<void>;
export {};
