import type { ChildProgressEvent, ChildResult } from "../core/types.js"; import { type IsolatedChildArgsInput } from "./args.js"; import { type SpawnFn } from "./spawn.js"; /** * B3 soft-limit wrap-up message, written to the steer file when the assistant * turn count reaches `maxTurns` (benchmark tintinweb agent-runner.ts:903-920: * "Wrap up immediately" — a steer, never a kill). */ export declare const WRAP_UP_STEER_MESSAGE = "You have reached your turn limit. Wrap up immediately \u2014 provide your final answer now."; /** Input to `resolveTurnLimits` (raw per-dispatch options). */ export interface TurnLimitsInput { maxTurns?: number; graceTurns?: number; } /** Resolved B3 turn limits: `maxTurns` undefined = unlimited. */ export interface TurnLimits { maxTurns?: number; graceTurns: number; } /** * Resolve per-dispatch B3 turn limits against the shared bounds: * - maxTurns: non-positive/invalid = UNLIMITED (undefined); else clamped to * [HARD_MIN_MAX_TURNS, HARD_MAX_MAX_TURNS] (1..200); * - graceTurns: default 5, floored at 1, capped at 20 (grace is never 0 — * the child always gets at least one turn to wrap up after the steer). */ export declare function resolveTurnLimits(input: TurnLimitsInput): TurnLimits; export interface LaneDispatchInput { task: string; model?: string; thinking?: string; tools?: string; agentPrompt?: string; extensions?: IsolatedChildArgsInput; /** * Explicit session path override. When set, dispatch uses this exact path * instead of the lane's stable session path (continue reuses the original * run's session file — pi resumes the session AS-IS, no offset flag). */ sessionPath?: string; /** * B3 graceful turn limit: max assistant turns (ndjson message_end count) * before the soft wrap-up steer. Undefined/non-positive = unlimited. * Bounded to 1..200 by `resolveTurnLimits`. */ maxTurns?: number; /** * B3 grace turns after `maxTurns` before the hard abort. Default 5, min 1, * max 20 (the child always gets at least one turn to wrap up). */ graceTurns?: number; /** Run id naming the `.steer` file written at the soft limit. */ runId?: string; /** Directory receiving the steer file (both runId + steerDir required). */ steerDir?: string; /** Custom wrap-up message (default WRAP_UP_STEER_MESSAGE). */ wrapUpMessage?: string; /** * C6 worktree isolation: cwd override for the child process (the disposable * git worktree). Defaults to the pool cwd. The session file stays in the * pool's sessionDir (parent side). */ cwd?: string; /** * Streaming child progress feed: fired per assistant turn (message_end), * per captured child toolCall (`name(args)` summary) and per completed * assistant text. runId/agent are filled by the pool from the dispatch * input. In-memory only — never persisted to ledger/attestations. */ onChildEvent?: (event: ChildProgressEvent) => void; } export interface LanePoolOptions { cwd?: string; /** Command resolved for the child (default "pi"). */ piCommand?: string; /** Injectable spawner (I11). */ spawn?: SpawnFn; /** Child env (e.g. buildChildEnv output). Optional. */ env?: NodeJS.ProcessEnv; /** Max concurrent one-shot tasks (backpressure). Default 4. */ maxParallel?: number; /** Session dir for stable per-lane session files. Default /.pi/agent-sessions. */ sessionDir?: string; /** Fallback file stem when a lane id sanitizes empty. Default "subagent". */ agentName?: string; /** Abort grace between SIGTERM and SIGKILL in ms. Default 5000. */ killGraceMs?: number; /** * Optional model probe hook. NEVER awaited on the critical path: it is fired * fire-and-forget per dispatch (off the critical path), as the spike requires. */ probe?: (model: string, lane: string) => Promise | void; /** Injectable clock for deterministic idle tracking. */ now?: () => number; } export interface LaneInfo { id: string; sessionPath: string; createdAt: number; lastUsedAt: number; active: boolean; } export declare const DEFAULT_MAX_PARALLEL = 4; export declare const DEFAULT_AGENT_NAME = "subagent"; export declare class LanePool { private readonly cwd; private readonly piCommand; private readonly spawn; private readonly env?; private readonly maxParallel; private readonly sessionDir; private readonly agentName; private readonly killGraceMs?; private readonly probe?; private readonly now; private readonly lanes; private readonly activeLanes; private readonly inFlight; private readonly pendingResolvers; private activeCount; private closed; constructor(options?: LanePoolOptions); /** * Get-or-create the STABLE session path for a lane. Reused across a chain of * tasks on the same lane => context continuity via the same session file. */ sessionPathFor(lane: string): string; /** True if a lane currently has an in-flight task. */ isLaneActive(lane: string): boolean; /** Snapshot of registered lanes and their idle/active state. */ laneInfo(): LaneInfo[]; get laneCount(): number; get activeCountValue(): number; /** * Dispatch a single task as a one-shot spawn on the given lane, reusing the * lane's stable session path. Returns the parsed ChildResult. The model probe * (if configured) is fired off the critical path and never blocks spawn. */ dispatch(lane: string, input: LaneDispatchInput): Promise; private acquireSlot; private releaseSlot; private runOneShot; /** * Remove lanes that have been idle for at least `ttlMs` and have no in-flight * task. Their stable session paths are forgotten (next dispatch creates a * fresh one). Returns the removed lane ids. */ closeIdle(ttlMs: number): string[]; /** Abort every in-flight task and mark the pool closed (bounded kill via signal). */ closeAll(): void; get isClosed(): boolean; }