@juspay/neurolink
Version:
The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002
111 lines (110 loc) • 5.75 kB
TypeScript
/**
* Background commands (N4) — run a command detached, bank every byte it
* writes, monitor it while it runs.
*
* A reviewing agent has to run real commands — a build, a test suite, a linter
* whose output IS the evidence for a finding — and the naive shapes both fail.
* `bashTool` blocks the loop, hands the model a shell, and truncates its own
* output at 100 KB. A `child_process` call with a string command is a shell
* injection with extra steps.
*
* This module keeps three promises instead:
*
* - **Detached.** `startBackgroundCommand` returns a `taskId` immediately; the
* agent keeps working and asks about the command when it wants to.
* - **Nothing discarded.** Both streams are written straight to files as they
* arrive and the COMPLETE files are banked as artifacts (N3) when the
* command settles. The conversation gets a bounded tail plus a read-back
* call. The single bound is `maxOutputBytes`, and reaching it is a state
* (`output-limit`) the caller can see, not a silent cut.
* - **Hardened by contract.** argv arrays with `shell: false`, an exact-match
* executable allowlist, a cwd that must resolve through symlinks inside a
* declared root, and a timeout that escalates SIGTERM → SIGKILL.
*
* Completion reaches the model the same way a delegate's does (N2.3): the
* `running` / `finished` counters ride on every command tool result and — via
* the checklist — on every `tasks_list`. The core generate loop is untouched.
*
* @module agent/backgroundCommands
*/
import type { NeuroLink } from "../neurolink.js";
import type { BackgroundCommandCounts, BackgroundCommandHandle, BackgroundCommandOptions, BackgroundCommandOutputPage, BackgroundCommandPageRequest, BackgroundCommandPolicy, BackgroundCommandStatus, MCPExecutableTool } from "../types/index.js";
/**
* Declare what this host may execute. There is no default: until this is
* called, every start is refused, because "run whatever the model asks" is not
* a defensible default for a primitive that spawns processes.
*/
export declare function setBackgroundCommandPolicy(host: NeuroLink, policy: BackgroundCommandPolicy): void;
/** The policy in force for a host, or undefined when none was declared. */
export declare function getBackgroundCommandPolicy(host: NeuroLink): BackgroundCommandPolicy | undefined;
/**
* Commands for a host's session: `running` have not settled, `finished` have.
* Carried on every command tool result and on every `ChecklistToolResult`, so
* the model learns a build finished without polling for it.
*/
export declare function backgroundCommandCounts(host: NeuroLink, sessionId?: string): BackgroundCommandCounts;
/**
* Start a command in the background and get its task id immediately.
*
* @throws when no policy is set, argv is malformed, the executable is not
* allowlisted, the policy vetoes the command, or the cwd escapes the
* sandbox root. Every message names its own recovery step.
*/
export declare function startBackgroundCommand(host: NeuroLink, argv: string[], options: BackgroundCommandOptions): Promise<BackgroundCommandHandle>;
/**
* Everything known about one command right now — synchronous, because the
* job state is live and a monitor that has to be awaited is a monitor nobody
* calls mid-loop.
*
* @throws when the task id is unknown to this host
*/
export declare function getBackgroundCommandStatus(host: NeuroLink, taskId: string): BackgroundCommandStatus;
/**
* Wait for a command to settle.
*
* `timeoutMs` bounds the WAIT, not the command: when it elapses the current
* (still running) status is returned rather than throwing, so a caller can
* poll in bounded steps without ever losing the job.
*
* @throws when the task id is unknown to this host
*/
export declare function awaitBackgroundCommand(host: NeuroLink, taskId: string, opts?: {
timeoutMs?: number;
}): Promise<BackgroundCommandStatus>;
/**
* Kill a running command: SIGTERM (or the signal you name), SIGKILL five
* seconds later if it is still there. Resolves with the settled status, so a
* caller never has to guess whether the output was banked yet.
*
* Killing an already-settled command is a no-op that returns its status —
* the outcome is not discarded.
*
* @throws when the task id is unknown to this host
*/
export declare function killBackgroundCommand(host: NeuroLink, taskId: string, signal?: NodeJS.Signals): Promise<BackgroundCommandStatus>;
/**
* Kill every unsettled command this host started. Host lifecycle only
* (`shutdown()`/`dispose()`): a disposed instance must not leave child
* processes running with nobody left to collect them.
*
* @returns how many commands were signalled
*/
export declare function killAllBackgroundCommands(host: NeuroLink): Promise<number>;
/**
* Read one character window of a command's output, straight from its log file.
*
* Works while the command is still running — that is the monitor case — and
* after it settled. Character offsets, `totalSize` and `hasMore` match
* `retrieve_context` exactly, so paging code written for one works on the
* other.
*
* @throws when the task id is unknown to this host
*/
export declare function readBackgroundCommandOutput(host: NeuroLink, taskId: string, page: BackgroundCommandPageRequest): Promise<BackgroundCommandOutputPage>;
/**
* The four model-facing command tools, bound to `host`. Register them with
* `host.registerTool()` (see `NeuroLink.registerBackgroundCommandTools()`),
* never on the tool registry directly: only the "user-defined" category
* reaches the LLM's tool schema.
*/
export declare function createBackgroundCommandTools(host: NeuroLink): Record<string, MCPExecutableTool>;