/** * One autopilot pass: move every in-flight backlog item one step along * build → PR → CI → merge → release, and start new builds * (autopilot-driver/AC-1..AC-18, ported from `scripts/autopilot/driver.mjs` by * autopilot-on-demand/AC-16). * * A sequencer, not an agent: the backlog order is `aidlc roadmap next`, the work * is an agent following the unattended rule, the release is the configured * release command. It decides nothing a person has not already decided, and * stops itself on the rules below — including where a person asked it to wait: * after a paused phase, and at the pull request (autopilot-stop-controls). * * Every effect goes through `ctx` so the whole loop runs in tests with no git * remote, no GitHub and no agent. * * @module */ import { type EffectiveRates } from '../cost/rates.js'; import { type Host } from '../core/host.js'; import type { Notice } from './notify.js'; import type { AutopilotConfig, AutopilotItem, AutopilotPaths, AutopilotState, ItemStep, TypedLaunch } from './state.js'; import { type PausePhase, type Stops } from './stops.js'; export interface RunResult { status: number; stdout: string; stderr: string; dry?: boolean; } /** How a spawned agent ended, as seen by the process that spawned it. */ export interface AgentExit { /** null when a signal ended it. */ code: number | null; signal: string | null; /** From spawn to exit. */ ms: number; /** At most 4 KB of what this launch wrote to the item's log — never an earlier launch's (codex-preset-removed-flag/AC-8). */ output: string; /** The spawn's error, when the command never started (agent-binary-missing-crashes-loop/AC-1). */ error?: string; } /** * A value `process.kill` reads as one process. It reads 0 as this process's group and * -1 as every process the user may signal, so a pid that is not a positive integer * reaches no signal (agent-binary-missing-crashes-loop/AC-7). */ export declare function isPid(pid: number | null | undefined): pid is number; export interface DriverCtx { /** Primary checkout — where merges and releases happen. */ repo: string; /** argv prefix that runs this CLI, e.g. `[node, /path/cli.js]`. */ cli: string[]; paths: AutopilotPaths; config: AutopilotConfig | null; dryRun: boolean; now(): number; say(line: string): void; run(cmd: string, args: string[], opts?: { cwd?: string; env?: Record; }): RunResult; spawnDetached(argv: string[], opts: { cwd: string; env: Record; logFile: string; }): number; isAlive(pid: number): boolean; /** A just-spawned pid's identity, for `pause` to match later; null when it cannot be read. */ identify?(pid: number): { key: number; boot: string | null; ns: string | null; } | null; /** * How `pid` ended, or null when this process did not spawn it or it has not ended. * Absent is the same as null: the pass cannot tell (codex-preset-removed-flag/AC-11). */ exited?(pid: number): AgentExit | null; kill(pid: number): void; /** * Whether the agent command can be found where its spawn will look. Absent reads as * found: the pass cannot tell (start-failure-leaves-branch/AC-9). */ findsCommand?(cmd: string): boolean; fs: { read(path: string): string | null; exists(path: string): boolean; write(path: string, text: string): void; /** * Bytes in `path`; 0 when it does not exist. This and the two below are optional: * without them a typed launch has no readable result and is handled as before * (autopilot-reads-agent-result/AC-15, AC-32). */ size?(path: string): number; /** At most `max` bytes of `path` from `offset`; empty when unreadable. */ readRange?(path: string, offset: number, max: number): Buffer; append?(path: string, text: string): void; }; /** The effective rate table for the primary checkout. Absent: Codex usage is unpriced (AC-21). */ rates?(): EffectiveRates; /** The model a Codex thread used, read from its rollout; null when it cannot be read (AC-20, AC-21). */ codexModel?(threadId: string, launchedAt: string): string | null; /** * Tell a person something needs them or is done. Absent sends nothing: only a * background run sets it (autopilot-live-progress/AC-20, AC-23). */ notify?(notice: Notice): void; } /** How many builds a run may still start, and what to tell it (autopilot-on-demand/AC-7). */ export interface Budget { /** null = unlimited (`--all`). */ remaining: number | null; onStart(name: string): void; /** An item left the driver: released, or held. */ onFinish(name: string): void; } /** Agent relaunches to resolve conflicts with main before an item is parked (autopilot-merge-lane-stalls-on-conflicts/AC-11). */ export declare const CONFLICT_FIX_LIMIT = 2; export declare class RateLimited extends Error { } /** * How the item's change request is named: `pull request #7`, or `merge request !7` on * GitLab, in messages, notices and prompts alike (autopilot-gitlab-merge-requests/AC-17). */ export declare function ref(host: Host, n: number | null): string; /** * @param pauses - `AIDLC_PAUSE_AFTER` as the agent will see it. Empty is said in words, * because the rule reads a prompt silent on the list as "nobody told me" and pauses * everywhere (scoped-preset-cannot-read-unattended-env/AC-2). */ export declare function agentPrompt(item: { name: string; }, pauses?: string): string; export declare function fixPrompt(item: { name: string; pr: number | null; }, failing: string[], host?: Host): string; export declare function conflictPrompt(item: { name: string; pr: number | null; }, host?: Host): string; /** The stops an item is held to: those recorded when it started (autopilot-stop-controls/AC-6). */ export declare function itemStops(item: AutopilotItem): Stops; /** * A non-zero exit sooner than this after launch is a launch failure, not a failed * build. Argument and startup errors exit in well under a second; a session that * fails later prints a banner first, which names nothing (codex-preset-removed-flag/AC-6, AC-10). */ export declare const LAUNCH_FAILURE_MS = 30000; /** * The first non-blank line of `text`, terminal escapes and control characters removed, * at most 200 characters; null when there is none (codex-preset-removed-flag/AC-12). * The first line, not `fail`'s last: for an argument error the last is the usage line. */ export declare function firstOutputLine(text: string): string | null; /** * The reset time of the latest session-limit wait still in force, or null. While there is * one, no agent is launched for any item: the limit is the account's, so every launch * would hit it (autopilot-reads-agent-result/AC-26). */ export declare function launchBlocked(state: AutopilotState, now: number): string | null; /** * The instance's `current_phase` in its worktree, or null when unreadable. Shared with the * view. Only a phase-shaped name: the agent writes this file, and the phase reaches park * reasons committed to the roadmap (autopilot-reads-agent-result/AC-29). */ export declare function instancePhase(read: (path: string) => string | null, item: Pick): string | null; /** * What a Codex launch's summed `turn.completed` usage costs, in micro-USD, priced as the * Codex cost provider prices a turn — cached input counted inside input, not on top of * it — at the effective rate for its model; null when it cannot be priced * (autopilot-reads-agent-result/AC-20, AC-21). A resumed launch's usage is the thread's * running total, not its own turns', so the figure is the thread's cost to date; callers * subtract what is on record for the thread (codex-resume-double-counts/AC-1). */ export declare function launchUsageMicro(ctx: Pick, launch: TypedLaunch): number | null; /** * Return every item whose failure is resumable to the step it failed in, and * clear the error. Called when a run starts: asking to run is asking to retry * (autopilot-merge-lane-stalls-on-conflicts/AC-15). Returns the names resumed. */ export declare function resumeFailed(state: AutopilotState): string[]; /** * Return one errored item to the step it resumes from, as a new run does; false when it is * not errored or cannot resume. Shared with the watcher's retry answer, so the two cannot * resume differently (autopilot-watcher-asks-person/AC-24). */ export declare function resumeItem(state: AutopilotState, item: AutopilotItem): boolean; /** * Stop tracking an item finished or abandoned by hand. Touches only autopilot's * own state — no roadmap move, claim, branch or worktree (AC-17). Returns the * removed item, or null when none has that name (AC-18). */ export declare function forgetItem(state: AutopilotState, name: string): AutopilotItem | null; export type ApproveOutcome = { ok: true; item: AutopilotItem; phase: PausePhase; } | { ok: false; reason: string; }; /** * Approve the phase an item is waiting on. Touches only autopilot's own state; the * next pass carries the item on (autopilot-stop-controls/AC-16, AC-17). */ export declare function approveItem(state: AutopilotState, name: string, host?: Host, /** Who approved in the watching session, and when; absent for an approval at a terminal. */ watcher?: { by: string; at: string; }): ApproveOutcome; /** One pass. Returns the new state; the caller saves it (not in a dry run). */ export declare function tick(ctx: DriverCtx, state: AutopilotState, budget?: Budget): AutopilotState; /** Each item's step by name, to compare against after a change (autopilot-live-progress/AC-11). */ export declare function stepsOf(state: AutopilotState): Map; /** * Stamp `step_since` on every item whose step differs from `before`; a new item has * no entry there and is stamped too. Diffing here, rather than at each of the * fourteen places a step is set, means a new handler cannot forget it. */ export declare function markStepChanges(state: AutopilotState, before: Map, now: number): void; /** * The driver's line, shared by `status` and the watch view. * * @param stopped - whether the STOP file exists. * @param lastPass - how to show the last pass's time; `status` shows it as stored. */ export declare function driverLine(stopped: boolean, state: AutopilotState, now: number, lastPass?: (iso: string) => string): string; export declare function queueLine(state: AutopilotState): string | null; export declare function statusLines(ctx: Pick, state: AutopilotState, host?: Host): string[]; //# sourceMappingURL=driver.d.ts.map