@sema-agent/core
Version:
Stateless, task-oriented AI agent core
277 lines (276 loc) • 14.3 kB
JavaScript
import { argsAsTheToolReads, stampArgsReading } from "./effective-path-target.js";
import { abortedSeatDeny, decisionText, describeThrown, isEngineRefusal, ownArgs, ownedReading, refuseOutOfContractDecision, unaskedSeatAnswer } from "./tool-policy.js";
import { createSettlementLedger, engineFailClosedExit, hookDenyExit, unownedArgsExit } from "./runner/gate-exit.js";
import { classicEventBase, dispatchClassic, ENGINE_CORE_ANSWER, engineCore, failedHandlerClause, judgedInputOf, observePermissionDenied, recordScreeningChain } from "./mods/stations.js";
import { runGateFold } from "./gate-fold.js";
import { runGateLanes } from "./gate-lanes.js";
export { normalizeOrgGateVerdict, normalizePersistedRuleHit } from "./rule-lane-reads.js";
import { brandPolicyAskClass } from "./ask-class.js";
import { isAskOrigin, screenAskClassMembers } from "./ask-origin.js";
import { isAutoModeFallbackCause } from "./auto-mode.js";
import { inlineUntrusted } from "./untrusted-text.js";
export { formatHookFeedback } from "./reminder-mint.js";
import { createSafeNotifier } from "./safe-notify.js";
import { wireOf } from "./tool-name-readings.js";
const reflectApply = Reflect.apply;
const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
const getPrototypeOf = Object.getPrototypeOf;
const objectPrototype = Object.prototype;
function findEnvSlot(env, key) {
for (let o = env; o !== null && o !== objectPrototype; o = getPrototypeOf(o)) {
const desc = getOwnPropertyDescriptor(o, key);
if (desc !== undefined)
return { holder: o, desc };
}
return undefined;
}
function readEnvPrimitive(env, key) {
const slot = findEnvSlot(env, key);
if (slot === undefined)
return undefined;
return slot.desc.get !== undefined ? reflectApply(slot.desc.get, env, []) : slot.desc.value;
}
function createCwdReader(env) {
const slot = findEnvSlot(env, "cwd");
if (slot === undefined)
return undefined;
const getter = slot.desc.get;
const initial = getter !== undefined ? reflectApply(getter, env, []) : slot.desc.value;
if (typeof initial !== "string")
return undefined;
let last = initial;
return () => {
const desc = getOwnPropertyDescriptor(slot.holder, "cwd");
const value = desc === undefined
? undefined
: desc.get !== undefined
? desc.get === getter
? reflectApply(desc.get, env, [])
: undefined
: desc.value;
if (typeof value === "string")
last = value;
return last;
};
}
export function createHookEnvCapabilities(env) {
const face = Object.create(null);
const canonicalPath = readEnvPrimitive(env, "canonicalPath");
const exists = readEnvPrimitive(env, "exists");
const readLink = readEnvPrimitive(env, "readLink");
const cwd = createCwdReader(env);
if (typeof canonicalPath === "function") {
face.canonicalPath = (path, abortSignal) => reflectApply(canonicalPath, env, [path, abortSignal]);
}
if (typeof exists === "function") {
face.exists = (path, abortSignal) => reflectApply(exists, env, [path, abortSignal]);
}
if (typeof readLink === "function") {
face.readLink = (path, abortSignal) => reflectApply(readLink, env, [path, abortSignal]);
}
if (cwd !== undefined) {
face.cwd = cwd;
}
return Object.freeze(face);
}
const IDENTITY_AGENT_NAME_MAX = 80;
export function mintHookInvocationIdentity(facts) {
return Object.freeze(Object.assign(Object.create(null), {
sessionId: facts.sessionId,
taskId: facts.taskId,
legKind: facts.legKind,
isDelegatedChild: facts.isDelegatedChild,
...(facts.insideFork === true ? { insideFork: true } : {}),
...(facts.agentName !== undefined ? { agentName: inlineUntrusted(facts.agentName.slice(0, 320), IDENTITY_AGENT_NAME_MAX) } : {}),
...(facts.spawnName !== undefined ? { spawnName: inlineUntrusted(facts.spawnName.slice(0, 320), IDENTITY_AGENT_NAME_MAX) } : {}),
...(facts.agentType !== undefined ? { agentType: inlineUntrusted(facts.agentType.slice(0, 320), IDENTITY_AGENT_NAME_MAX) } : {}),
...(facts.parentToolCallId !== undefined ? { parentToolCallId: facts.parentToolCallId } : {}),
}));
}
function screenPreToolUseResult(r) {
if (r === undefined || r === null || r === false)
return r;
return refuseOutOfContractDecision(r, { reasonIsNonInput: true });
}
function preToolUseAnswerViolation(answer) {
const r = screenPreToolUseResult(answer);
if (r === undefined || r === null || r === false || !isEngineRefusal(r))
return undefined;
return typeof r.message === "string" ? r.message : "the PreToolUse answer was refused by the engine";
}
function preToolUseRefusedReason(subject, failed) {
if (failed === undefined) {
return `the task was cancelled while a PreToolUse hook was still screening ${subject}; the call was NOT executed ` + `(fail-closed). No verdict was reached — this is the run ending, not the tool failing.`;
}
const tail = failed.cause === "threw"
? ` This is a failure of the deployment's hook, NOT of the tool itself — an identical retry reaches the same hook and fails the same way.`
: failed.cause === "timeout"
? ` This is a failure of the deployment's hook, NOT of the tool itself — the screening face never returned a verdict, so nothing was decided about this call either way.`
: "";
return `a PreToolUse hook ${failedHandlerClause(failed)} while screening ${subject}; the call was NOT executed (fail-closed).${tail}`;
}
export function createPreToolUseScreening(input) {
const bus = input.modBus;
if (bus === undefined || !bus.hasHandlers("classic.PreToolUse"))
return undefined;
const { toolCallId, toolName } = input.event;
const screenedName = wireOf("gate.preToolUse", input.event);
const eventOf = (args) => {
const cwd = input.trackedCwd?.();
return {
...classicEventBase(input.identity, input.observedPermissionMode?.()),
tool: screenedName,
tool_use_id: toolCallId,
input: judgedInputOf(args),
...(typeof cwd === "string" ? { cwd } : {}),
...(input.hookEnv !== undefined ? { env: input.hookEnv } : {}),
};
};
return {
screen: async (args, subject) => {
let outcome;
try {
outcome = await dispatchClassic(bus, "classic.PreToolUse", eventOf(args), { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), checkAnswer: preToolUseAnswerViolation });
}
catch (err) {
return { kind: "refused", reason: preToolUseRefusedReason(subject, { kind: "failed", registration: "engine", cause: "threw", reason: `could not raise the screening: ${describeThrown(err)}`, skipped: [] }) };
}
if (outcome === undefined || outcome.kind === "aborted")
return { kind: "refused", reason: preToolUseRefusedReason(subject, undefined) };
if (outcome.kind === "failed")
return { kind: "refused", reason: preToolUseRefusedReason(subject, outcome) };
const r = screenPreToolUseResult(outcome.answer);
return { kind: "answered", answer: r === null || r === false ? undefined : r, ...(outcome.answeredBy !== undefined ? { answeredBy: outcome.answeredBy } : {}) };
},
deniedReason: (answer) => decisionText(answer) ?? `tool "${toolName}" blocked by a PreToolUse hook`,
};
}
export function createPreToolUseConstraintPolicy(chain, env) {
const judges = (p) => !p.observe;
const minted = brandPolicyAskClass({
check: async (req, signal) => {
if (!chain.hasHandlers("classic.PreToolUse", judges))
return { action: "allow" };
const unasked = unaskedSeatAnswer(req, signal, { aborted: `the inherited PreToolUse screening of "${req.toolName}" was not consulted: the run or turn was already interrupted`, unreachable: `the inherited PreToolUse screening of "${req.toolName}" was not consulted: nobody can be asked about this call` }, "hook");
if (unasked !== undefined)
return unasked;
const subject = `this call to "${req.toolName}"`;
let outcome;
try {
outcome = await chain.dispatch("classic.PreToolUse", { tool: req.toolName, tool_use_id: req.toolCallId, input: judgedInputOf(req.args), ...(env !== undefined ? { env } : {}) }, {
bottom: engineCore,
participant: judges,
...(signal !== undefined ? { signal } : {}),
checkAnswer: (a) => (a === ENGINE_CORE_ANSWER ? undefined : preToolUseAnswerViolation(a)),
});
}
catch (err) {
return { action: "deny", message: preToolUseRefusedReason(subject, { kind: "failed", registration: "engine", cause: "threw", reason: `could not raise the screening: ${describeThrown(err)}`, skipped: [] }), decisionReason: "hook" };
}
if (outcome.kind === "aborted")
return abortedSeatDeny(req, `the inherited PreToolUse screening of "${req.toolName}" ended when the run or turn was interrupted`, "hook");
if (outcome.kind === "failed")
return { action: "deny", message: preToolUseRefusedReason(subject, outcome), decisionReason: "hook" };
const r = screenPreToolUseResult(outcome.answer);
if (r === undefined || r === null || r === false)
return { action: "allow" };
if (r.action === "deny") {
return { action: "deny", message: decisionText(r) ?? `tool "${req.toolName}" blocked by a PreToolUse hook`, decisionReason: "hook" };
}
if (r.action === "ask") {
return {
action: "ask",
message: decisionText(r) ?? `approval required for "${req.toolName}" (inherited PreToolUse hook)`,
decisionReason: "hook",
...(r.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
...(r.updatedInput !== undefined ? { updatedInput: r.updatedInput } : {}),
};
}
const stated = r.action === "allow" ? { decisionReason: "hook" } : {};
return r.updatedInput !== undefined ? { action: "allow", updatedInput: r.updatedInput, ...stated } : { action: "allow", ...stated };
},
}, "hook");
recordScreeningChain(minted, chain);
return minted;
}
export function askCarryRowMembers(carry) {
if (carry === undefined)
return {};
const origin = carry.origin;
const why = carry.classifierNoRuling;
const classed = screenAskClassMembers(carry.askClass, carry.classes);
return {
...(carry.denialLimitFallback !== undefined ? { denialLimitFallback: { ...carry.denialLimitFallback } } : {}),
...(isAutoModeFallbackCause(why) ? { classifierNoRuling: why } : {}),
...(isAskOrigin(origin) ? { origin } : {}),
...(classed !== undefined ? classed : {}),
};
}
export async function runToolGate(input) {
const { event, adjudicate, resolveAsk, suspendAsk } = input;
const { toolCallId, toolName } = event;
const callSignal = input.callSignal;
const modelInput = event.input;
const owned = ownArgs(modelInput);
const args = owned.ok ? owned.value : modelInput;
const notifier = createSafeNotifier(input.onNotifyError !== undefined ? { onError: input.onNotifyError } : undefined);
const notifyPermissionDeniedSeat = (payload) => {
const mode = input.observedPermissionMode?.();
const seen = mode !== undefined ? { ...payload, permissionMode: mode } : payload;
const onHostObserverFailure = (err) => void notifier.notify(() => { throw err; }, "toolGate.permissionDenied");
return observePermissionDenied({ legBus: input.modBus, hostObserver: input.permissionDenied, payload: seen, signal: input.abortSignal, onHostObserverFailure });
};
const screening = createPreToolUseScreening(input);
const pass = {
input,
event,
adjudicate,
resolveAsk,
suspendAsk,
toolName,
toolCallId,
callSignal,
notifier,
ledger: createSettlementLedger({ toolCallId, toolName }),
notifyPermissionDenied: notifyPermissionDeniedSeat,
screening,
afterFold: runGateLanes,
currentInput: args,
req: Object.freeze({ toolName, args, toolCallId, ...(() => { const f = input.toolFaceOf?.(toolName); const read = event.readArguments; if (f !== undefined && read !== undefined)
stampArgsReading(f, ownedReading(read)); return f !== undefined ? { face: f } : {}; })() }),
preToolContext: [],
hookAsk: undefined,
parkFailed: undefined,
deniedBy: undefined,
denyCause: undefined,
decision: { action: "allow" },
tightenedBy: undefined,
policyRewrite: undefined,
};
if (!owned.ok)
return await unownedArgsExit(pass, owned.reason);
if (screening !== undefined) {
const screened = await screening.screen(pass.currentInput, `this call to "${toolName}"`);
if (screened.kind === "refused") {
return await engineFailClosedExit(pass, screened.reason);
}
const r = screened.answer;
if (r) {
if (r.additionalContext) {
pass.preToolContext.push(r.additionalContext);
}
if (r.action === "deny") {
return hookDenyExit(pass, screening.deniedReason(r));
}
if (r.updatedInput !== undefined) {
pass.currentInput = argsAsTheToolReads(pass.req, r.updatedInput);
}
if (r.action === "ask") {
pass.hookAsk = r;
}
if (r.action === "allow")
pass.hookAllowedBy = screened.answeredBy !== undefined ? [screened.answeredBy] : [];
}
}
return await runGateFold(pass);
}