@sema-agent/core
Version:
Stateless, task-oriented AI agent core
250 lines (249 loc) • 17.9 kB
JavaScript
import { SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
import { mountedToolRequiresUserInteraction } from "../mode-policy.js";
import { complianceCallDenial } from "../compliance.js";
import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js";
import { screenGateOutcome } from "../gate-outcome.js";
import { directDeny } from "./gate-exit.js";
import { emitTrace } from "../trace.js";
import { deliverEngineNotice } from "../engine-notice.js";
import { askUnresolvableNotice } from "../ask-unresolvable-notice.js";
import { askReachesPerson } from "../wiring-manifest.js";
import { classifierUnavailableSeat } from "./classifier-ladder.js";
import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
import { callFaceSeat, rosterToolName, toolCallFaceOf } from "../tool-roster.js";
import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
import { createApprovedCallScreen, createToolResultStation } from "./call-hook-stations.js";
const PEER_MESSAGE_NAMES = new Set([SEND_MESSAGE_TOOL_NAME]);
function screenGateResult(result) {
if (result.suspend !== undefined)
return { defects: [] };
const gate = result.gate;
if (gate === undefined)
return { defects: ["a tool-gate result carried no outcome record on a terminal exit — every terminal exit mints one; the frame carries none"] };
const defects = screenGateOutcome(gate);
const blocked = result.block === true;
if ((gate.disposition.kind === "denied") !== blocked) {
defects.push(`a tool-gate result's outcome reads "${gate.disposition.kind}" on ${blocked ? "a blocked" : "an executing"} call — the two faces of one verdict disagree; the frame carries no outcome`);
}
return defects.length > 0 ? { defects } : { gate, defects };
}
export function maybeHumanRejectionHalt(input) {
const d = input.gate?.disposition;
const s = input.gate?.settlement;
const bare = d?.kind === "denied" && d.deniedBy === "ask_resolution" && s?.kind === "human_refused" && s.note === undefined;
if (!bare || input.isDelegatedChild)
return undefined;
return {
reason: `This tool call was NOT executed: the user rejected the "${input.toolName}" tool call in the same ` +
`assistant message, which stops the rest of the batch. Nothing was run for this call — ` +
`re-issue it after the user's direction only if it is still needed.`,
details: {
error: "gate.batch_halted",
code: "gate.batch_halted",
rejectedToolCallId: input.toolCallId,
rejectedToolName: input.toolName,
},
};
}
export function prepareGateStations(input) {
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, askRoute, permissionMode, toolMarkSources, a2aIsolatePeers, ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement, seatInputsFor } = input;
const gatePreToolUse = ownGatePreToolUse !== undefined ? (name, args, ctx) => ownGatePreToolUse(name, args, { ...ctx, permissionMode: permissionMode.observed }) : undefined;
const releasesAnnounced = new Map();
const onModeReleased = (info) => {
const table = info.readUnestablished === true ? input.readDenyInForce() : undefined;
if (table !== undefined && table.tiers.length + table.rows.length > 0) {
const named = [...(table.tiers.length > 0 ? [`built-in tiers ${table.tiers.join(", ")}`] : []), ...table.rows.slice(0, 8), ...(table.rows.length > 8 ? [`${table.rows.length - 8} more`] : [])].join("; ");
deliverEngineNotice(deps.onNotice, {
code: "permission.read_deny_unapplied",
message: `the ${info.release.mode} permission mode released "${info.toolName}" although a read-deny table is in force (${named}): the read boundary could not establish what this command reads, so no row of the table was applied to it`,
detail: { mode: info.release.mode, toolCallId: info.toolCallId, tiers: [...table.tiers], rows: table.rows.slice(0, 8), rowCount: table.rows.length, sessionId, runId },
});
}
const key = `${info.release.mode}\u0000${info.release.askClass}`;
const count = (releasesAnnounced.get(key) ?? 0) + 1;
releasesAnnounced.set(key, count);
if (count !== 1)
return;
deliverEngineNotice(deps.onNotice, {
code: "permission.ask_released_by_mode",
message: `the ${info.release.mode} permission mode released a ${info.release.askClass} ask on "${info.toolName}" without putting it to anyone (further releases of this pair in this run ride each call's tool_end.gate.disposition.released)`,
detail: { mode: info.release.mode, askClass: info.release.askClass, ...(info.release.origin !== undefined ? { origin: info.release.origin } : {}), ...(info.release.mandate !== undefined ? { mandate: info.release.mandate } : {}), count, firstToolCallId: info.toolCallId, sessionId, runId },
});
};
if (askLane !== undefined && parkAsk !== undefined) {
const { adjudicate, resolveAskBound, notifyPermissionDenied, notifyHookError } = askLane;
const { resolveContentAsk, suspendAsk } = parkAsk;
for (const t of tools) {
const eff = t.effect;
if (eff !== undefined && !toolEffects.has(t.name))
toolEffects.set(t.name, eff);
}
const writeProtectionArmed = writeProtectionCheck !== undefined && tools.some((t) => PATH_CONFINABLE_WRITE_TOOLS.has(t.name));
toolCallGateArmedRef.armed =
effectivePolicy !== undefined ||
hooks?.preToolUse !== undefined ||
egressTools.size > 0 ||
irreversibleTools.size > 0 ||
spec.enablePlanMode === true ||
complianceDenies.has("web_fetch") ||
writeProtectionArmed ||
shellReadCheck !== undefined;
if (toolCallGateArmedRef.armed) {
harness.on("tool_call", async (called) => {
const e = { ...called, toolName: rosterToolName(toolRosterDeltas.current, called.toolName) };
const call = { toolName: e.toolName, ...callFaceSeat(toolCallFaceOf(toolRosterDeltas.current, called.toolName)) };
const tierSeat = seatOf("gate.shellSeat", irreversibilityTier, call);
const markSources = new Set(heldValues("gate.markSources", toolMarkSources, call).flatMap((s) => [...s]));
blockedToolCalls.delete(e.toolCallId);
inheritedAskGrants.delete(e.toolCallId);
{
const complianceDeny = heldValues("gate.compliance", { get: (n) => complianceCallDenial(complianceDenies, n) }, call)[0];
if (complianceDeny !== undefined) {
if (blockedTracked)
blockedToolCalls.add(e.toolCallId);
const gate = directDeny("compliance");
gateOutcomes.set(e.toolCallId, gate);
if (notifyPermissionDenied) {
await notifyPermissionDenied({ toolName: e.toolName, input: cloneObserverInput(e.input), toolCallId: e.toolCallId, reason: complianceDeny, gate, identity: hookIdentity, permissionMode: permissionMode.observed });
}
return { block: true, reason: formatHookFeedback(complianceDeny, reminderMark), preToolContext: [] };
}
}
let result;
try {
result = await runToolGate({
onNotifyError: (f) => emitTrace(deps.tracer, () => ({ kind: "observer.notify_failed", version: 1, taskId: hostTaskId, site: f.site, message: f.error.message, ts: Date.now() })),
event: e,
...(e.signal !== undefined ? { callSignal: e.signal } : {}),
identity: hookIdentity,
reminderMark,
...(gatePreToolUse !== undefined ? { preToolUse: gatePreToolUse } : {}),
hookTimeoutMs,
...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name === e.toolName ? called.toolName : name),
adjudicate,
resolveAsk: resolveAskBound,
suspendAsk,
resolveContentAsk,
egress: heldBy("gate.egress", egressTools, call),
peerMessage: heldBy("gate.peerMessage", PEER_MESSAGE_NAMES, call),
...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
irreversibility: tierSeat?.value,
reversibilityProbe: tierSeat?.read(reversibilityProbes),
abortSignal: abortController.signal,
...(notifyPermissionDenied ? { permissionDenied: (p) => notifyPermissionDenied({ ...p, permissionMode: permissionMode.observed }) } : {}),
onHookError: notifyHookError,
shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
permissionMode: () => permissionMode.current,
...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
...seatInputsFor(e.toolName, wireOf("gate.skillActivationSeat", call)),
...(mountedToolRequiresUserInteraction(tools, e.toolName) ? { requiresUserInteraction: true } : {}),
...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
onModeReleased,
ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
onHeadlessDenialLimit: stopForDenialLimit,
onAskUnresolvable: (info) => deliverEngineNotice(deps.onNotice, askUnresolvableNotice({ sessionId, ...info })),
...(permissionRuleLane
? {
persistedRules: {
adjudicate: permissionRuleLane.adjudicate,
onResolved: (info) => emitTrace(deps.tracer, () => ({
kind: "permission.persisted_rule_allowed",
version: 2,
taskId: hostTaskId,
toolName: info.toolName,
toolCallId: info.toolCallId,
rules: [...info.rules],
ts: Date.now(),
})),
},
}
: {}),
onReadOnlyAllowed: (info) => emitTrace(deps.tracer, () => ({
kind: "permission.read_only_allowed",
version: 1,
taskId: hostTaskId,
toolName: info.toolName,
toolCallId: info.toolCallId,
command: info.command,
ts: Date.now(),
})),
...(permissionRuleOrgLane
? {
orgRules: {
adjudicate: permissionRuleOrgLane.adjudicate,
contentAskToolMounted: questionToolMounted,
onUnavailable: (info) => emitTrace(deps.tracer, () => ({
kind: "permission.org_snapshot_unavailable",
version: 1,
taskId: hostTaskId,
toolName: info.toolName,
toolCallId: info.toolCallId,
message: info.message,
ts: Date.now(),
})),
},
}
: {}),
isMarkedUnresolvable: (toolCallId) => inheritedUnavailableAsks.has(toolCallId),
...(sandboxAdmissionArmed
? {
sandboxAdmission: {
askClassesOf: (toolCallId) => foldAskClasses.get(toolCallId),
boundaryCapable: sandboxBoundaryCapable(call),
onAdmitted: emitSandboxAdmitted,
},
}
: {}),
});
}
finally {
inheritedUnavailableAsks.delete(e.toolCallId);
inheritedAskGrants.delete(e.toolCallId);
foldAskClasses.delete(e.toolCallId);
ancestorArbitrations.delete(e.toolCallId);
}
const ancestorAdmitted = ancestorSandboxAdmissions.get(e.toolCallId);
ancestorSandboxAdmissions.delete(e.toolCallId);
if (ancestorAdmitted !== undefined && !result.block && !result.suspend) {
emitSandboxAdmitted({ toolCallId: e.toolCallId, toolName: e.toolName, askClasses: ancestorAdmitted, boundary: "sandbox_internal" });
result.preToolContext.push(`permission.sandbox_admitted: this call's inherited pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
}
if (!result.block && !result.suspend && result.preToolContext.length > 0) {
preToolContexts.set(e.toolCallId, result.preToolContext);
}
if (blockedTracked && (result.block || result.suspend)) {
blockedToolCalls.add(e.toolCallId);
}
const screened = screenGateResult(result);
for (const defect of screened.defects)
deps.onError?.(new Error(defect), { phase: "config", sessionId });
if (screened.gate !== undefined)
gateOutcomes.set(e.toolCallId, screened.gate);
if (result.block) {
const haltRemaining = maybeHumanRejectionHalt({
gate: screened.gate,
isDelegatedChild: delegation.isDelegatedChild === true,
toolName: e.toolName,
toolCallId: e.toolCallId,
});
const details = result.details !== undefined ? { details: result.details } : {};
if (haltRemaining !== undefined) {
batchHaltRef.current = { rejectedToolCallId: e.toolCallId };
return { block: true, reason: result.reason, ...details, haltRemaining };
}
return { block: true, reason: result.reason, ...details };
}
return result.updatedInput !== undefined ? { updatedInput: result.updatedInput } : undefined;
});
}
}
const toolResultStation = createToolResultStation({ hooks, blockedToolCalls, toolRosterDeltas, preToolContexts, hookTimeoutMs, abortController, spec, handsCwdRef, hookEnvFace, hookIdentity, permissionMode, notifyOwnHookCrash, reminderMark, frameSettlement });
if (toolResultStation !== undefined)
harness.on("tool_result", toolResultStation);
const approvedCallScreen = createApprovedCallScreen({ preToolUse: gatePreToolUse, hookTimeoutMs, abortController, handsCwdRef, hookEnvFace, hookIdentity, notifyOwnHookCrash, preToolContexts });
return { toolCallStationArmed: toolCallGateArmedRef.armed, toolResultStationArmed: toolResultStation !== undefined, ...(approvedCallScreen !== undefined ? { approvedCallScreen } : {}) };
}