import type { Clock } from "../runtime/clock.js"; import type { LogLevel } from "../logger.js"; /** The seven things a host may lack or refuse. A function or node names * the ones it needs with the `@capabilities(...)` tag, and the compiler * checks them against the platform being compiled for. */ export declare const CAPABILITIES: readonly ["fileRead", "fileWrite", "network", "subprocess", "env", "terminal", "llm"]; export type Capability = (typeof CAPABILITIES)[number]; /** The platforms a program can be compiled for. */ export type Platform = "node" | "browser"; /** Which capabilities each platform has. The compiler reads this to refuse * a program that needs more than its platform can give, and the default * host of each platform reads it to grant the same list. One table, so the * two cannot disagree. */ export declare const PLATFORM_CAPABILITIES: Record; /** The operating system a host runs on. `browserHost` reports `"unknown"`. */ export type OperatingSystem = "macos" | "linux" | "windows" | "wsl" | "unknown"; /** A directory an approval named, realpathed once by the host that made * it. Every file operation takes one; nothing takes a bare string root. * Only files under lib/host read its inside (`rootPath` in roots.ts). */ export type { Root } from "./roots.js"; import type { Root } from "./roots.js"; /** A whole path the approval named, as its real parent plus a final name * that is never followed. */ export type Located = { root: Root; target: string; }; export type Entry = { name: string; type: "file" | "dir" | "other"; size: number; }; /** What `stat` says about a path. `null` from `stat` means nothing is * there. */ export type FileStat = { kind: "file" | "dir" | "other"; size: number; /** Milliseconds since the Unix epoch. */ modifiedMs: number; }; export declare const WRITE_MODES: readonly ["overwrite", "append", "create-only"]; export type WriteMode = (typeof WRITE_MODES)[number]; export type WriteOptions = { mode?: WriteMode; fileMode?: number; }; /** An open file for writes at positions, from `openForWrite`. */ export type WritableFile = { /** Write all of `data` at `position`, looping over a short write. */ writeAt(data: Uint8Array, position: number): Promise; truncate(size: number): Promise; close(): Promise; }; export type AppendableFile = { /** Write all of `data` at the end of the file. */ append(data: Uint8Array): Promise; close(): Promise; }; /** The files. Every function takes a `Root` an approval named, and a host * built with a narrower root reaches less. The rule every host keeps: under * an approval that names directory D, no byte is read from or written to * a path outside D, and a symlink below D is refused. Reading and * resolving need `fileRead`; writing, moving, and deleting need * `fileWrite`. See docs/dev/stdlib/contained-files.md. */ export type HostFiles = { /** The root for a directory a caller spelled, realpathed once. */ root(dir: string): Promise; /** The root an approval already named, spelled the way the approver saw * it: every existing component must be a real directory, so a link * planted at the approved path while the prompt was pending is * refused. */ fixedRoot(dir: string): Promise; /** The real spelling of a directory, for an interrupt payload or a * comparison of paths. */ realDir(dir: string): Promise; /** Split a whole path into its real parent and final name. */ wholePath(path: string): Promise; /** The whole-path twin of `fixedRoot`: the real parent the approver saw, * checked to still be spelled without links, plus the final name. */ fixedPath(path: string): Promise; /** The real spelling of a whole path, for an interrupt payload. */ realPath(path: string): Promise; /** `target` under `root`, validated: a path this host's subprocess part * understands, for a program such as ffmpeg or a module loader. */ resolvePath(root: Root, target: string): Promise; /** The `dir` and `filename` an interrupt payload shows for a single-file * operation. Runs its steps in one piece, with no await between them, * because it runs between a wrapper's call and its interrupt. */ locate(dir: string, filename: string, operation: "read" | "write"): Promise<{ dir: string; filename: string; }>; /** Hold a lock on one path for the length of `work`. The lock belongs * to the host, so it covers every run that shares it. */ withLock(root: Root, target: string, work: () => Promise): Promise; readText(root: Root, target: string): Promise; readBytes(root: Root, target: string): Promise; /** The file in pieces, for one too large to buffer. */ readChunks(root: Root, target: string): AsyncIterable; /** One level of a directory. Symlinked entries are left out. */ list(root: Root, target: string): Promise; stat(root: Root, target: string): Promise; writeText(root: Root, target: string, content: string, options?: WriteOptions): Promise; writeBytes(root: Root, target: string, bytes: Uint8Array, options?: WriteOptions): Promise; /** Read a file, call `change` with its text (`null` when it does not * exist), and write the result. No other call on the same file runs * between the read and the write. */ updateText(root: Root, target: string, change: (current: string | null) => string): Promise; openForWrite(root: Root, target: string, options?: WriteOptions): Promise; /** A file kept open for appends, for a writer that adds a line at a * time. Two handles on one file never write over each other. */ openForAppend(root: Root, target: string, options?: WriteOptions): Promise; mkdir(root: Root, target: string): Promise; remove(root: Root, target: string): Promise; copy(from: Located, to: Located): Promise; move(from: Located, to: Located): Promise; }; /** What the Agency functions `env` and `setEnv` read and write. This is a * capability because it hands any variable to the program, and so to an * agent. The runtime's own reads go through `settings` instead. */ export type HostEnv = { get(name: string): string | null; set(name: string, value: string): void; /** Every variable, for a child process's environment. A copy: writing * to it changes nothing. */ all(): Record; }; /** The terminal, for `print`, `input`, and the stdlib functions that write * to standard output or standard error. */ export type HostTerminal = { /** Print values on one line, the way `print` in Agency does. The values * are not strings: a host formats them. `nodeHost` hands them to * `console.log`, so an object prints the way Node prints it. */ print(values: unknown[]): void; /** Write text to standard output, with no newline added. */ writeOut(text: string): void; /** Write text to standard error, with no newline added. */ writeErr(text: string): void; /** Show `prompt` and read one line. Rejects with the signal's reason when * `signal` aborts while waiting, and resolves with "" when standard * input ends first. */ readLine(prompt: string, signal?: AbortSignal): Promise; /** Everything on standard input until it ends, as text. */ readAll(): Promise; /** Whether a person is at the terminal. False under a pipe or in CI. */ isInteractive(): boolean; /** The width and height in character cells, or null when standard * output is not a terminal (a pipe, a file). */ size(): TerminalSize | null; /** Whether output should carry ANSI colour. On Node that is the usual * convention: no when `NO_COLOR` is set, yes when `FORCE_COLOR` is set * to anything but an empty string, `0`, or `false`, and otherwise yes * when standard output is a terminal. */ supportsColor(): boolean; }; export type TerminalSize = { columns: number; rows: number; }; /** Facts about the process and the machine. Every host has these, so they * are not a capability. `browserHost` answers each with a value the app * gave it or a fixed default, and refuses only `exit`. */ export type HostSystem = { operatingSystem(): OperatingSystem; cwd(): string; homeDir(): string; tempDir(): string; /** The command line, as Node gives it: the runtime, the script, then the * program's own arguments. */ args(): string[]; processId(): number; /** The directory of the module whose `import.meta.url` is given. */ moduleDir(moduleUrl: string): string; /** Where the agency-lang package is installed, for the files it ships * (the stdlib, its docs, the agents' skills and prompts) and for policy * patterns that name them; null where the package is bundled into the * program, as in a browser. */ installDir(): string | null; /** The real spelling of a directory, the way file effects spell the * paths in their payloads: links resolved through the part that exists, * the rest as written, so a directory not made yet is spelled the way * it will be. Synchronous, because the policy directories are resolved * while a context is built. */ realDir(dir: string): string; /** Whether the module whose `import.meta.url` is given is the one the * process was started with. A compiled program runs its `main` node only * when this is true. */ isMainModule(moduleUrl: string): boolean; exit(code: number): never; /** Name the process, as `ps` and an activity monitor show it. A * platform with no process name ignores it. */ setTitle(title: string): void; /** The channel to the parent process, when the parent forked this one * with an IPC channel the way Agency's subprocess runs do, or null. * `send` throws once the parent is gone. */ parentChannel(): ParentChannel | null; /** Run `fn` as the process ends, for a synchronous write such as the * coverage file. On a platform whose process does not end, `fn` never * runs. */ onExit(fn: () => void): void; }; export type ParentChannel = { send(message: unknown): void; /** Hand every message from the parent to `listener` until the returned * function is called. */ onMessage(listener: (message: unknown) => void): () => void; }; /** How the runtime reads what it needs for itself and reports what it must. * `read` answers the test switches, the log level, and the API key a * connector sends; on Node it reads the same variables as `env.get`. The * difference is who asks: a program calling `env("KEY")` goes through the * `env` capability, which a host can refuse, and the runtime reading * `AGENCY_MAX_COST` goes through here, which no host refuses. */ export type HostSettings = { read(name: string): string | null; /** Where a line from the runtime's logger goes. The level is the * logger's; the line is already formatted. Never throws. */ log(level: LogLevel, text: string): void; }; /** Random values. `nodeHost` and `browserHost` use the platform's secure * source; `memoryHost` returns a sequence a test can predict. */ export type HostRandom = { /** A short unique id, the way `nanoid()` makes one. */ id(): string; bytes(length: number): Uint8Array; }; /** What a host runs: a program with its arguments, or a script bash runs. */ export type Command = { kind: "program"; program: string; args: string[]; } | { kind: "shell"; script: string; }; export type RunOptions = { /** The working directory; the host's own when left out. */ cwd?: string; /** The child's whole environment; the host's own when left out. */ env?: Record; /** Sent to standard input, which is then closed. Without it standard * input is closed at once. */ input?: string | Uint8Array; /** The child is killed when it has run this long. 0 or absent: no limit. */ timeoutMs?: number; /** The child is killed when this fires. */ signal?: AbortSignal; /** Each of standard output and standard error is kept up to this many * bytes; past it the child is killed and `truncated` names the stream. * 0 or absent: no limit. */ maxOutputBytes?: number; /** Whether standard output and standard error are collected. A child * that writes for minutes, such as a speech player, is not collected. */ collect?: { stdout: boolean; stderr: boolean; }; /** The signal a kill sends. SIGTERM when left out. */ killSignal?: "SIGTERM" | "SIGKILL"; }; /** How a child ended. The caller decides what each case means. */ export type RunResult = { /** The exit code, or null when a signal ended the child. */ exitCode: number | null; /** The signal that ended the child, or null. */ signal: string | null; stdout: string; stderr: string; /** The stream that reached `maxOutputBytes`, for which the child was * killed, or null. */ truncated: "stdout" | "stderr" | null; /** The child was killed because `timeoutMs` passed. */ timedOut: boolean; /** The child was killed because `signal` fired. */ aborted: boolean; }; /** A child that was started and is still running. */ export type RunningProcess = { kill(signal?: "SIGTERM" | "SIGKILL"): void; /** Resolves when the child ends. */ wait(): Promise; }; /** Subprocesses. `run` starts a child and waits for it. A program that * cannot be started rejects with the platform's error (`ENOENT` on * Node), not a result. `start` hands back the running child, for a * caller that stops it on an event of its own, such as a keypress. * `locate` is the path a program name resolves to on the search path, * the way the shell finds it (`PATH`, and `PATHEXT` on Windows), or * null when nothing executable has that name. */ export type HostSubprocess = { run(command: Command, options?: RunOptions): Promise; start(command: Command, options?: RunOptions): Promise; locate(program: string): Promise; }; /** The network, as the platform's own `fetch`: the same arguments and the * same `Response`. A host can refuse it, limit it to a list of sites, or * answer from scripted responses, the way the test runner's fetch mocks * do. */ export type HostNetwork = { fetch(input: string | URL | Request, init?: RequestInit): Promise; }; /** The parts a host is built from. `files`, `network`, `subprocess`, * `env`, and `terminal` are capabilities, so a host may leave them out; `makeHost` * then supplies parts that refuse. The other parts are required. */ export type HostParts = { files?: HostFiles; network?: HostNetwork; subprocess?: HostSubprocess; env?: HostEnv; terminal?: HostTerminal; system: HostSystem; settings: HostSettings; clock: Clock; random: HostRandom; }; export type Host = { /** Names the host in error messages: "node", "browser", "memory". */ name: string; /** What this host has. Only `requireCapabilities` reads it. */ capabilities: Capability[]; /** See `MakeHostArgs.onCallable`. Absent on every host but the test * runner's. */ onCallable?: (event: CallableEvent) => void; files: HostFiles; network: HostNetwork; subprocess: HostSubprocess; env: HostEnv; terminal: HostTerminal; system: HostSystem; settings: HostSettings; clock: Clock; random: HostRandom; }; /** The capability each capability part needs. A part not listed here * (`system`, `settings`, `clock`, `random`) is on every host. `files` * needs two: `fileRead` for the part, and `fileWrite` for the functions * in FILE_WRITE_FUNCTIONS. */ export declare const PART_CAPABILITY: { readonly files: "fileRead"; readonly network: "network"; readonly subprocess: "subprocess"; readonly env: "env"; readonly terminal: "terminal"; }; export type CapabilityPart = keyof typeof PART_CAPABILITY; /** The functions of the files part that write, move, or delete. They need * `fileWrite`; the rest of the part needs `fileRead`. */ export declare const FILE_WRITE_FUNCTIONS: (keyof HostFiles)[]; /** The capability one function of a capability part needs. */ export declare function functionCapability(part: CapabilityPart, fn: string): Capability; /** Thrown when code asks a host for something it cannot do. Agency turns an * error thrown inside a function into a failure result, so a program sees * a failed call with this message. A refusing host never reports success * for work it did not do. */ export declare class UnsupportedOnHostError extends Error { readonly capability: Capability; readonly hostName: string; /** The host function that was called, as "part.function", when one was. */ readonly functionName: string | null; constructor(args: { capability: Capability; hostName: string; functionName?: string; }); } /** A function or node starting, with what its `@capabilities` tag names, * or ending. `enterCallable` (lib/runtime/capabilities.ts) reports both * to the host's `onCallable`, when it has one. */ export type CallableEvent = { kind: "enter"; name: string; capabilities: Capability[]; } | { kind: "exit"; name: string; }; export type MakeHostArgs = { name: string; capabilities: Capability[]; parts: HostParts; /** Called with the function's name, as "part.function", and its * capability before any capability function runs. The test runner uses * it to record which capabilities a test used. */ onUse?: (functionName: string, capability: Capability) => void; /** Called when a function or node starts and when it ends. The test * runner uses it with `onUse` to attribute each host use to the * functions that were running. Decided once, when the host is built, * so a program cannot turn it on or point it somewhere by changing a * setting at run time. */ onCallable?: (event: CallableEvent) => void; }; /** * Build a host from its parts. For each capability part: * * - when its capability is granted, the part comes from `parts`, and a * missing part is an error here, not later; * - when it is not, the host gets a part whose every function throws * `UnsupportedOnHostError`. * * So no host writes a refusal, and a host with fewer capabilities is the * same code with a shorter list. */ export declare function makeHost(args: MakeHostArgs): Host; /** Throw `UnsupportedOnHostError` for the first capability in `needed` that * `host` lacks. The one function that reads `host.capabilities`. `what` * names the caller in the message. */ export declare function requireCapabilities(host: Host, needed: Capability[], what?: string): void; /** A copy of `host` with `clock` replaced. For the callers that pass a * `FakeClock` to a `RuntimeContext` and want the rest of the host as is. */ export declare function withClock(host: Host, clock: Clock): Host;