UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

117 lines (116 loc) • 9.05 kB
import { type EstablishedPlan } from "./shell-read-establish.js"; import type { ExecutionEnv } from "../../internal/harness-types.js"; import type { ToolCallRequest } from "../../core/tool-policy.js"; /** The REMOVAL programs — the reference implementation's dangerous-removal arm reads `rm` and `rmdir`, with or without a * recursive option (removing an empty home or the task root is still removing it). */ export declare const SHELL_REMOVAL_COMMANDS: readonly ["rm", "rmdir"]; export type ShellRemovalCommand = (typeof SHELL_REMOVAL_COMMANDS)[number]; /** What makes a removal target dangerous, in the order a card names it. */ export type DangerousRemovalKind = "filesystem_root" | "home" | "workspace" | "top_level"; /** The protected locations a removal is judged against. `homes`: the environment's declared home and every other spelling * of it (its canonical one); `workspace`: the task root, the working directory, the additional working directories. */ export interface RemovalSeat { readonly homes: readonly string[]; readonly workspace: readonly string[]; } /** One dangerous removal a command makes: the removal program, the operand as written, the target as established. */ export interface DangerousRemoval { readonly kind: DangerousRemovalKind; readonly program: string; readonly operand: string; readonly target: string; } /** * The ONE dangerous-removal predicate: is removing this absolute path removing a protected location? The reference * implementation's set (cli 2.1.291 `dme` / `xW`) — the filesystem root (a drive root, a UNC share root), a home directory * (every spelling the seat holds), a workspace directory or any of its ANCESTORS (the task root, the working directory, an * additional working directory — whether the home lies inside the task root or the task root inside the home), a top-level * directory (one segment below the root). `undefined` for a relative path or any other target. */ export declare function dangerousRemovalOf(path: string, seat: RemovalSeat): DangerousRemovalKind | undefined; /** What one removal finding asks: a protected location (`dangerous_delete`; `untrackedFrom` — the segment the working * directory stops being tracked at, where the target is relative and was judged where the text stood then); a recursive * forced removal whose target the * guard cannot verify (`unverifiable_delete`) — an operand the shell rewrites from a value the plan does not settle * (`rewritten`), the operands of a removal its argv stream fills in (`stream`), or an operand relative to the directory a * launcher option moved the removal into (`relocated`); or a shell handed text the engine cannot read * (`unverifiable_shell_text`) — a text a program produces (`produced`: what a pipe or a `<(…)` carries, an argv a stream * fills in, a substitution), or a text nobody read (`unread`: one the lexer could not read at all, {@link unreadPlan}, or a * segment whose program the walk could not name, {@link programUnread}). */ export type RemovalFinding = (DangerousRemoval & { readonly guard: "dangerous_delete"; readonly untrackedFrom?: string; }) | { readonly guard: "unverifiable_delete"; readonly program: string; readonly operand: string; readonly source: "rewritten" | "stream" | "relocated"; } | { readonly guard: "unverifiable_shell_text"; readonly program: string; readonly text: string; readonly source: "produced" | "unread"; }; /** * The names a removal target may be built on and still be verified: a root the SHELL provides, never one a command's * output fed — `rm -rf "$TMPDIR"/<literal path below it>` is not asked about. `TMPDIR` is the reference's carve-out (its * rule exempts "a well-known-safe temp pattern"); `HOME` is sema's (a registered divergence, looser than the reference, * whose rule names `$HOME` as the blast radius: it is a root the shell provides too). A declared home is read by the * value table itself — `$HOME` is then its value, and the target is judged where it lands; `PWD` is no root of this kind: * its value is where the working-directory thread says the command stands. */ export declare const KNOWN_SAFE_ROOTS: readonly string[]; /** * The removal findings of an established plan — ONE walk, nested plans included (a `bash -c` / `sh -c` text, a substitution * body), two words. For every `rm` / `rmdir` segment, each of its OPERANDS (every write target that is not one of its * option words: a word some implementation's reading of the program's row writes, {@link writeReadings}) as the plan * established it on the WRITE face (the entry the program unlinks, and the deny-floor shadow of a home word the plan did * not place) is judged by {@link dangerousRemovalOf}. In a recursive forced removal ({@link recursiveForce}), an operand * the shell rewrites from a value the plan's table does not settle — a name with no value here or one set in a form the * table cannot read, a substitution, a parameter operator, a positional parameter — is an unverifiable target, save a * literal path below a known-safe root the shell still provides there ({@link belowKnownSafeRoot}); so are the operands * of a removal its argv stream fills in (`… | xargs rm -rf`), and an operand the plan cannot place — in a removal a * launcher moved into another base, or in text such a removal hands a shell (`env -C <dir> rm -rf build`). A relative * operand past a directory move the working-directory thread cannot follow is judged where the text stood when it lost * track (the reference resolves it against the original working directory: KL-1403-b), and a card it earns says so. A `read` fills its name from the input: a value * the table does not settle (stricter than the reference, whose rule counts `read VAR` as an in-window assignment). A * name the table settles is its value, so `D=./build; rm -rf "$D/../build"` is judged * where it lands, as `rm -rf ./build` is. A shell handed a text the engine cannot read — one a program produces * ({@link runtimeScriptOf}), or one nobody read ({@link unreadPlan}, {@link programUnread}) — is an unreadable text of its * own. The read-face copy of an operand is not judged: it follows a link the removal does not follow. */ export declare function removalFindingsOf(established: EstablishedPlan, seat: RemovalSeat, safeRoots?: readonly string[], enclosingRelocated?: boolean): RemovalFinding[]; /** Does this plan (nested plans included) run a removal program, hand a shell a script built when it runs, or hold text * nobody could read ({@link unreadPlan}, {@link programUnread})? The guard establishes nothing for a command that does none. */ export declare function planRunsRemoval(plan: EstablishedPlan["plan"]): boolean; /** What the removal guard asks for one command: the guard word and its sentence's account — the first protected location * (the program, the location, the target), else the first target the guard cannot verify, else the first shell text the * engine cannot read; `undefined` when none. */ export interface RemovalVerdict { readonly guard: RemovalFinding["guard"]; readonly text: string; } /** * The seat the dangerous-removal arm of the removal guard (`createUnverifiableDeletePolicy`, tool-policy.ts) reads, beside the request's own `root` / * `cwd` (the task root and the live working directory): the execution environment (its declared home — the `HOME` the * shell child receives, the same seat the read station folds `~` / `$HOME` with — and its resolver, so a target that * resolves through a link is judged where it lands), and the leg's additional working directories. */ export interface RemovalGuardSeat { readonly env?: ExecutionEnv; readonly additionalRoots?: () => readonly string[]; } /** * The removal guard's READING of one command — undefined when no segment of it (nested shell text included) runs a * removal program or hands a shell a script built when it runs, and every text in it was read, so a command that removes * nothing costs the gate no suspension (a text the lexer could not read is not one: bash may run the lines before its * syntax error, and the guard reads no prefix of it). Otherwise `judge` establishes the plan the read * station plans (the same planner, the same value table, the same write-target reading) lexically against the request's * `cwd` and the seat's home, and — where an environment is seated — through its resolver too, and returns what either * establishment finds ({@link removalFindingsOf}: the protected-location word first, then the unverifiable target, then the * unreadable text). * `safeRoots`: the known-safe roots ({@link KNOWN_SAFE_ROOTS}, with a deployment's own added by the policy). */ export declare function removalReadingOf(command: string, req: Pick<ToolCallRequest, "cwd" | "root">, seat: RemovalGuardSeat | undefined, safeRoots?: readonly string[]): { judge(signal?: AbortSignal): Promise<RemovalVerdict | undefined>; } | undefined;