/** Path to the stdio stub the CLI spawns. Resolved once — it ships inside the package. */ export const STUB_PATH: string; /** The MCP server name the CLI sees; tool names reach the model as `mcp__bareagent__`. */ export const SERVER_NAME: "bareagent"; /** Mirrors the Loop's own BA-11 / BA-12 defaults so native mode is not quietly laxer. */ export const DEFAULT_MAX_CONSECUTIVE_DENIALS: 3; export const DEFAULT_MAX_IDENTICAL_TOOL_ERRORS: 3; /** * Stable key for BA-12's identical-tool-error guard. NARROWEST trigger by design: only a * BYTE-IDENTICAL repeat counts. Counting any consecutive failure was measured to punish the * recovery it exists to protect (a model varying args while working through an ENOENT). * @param {string} name * @param {any} args * @returns {string} */ export function toolErrorKey(name: string, args: any): string; /** * Clamp a diagnostic built from a thrown value of unknown shape before it can ride into a * `receipts`/audit string. A non-Error throw serialized wholesale has previously pushed a secret * into a plaintext audit log (the F16/BA-1 capture class), and an append-only log that captures a * key captures it forever. Conventional fields only, hard length cap, never the whole object. * @param {any} err * @returns {string} */ export function safeErrorText(err: any): string; /** * The parent-side bridge: a unix-socket server backed by the caller's in-process tool closures, * with the gate and both spin guards wired at the one seam that sees every call. * * Unix socket, not loopback TCP: a library must not open a listening network port as a side effect * of running a tool loop. The socket lives in a 0700 directory and is itself chmod 0600. * * @param {object} opts * @param {import('../types').ToolDef[]} opts.tools - the caller's tools (with live `execute` closures). * @param {Function|null} [opts.policy] - the SAME contract as `Loop({policy})`: only `true` allows; * a string is the deny reason fed back verbatim. * @param {any} [opts.ctx] - opaque governance ctx, forwarded to `policy` unchanged. * @param {number} [opts.maxConsecutiveDenials] * @param {number} [opts.maxIdenticalToolErrors] * @returns {Promise<{sockPath: string, state: any, close: () => void}>} */ export function createBridge({ tools, policy, ctx, maxConsecutiveDenials, maxIdenticalToolErrors }: { tools: import("../types").ToolDef[]; policy?: Function | null | undefined; ctx?: any; maxConsecutiveDenials?: number | undefined; maxIdenticalToolErrors?: number | undefined; }): Promise<{ sockPath: string; state: any; close: () => void; }>; /** * @param {string|null|undefined} subtype * @returns {{stopReason: string|null, error: string|null}} */ export function classifySubtype(subtype: string | null | undefined): { stopReason: string | null; error: string | null; }; /** * Decide the session's terminal tag. Pure, so the precedence is testable without a live CLI. * * The ordering is the whole point: * 1. `terminal` — a guard tripped and WE killed the session; most specific, and it explains why * the CLI's own subtype is missing or odd. * 2. bridge broken — a tool call the CLI ATTEMPTED but the bridge never SERVED never reached the * caller's closure. This must outrank a reported `success`, because a session * whose tools were all broken still ends `subtype:'success'` — the model writes * a tidy final answer explaining that its tools failed, and mapping that onto * `error:null` reports a run in which nothing worked as converged. * 3. `timedOut` — we killed it on the wall clock. * 4. the subtype — what the CLI itself said. * * @param {{terminal: string|null, bridgeDown: boolean, attempted: number, served: number, timedOut: boolean, subtype: string|null|undefined}} facts * @returns {{stopReason: string|null, error: string|null}} */ export function resolveSessionError(facts: { terminal: string | null; bridgeDown: boolean; attempted: number; served: number; timedOut: boolean; subtype: string | null | undefined; }): { stopReason: string | null; error: string | null; }; /** * Line-oriented processor for the CLI's `stream-json` stdout. * * ONE TURN IS ONE ASSISTANT MESSAGE, NOT ONE EVENT (BA-17). Measured on the real wire: the CLI * emits a SEPARATE `assistant` event per content BLOCK of the same message, and every one of them * repeats that message's `usage` verbatim — one 13-block message arrived as 13 events carrying the * same numbers. Treating each event as a turn is wrong on both axes a caller meters: * - the TURN axis — a caller whose attempt bound is an LLM-turn count sees 14 "turns" for 2 real * ones (measured 7×; 4.4× on the adopter's failing run), so its net guillotines * the session at a fraction of the allowance it advertised; * - the TOKEN axis — the same message's usage is added once per block (measured 5.04× inflated * against the CLI's own session total), so a budget cap fires early on tokens * that were never spent. * So a RUN of consecutive events sharing `message.id` is ONE turn: usage is recorded once and one * `onTurn` fires. Adjacent-run dedup, not a Set — an id that somehow recurred later must still count * as a new turn (dropping a real turn is the failure that matters). An event with NO id degrades to * one-turn-per-event: the pre-BA-17 behaviour, never a collapse of the whole session into one turn. * * FRAMING IS SYNCHRONOUS. The obvious shape — an `async` stdout handler that `await`s `onTurn` * inside its parse loop — has a data-corruption bug: while the `await` is suspended, the next * `'data'` event re-enters the handler and mutates the SHARED line buffer, so the suspended parse * resumes against a buffer that moved under it (dropped/duplicated/mangled lines). It is invisible * with a synchronous `onTurn` (the await resolves on the microtask queue before the next macrotask * `'data'` event) and REACHABLE the moment `onTurn` does real async work — e.g. a wired gate's * `gate.record`, which is exactly the intended consumer. So framing never awaits: it parses lines * synchronously and pushes per-turn forwards onto a queue that a SERIAL async drainer empties in * arrival order. Extracted from `runSession` so this can be unit-tested without spawning the CLI. * * @param {object} o * @param {Function|null} o.onTurn * @param {any} o.ctx * @param {number} o.startedAt * @param {(err: Error) => void} o.onHalt - called if a forwarded `onTurn` throws a HaltError. * @param {number|null} [o.turnCap] - BA-17 backstop: fires `onLimit` the moment a turn BEYOND this * many is observed. Deliberately not `>=`: `--max-turns` is passed to the CLI too, and when it * works (measured: it does, and it counts assistant turns) the CLI ends the session ITSELF at the * cap and emits its `result` event — which is the only place the authoritative session cost * arrives. Killing the session at exactly N would throw that figure away on every bounded run. So * this fires ONLY on an overrun, i.e. only if the flag ever stops working — it is undocumented in * `claude --help`, so the guarantee cannot rest on it alone. * @param {(() => void)} [o.onLimit] - called once when `turnCap` is exceeded. */ export function createSessionStream({ onTurn, ctx, startedAt, onHalt, turnCap, onLimit }: { onTurn: Function | null; ctx: any; startedAt: number; onHalt: (err: Error) => void; turnCap?: number | null | undefined; onLimit?: (() => void) | undefined; }): { /** Feed a stdout chunk. Pure synchronous framing — never awaits. */ feed(chunk: Buffer | string): void; /** Await every queued per-turn forward — call before resolving the session. */ flush(): Promise; readonly turns: import("../types").Usage[]; /** Assistant TURNS observed — including any that carried no usage. */ readonly turnCount: number; /** The last turn's text. The work a bounded/guard-stopped session still did (BA-5). */ readonly lastText: string; readonly attempted: number; readonly final: any; }; /** * Run ONE native CLI session to completion, streaming per-turn usage as it arrives. * * @param {object} opts * @returns {Promise} */ export function runSession(opts: object): Promise;