@sema-agent/core
Version:
Stateless, task-oriented AI agent core
245 lines (244 loc) • 12.1 kB
JavaScript
import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
import { isProtectedWrite } from "./tool-registry.js";
import { rosterEntryNamed } from "./tool-roster.js";
import { wireOf } from "./tool-name-readings.js";
import { clampPermissionMode, isPermissionMode, PERMISSION_MODES } from "./ask-origin.js";
import { argvOf, lexShellText } from "../shell/lex.js";
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
import { createSafeNotifier } from "./safe-notify.js";
import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "./retired-keys.js";
import { resolveOutputStyleFrame } from "./output-style.js";
import { CheckpointError, isCheckpointConfigRefusalCode, recordedModeSeed } from "./checkpoint-store.js";
export const PERMISSION_MODE_FOLD_REASONS = Object.freeze(["operator_deny", "face_absent", "inherited_ceiling"]);
export const ACCEPT_EDITS_FILE_COMMANDS = Object.freeze(["mkdir", "touch", "rm", "rmdir", "mv", "cp", "sed"]);
const MODE_POLICY_TABLE = {
plan: { enablePlanMode: true, writeGate: "plan", fileCommands: [], writeFace: undefined, unstated: "ask" },
acceptEdits: { enablePlanMode: false, writeGate: "accept_root", fileCommands: ACCEPT_EDITS_FILE_COMMANDS, writeFace: undefined, unstated: "ask" },
default: { enablePlanMode: false, writeGate: "ask", fileCommands: [], writeFace: undefined, unstated: "ask" },
auto: { enablePlanMode: false, writeGate: "ask", fileCommands: [], writeFace: undefined, unstated: "ask" },
dontAsk: { enablePlanMode: false, writeGate: "ask", fileCommands: [], writeFace: undefined, unstated: "ask" },
bypassPermissions: { enablePlanMode: false, writeGate: "none", fileCommands: [], writeFace: "open", unstated: "none" },
};
export function modePolicyOf(mode) {
return MODE_POLICY_TABLE[mode];
}
export function modeAcceptsFileCommand(mode, toolName, args) {
const commands = modePolicyOf(mode).fileCommands;
if (commands.length === 0 || toolName !== COMMAND_RULE_TOOL)
return false;
const command = args?.command;
if (typeof command !== "string" || args.run_in_background === true)
return false;
const lexed = lexShellText(command);
if ("reject" in lexed || lexed.segments.length === 0 || lexed.substitutions.length > 0)
return false;
return lexed.segments.every((segment) => {
if (segment.connector === "|" || segment.connector === "&" || segment.tokens.some((t) => t.kind !== "word"))
return false;
const head = argvOf(segment)[0];
return head !== undefined && head.assignment === undefined && head.expansions.length === 0 && head.raw === head.folded && commands.includes(head.raw);
});
}
export function mountedToolDeclaresReadOnly(tools, toolName) {
return tools.findLast((t) => t.name === toolName)?.mcpReadOnlyHint === true;
}
export function asksUnstated(d, seat, mode) {
return d.action === "allow" && d.decisionReason === undefined && seat === "passthrough" && mode !== undefined && modePolicyOf(mode).unstated === "ask";
}
export const unstatedAskMessage = (toolName) => `The agent requested permission to use ${toolName}, but it has not been granted yet.`;
export function permissionSeatAt(input, args) {
if (input.permissionSeatOf !== undefined)
return input.permissionSeatOf(args);
return input.permissionSeat === "passthrough" ? { seat: "passthrough" } : undefined;
}
export function mountedToolPermissionSeat(tools, toolName) {
return tools.findLast((t) => t.name === toolName)?.permissionSeat === "passthrough" ? "passthrough" : undefined;
}
export function mountedToolRequiresUserInteraction(tools, toolName) {
return tools.findLast((t) => t.name === toolName)?.mcpRequiresUserInteraction === true;
}
const ENGINE_ROSTER_SOURCES = new Set(["builtin", "synthetic"]);
export function planRefusesWrite(req, roster, readOnlyDeclared) {
if (isProtectedWrite(req))
return true;
const row = roster === undefined ? undefined : rosterEntryNamed(roster, wireOf("mode.planRosterRow", req));
return row !== undefined && !ENGINE_ROSTER_SOURCES.has(row.source) && row.effect !== "read" && !(row.effectProvenance === "default" && readOnlyDeclared?.(row.name) === true);
}
const planAsk = (message) => ({ action: "ask", message, decisionReason: "mode" });
export function createModeGatePolicy(modeRef, seats) {
const exemptDirs = seats.exemptDirs !== undefined && seats.exemptDirs.length > 0 ? { exemptDirs: seats.exemptDirs } : {};
const askGate = createFsWriteGatePolicy({ env: seats.env, rootPath: seats.rootPath, ...exemptDirs, defaultWrite: "ask" });
const acceptRootGate = createFsWriteGatePolicy({ env: seats.env, rootPath: seats.rootPath, acceptDirs: seats.acceptDirs !== undefined && seats.acceptDirs.length > 0 ? [...seats.acceptDirs] : [seats.rootPath], ...exemptDirs, defaultWrite: "ask" });
const planGate = createFsWriteGatePolicy({ env: seats.env, rootPath: seats.rootPath, ...exemptDirs, defaultWrite: "ask", defaultAsk: (path, note) => planAsk(`Cannot write to ${path} while in plan mode.${note ?? ""}`) });
return {
async check(req, signal) {
const gate = modePolicyOf(modeRef.current).writeGate;
if (gate === "none")
return { action: "allow" };
if (gate === "plan") {
if (!planRefusesWrite(req, seats.roster?.current, seats.readOnlyDeclared))
return { action: "allow" };
if (!isProtectedWrite(req))
return planAsk(`Cannot call ${req.toolName} while in plan mode.`);
return planGate.check(req, signal);
}
return (gate === "ask" ? askGate : acceptRootGate).check(req, signal);
},
};
}
export function modeRequestAfterResume(seat, recorded, outcome) {
const exitsPlan = outcome?.gate === "plan_review" && outcome.decision !== "reject" && recorded.permissionMode === "plan";
if (!exitsPlan)
return seat ?? recorded.permissionMode;
return seat !== undefined && seat !== "plan" ? seat : recorded.prePlanMode;
}
export function refusePermissionModeDoors(spec) {
assertNoRetiredKeys(spec, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
const mode = spec.permissionMode;
if (mode !== undefined && !isPermissionMode(mode)) {
const e = new Error(`permissionMode must be one of ${PERMISSION_MODES.map((w) => JSON.stringify(w)).join(", ")} when present (got ${JSON.stringify(mode)}) — omit it for the default mode; an unrecognized word is refused rather than read as any mode.`);
e.code = "config.permission_mode_invalid";
throw e;
}
const floor = spec.shellGate;
if (floor !== undefined && floor !== "always") {
const e = new Error(`shellGate must be the floor word "always" when present (got ${JSON.stringify(floor)}) — the doctrine values "off" / "classify" are retired: the shell's shape tier is installed on every full shell and the permission mode (permissionMode) decides what its asks become; omit shellGate for no operator floor.`);
e.code = "config.shell_gate_value_retired";
throw e;
}
return mode;
}
export function refuseResumeDoors(config, recordedGate) {
screenAs("taskConfig", () => {
refusePermissionModeDoors(config);
resolveOutputStyleFrame(config.outputStyle);
});
screenAs("inheritedGate", () => void recordedModeSeed(recordedGate));
}
function screenAs(field, screen) {
try {
screen();
}
catch (e) {
if (e instanceof Error && "code" in e && isCheckpointConfigRefusalCode(e.code))
throw new CheckpointError("checkpoint.config_refused", e.message, { field, code: e.code });
throw e;
}
}
export class PermissionModeController {
requestedBySpec;
ceiling;
steers = [];
arming;
onFold;
prePlan;
constructor(requestedBySpec, ceiling, opts) {
this.requestedBySpec = requestedBySpec;
this.ceiling = ceiling;
this.onFold = opts?.onFold;
if (requestedBySpec === "plan")
this.prePlan = opts?.prePlanMode;
}
get prePlanMode() {
return this.declared === "plan" ? this.prePlan : undefined;
}
get requested() {
return this.steers.at(-1)?.word ?? this.requestedBySpec;
}
ceilingNow(observe) {
if (this.ceiling === undefined || typeof this.ceiling === "string")
return this.ceiling;
return observe ? (this.ceiling.observed ?? this.ceiling.current) : this.ceiling.current;
}
get declared() {
return this.declaration().effective;
}
declaration(observe = false) {
const ceiling = this.ceilingNow(observe);
if (ceiling === undefined)
return { effective: this.requested ?? "default", clamped: false };
return clampPermissionMode(this.requested, ceiling);
}
armedForAuto = false;
faultSeat = createSafeNotifier();
get current() {
const effective = this.receipt().effective;
const arming = this.arming;
if (effective === "auto" && !this.armedForAuto && arming?.armAuto !== undefined) {
this.armedForAuto = true;
void new Promise((resolve) => resolve(arming.armAuto?.())).catch((e) => this.faultSeat.notifyAsync(() => arming.onArmFault?.(e), "permissionMode.onArmFault"));
}
return effective;
}
get observed() {
return this.receiptOf(true).effective;
}
receipt() {
return this.receiptOf(false);
}
receiptOf(observe) {
const { effective: declared, clamped } = this.declaration(observe);
const requested = this.requested;
const base = requested !== undefined ? { requested } : {};
const a = this.arming;
const armingFold = a === undefined
? undefined
: declared === "bypassPermissions" && a.caps?.bypassPermissions === false
? "operator_deny"
: declared === "auto" && !a.autoFacePresent
? "face_absent"
: declared === "auto" && a.caps?.autoMode === false
? "operator_deny"
: undefined;
if (armingFold !== undefined)
return { ...base, effective: "default", foldedBy: armingFold };
if (clamped)
return { ...base, effective: declared, foldedBy: "inherited_ceiling" };
return { ...base, effective: declared };
}
ceilingRebound(before) {
const after = this.receipt();
if (after.foldedBy === "inherited_ceiling" && (before.foldedBy !== "inherited_ceiling" || before.effective !== after.effective))
this.onFold?.(after);
}
bindArming(arming) {
const before = this.receipt();
this.arming = arming;
const receipt = this.receipt();
if (receipt.foldedBy !== undefined && receipt.foldedBy !== before.foldedBy)
this.onFold?.(receipt);
return receipt;
}
async steer(mode) {
const entry = { word: mode };
const before = this.declared;
this.steers.push(entry);
const receipt = this.receipt();
const arming = this.arming;
if (receipt.effective === "auto" && arming?.armAuto !== undefined) {
this.armedForAuto = true;
try {
await arming.armAuto();
}
catch (e) {
this.armedForAuto = false;
const at = this.steers.indexOf(entry);
if (at >= 0)
this.steers.splice(at, 1);
throw e;
}
}
const at = this.steers.indexOf(entry);
if (at > 0)
this.steers.splice(0, at);
if (this.steers.at(-1) === entry) {
if (mode !== "plan")
this.prePlan = undefined;
else if (before !== "plan")
this.prePlan = before;
}
if (receipt.foldedBy !== undefined)
this.onFold?.(receipt);
return receipt;
}
}