Runner and PlugsReference
Host API types
The type declarations of @blaiseagent/runner/host, with their TSDoc.
The building blocks for plugs that provide a host, from @blaiseagent/runner/host. The prose
reference is Host API.
bindHost
/**
* The host as one plug sees it as `ctx.host`: `exec` starts in `agent.cwd` with `agent.env`
* layered under the call's own `env`, and relative `fs` paths resolve against `agent.cwd`. The
* agent draft is read at call time, so a change made by an outer plug is seen at once.
*/
export function bindHost(host: Host, agent: Pick<AgentSpawnSpec, "cwd" | "env">): Host;ByteStream
/** A two-way byte stream: a WebSocket, a TCP socket, a child's stdio, … */
export interface ByteStream {
readonly readable: ReadableStream<Uint8Array>;
readonly writable: WritableStream<Uint8Array>;
}closeHost
/**
* Releases the connection behind a host made by `createHost`. Hosts made any other way (the
* local host, `wrapHost`) own nothing, and closing them does nothing.
*/
export function closeHost(host: Host): Promise<void>;createHost
/**
* Builds a lazy `Host`: `connect` runs on first use (an `exec`, an `fs` call, the agent's
* spawn) and at most once; concurrent first uses share the call. `exec` and `spawn` run over the
* connection's transport, and `fs` is the vfs API over its provider (an `ExecProvider` unless the
* connection brings its own). The runner closes the connection when the `next()` that
* introduced the host resolves.
*/
export function createHost(options: CreateHostOptions): Host;CreateHostOptions
export interface CreateHostOptions {
readonly info: HostInfo;
/**
* Creates or wakes the environment and connects to it. Runs at most once, on first use;
* concurrent first uses share the same call.
*/
readonly connect: () => Promise<HostConnection>;
}ExecProvider
/**
* A file system provider for any host that can run POSIX tools: every operation is a small
* command over `transport.spawn` (`stat`, `cat`, `find`, `mkdir`, `mv`, …). It supports the async
* API only; the sync methods throw `ENOTSUP`.
*
* It is the default for hosts whose connection offers nothing better (SSH offers SFTP).
*/
export class ExecProvider extends VirtualProvider {
#private;
private readonly transport;
constructor(transport: ProcessTransport);
get supportsSymlinks(): boolean;
private run;
private flavour;
private statOf;
stat(path: string): Promise<VirtualStats>;
lstat(path: string): Promise<VirtualStats>;
readdir(path: string, options?: ReaddirOptions): Promise<string[] | VirtualDirent[]>;
mkdir(path: string, options?: MkdirOptions): Promise<string | undefined>;
rmdir(path: string): Promise<void>;
unlink(path: string): Promise<void>;
rename(oldPath: string, newPath: string): Promise<void>;
readFile(path: string, options?: Encoding): Promise<Buffer | string>;
writeFile(
path: string,
data: string | Buffer,
options?:
| {
encoding?: BufferEncoding;
mode?: number;
}
| BufferEncoding
): Promise<void>;
appendFile(
path: string,
data: string | Buffer,
options?:
| {
encoding?: BufferEncoding;
mode?: number;
}
| BufferEncoding
): Promise<void>;
private write;
exists(path: string): Promise<boolean>;
copyFile(src: string, dest: string): Promise<void>;
realpath(path: string): Promise<string>;
access(path: string, mode?: number): Promise<void>;
readlink(path: string): Promise<string>;
symlink(target: string, path: string): Promise<void>;
openSync(path: string): never;
statSync(path: string): never;
lstatSync(path: string): never;
readdirSync(path: string): never;
mkdirSync(path: string): never;
rmdirSync(path: string): never;
unlinkSync(path: string): never;
renameSync(oldPath: string): never;
readFileSync(path: string): never;
writeFileSync(path: string): never;
appendFileSync(path: string): never;
existsSync(): never;
copyFileSync(src: string): never;
realpathSync(path: string): never;
accessSync(path: string): never;
readlinkSync(path: string): never;
symlinkSync(_target: string, path: string): never;
}generateSessionKey
/**
* An ephemeral ed25519 keypair in OpenSSH formats, for one session's access to a machine.
*
* `ssh2` on Bun occasionally (about one in a hundred) generates a pair that its own parser then
* rejects, so each pair is checked and a bad one is replaced.
*/
export function generateSessionKey(): {
publicKey: string;
privateKey: string;
};HostConnection
/** What `connect` returns: the live connection behind a lazy host. */
export interface HostConnection {
readonly transport: ProcessTransport;
/**
* Async fs provider. The default is an exec-based provider over `transport`
* (`find`, `cat`, `mkdir`, `cp` and other coreutils over `spawn`). SSH hosts pass SFTP.
*/
readonly fs?: VirtualProvider;
readonly forward?: (spec: ForwardSpec) => Promise<AsyncDisposable>;
/** Enables the sync `fs` methods via a worker; omitted means they throw ENOTSUP. */
readonly sync?: WorkerConnectSpec;
/** Called by the runner when the host is no longer needed. */
close(): Promise<void>;
}localHost
/**
* The runner's own machine. Its `fs` is the vfs API over the real file system (so the sync
* methods work), and its processes are children of the runner. Nothing to connect or close.
*/
export function localHost(options?: LocalHostOptions): Host;LocalHostOptions
export interface LocalHostOptions {
/** The environment processes start from. Defaults to the runner's own. */
readonly env?: Readonly<Record<string, string | undefined>>;
/** Default agent cwd. Defaults to the runner's working directory. */
readonly workspace?: string;
}nodeTransport
/**
* Runs processes on the machine the runner is on. `baseEnv` is the environment every process
* starts from; the `env` of a spawn is layered over it.
*/
export function nodeTransport(
baseEnv: Readonly<Record<string, string | undefined>>
): ProcessTransport;ProcessTransport
/** The one primitive a host must supply: spawn a process with byte streams. */
export interface ProcessTransport {
/** Owns process-group cleanup and disconnect supervision; no outer watchdog is needed. */
readonly supervised?: boolean;
spawn(
argv: readonly string[],
options: {
readonly cwd?: string;
readonly env?: Record<string, string>;
}
): Promise<HostProcess>;
}SftpProvider
/**
* A file system provider over an SFTP session. Async only: the sync methods throw `ENOTSUP`
* unless the host brings a sync bridge (see `HostConnection.sync`). The session opens on first
* use.
*/
export class SftpProvider extends VirtualProvider {
#private;
constructor(open: () => Promise<SFTPWrapper>);
get supportsSymlinks(): boolean;
stat(path: string): Promise<VirtualStats>;
lstat(path: string): Promise<VirtualStats>;
readdir(path: string, options?: ReaddirOptions): Promise<string[] | VirtualDirent[]>;
mkdir(path: string, options?: MkdirOptions): Promise<string | undefined>;
rmdir(path: string): Promise<void>;
unlink(path: string): Promise<void>;
rename(oldPath: string, newPath: string): Promise<void>;
readFile(path: string, options?: Encoding): Promise<Buffer | string>;
writeFile(path: string, data: string | Buffer, options?: WriteOptions): Promise<void>;
appendFile(path: string, data: string | Buffer, options?: WriteOptions): Promise<void>;
exists(path: string): Promise<boolean>;
copyFile(src: string, dest: string): Promise<void>;
realpath(path: string): Promise<string>;
access(path: string): Promise<void>;
readlink(path: string): Promise<string>;
symlink(target: string, path: string): Promise<void>;
openSync(path: string): unknown;
statSync(path: string): VirtualStats;
lstatSync(path: string): VirtualStats;
readdirSync(path: string): string[] | VirtualDirent[];
mkdirSync(path: string): string | undefined;
rmdirSync(path: string): void;
unlinkSync(path: string): void;
renameSync(oldPath: string): void;
readFileSync(path: string): Buffer | string;
writeFileSync(path: string): void;
appendFileSync(path: string): void;
existsSync(path: string): boolean;
copyFileSync(src: string): void;
realpathSync(path: string): string;
accessSync(path: string): void;
readlinkSync(path: string): string;
symlinkSync(_target: string, path: string): void;
}SshAuth
export interface SshAuth {
readonly username: string;
/** An OpenSSH or PEM private key. */
readonly privateKey: string;
/**
* The pinned host key fingerprint (`SHA256:…`, as `ssh-keygen -lf` prints it). Omit it only
* when `stream` is already authenticated end to end.
*/
readonly hostKey?: string;
}sshConnect
/**
* SSH (`ssh2`) over any byte stream, with keepalives. Authenticates with a private key and checks
* the host key against the pinned fingerprint before anything is sent. What goes wrong after the
* handshake is said to `log`: the first error as a warning, any later one as debug output.
*/
export function sshConnect(
stream: ByteStream,
auth: SshAuth,
log?: PlugLogger
): Promise<SshConnection>;SshConnection
export interface SshConnection extends AsyncDisposable {
readonly transport: ProcessTransport;
sftp(): VirtualProvider;
forward(spec: ForwardSpec): Promise<AsyncDisposable>;
}tcpStream
/** A TCP connection as a `ByteStream`. Rejects if the connection cannot be made. */
export function tcpStream(host: string, port: number): Promise<ByteStream>;webSocketStream
/**
* A WebSocket as a `ByteStream`, for gateways and HTTPS-only endpoints. Binary messages are the
* bytes of the stream; text messages are rejected.
*/
export function webSocketStream(
url: string,
options?: {
readonly headers?: Record<string, string>;
}
): Promise<ByteStream>;WorkerConnectSpec
/**
* Where a worker thread can import the plug's `connect` function from. The sync fs bridge
* opens its own connection there: the main thread is blocked in `Atomics.wait` during a sync
* call and cannot drive a socket.
*/
export interface WorkerConnectSpec {
readonly module: string;
readonly exportName: string;
/** Structured-clone-able arguments for `connect`. */
readonly args: unknown;
}wrapHost
/**
* A host that runs every process of `base` through `wrap` (for example under an OS-level
* sandbox). `fs`, `info` and `forward` are `base`'s, unchanged. It owns nothing: the plug that
* created `base` closes it.
*/
export function wrapHost(base: Host, wrap: WrapProcess): Host;WrapProcess
export type WrapProcess = (
argv: readonly string[],
options: {
readonly cwd: string;
readonly env: Record<string, string>;
}
) => Promise<{
argv: readonly string[];
env: Record<string, string>;
}>;