@sema-agent/core
Version:
Stateless, task-oriented AI agent core
117 lines (116 loc) • 9.05 kB
TypeScript
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;