@sema-agent/core
Version:
Stateless, task-oriented AI agent core
119 lines (118 loc) • 7.52 kB
JavaScript
import { createPreToolUseScreening, formatHookFeedback, hookSeatExpiredError, runHookSeat } from "../hooks.js";
import { isEngineRefusal } from "../tool-policy.js";
import { policyCannotAnswer } from "../real-approval.js";
import { traceHookCrash } from "../gate-crash-trace.js";
import { createSafeNotifier } from "../safe-notify.js";
import { rosterToolName } from "../tool-roster.js";
import { wireOf } from "../tool-name-readings.js";
import { noteHostRewrite } from "../frame-settlement.js";
export function createToolResultStation(input) {
const { hooks, blockedToolCalls, toolRosterDeltas, preToolContexts, hookTimeoutMs, abortController, spec, handsCwdRef, hookEnvFace, hookIdentity, permissionMode, notifyOwnHookCrash, reminderMark, frameSettlement } = input;
const toolResultStationArmed = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure);
if (toolResultStationArmed) {
return async (e) => {
if (blockedToolCalls.delete(e.toolCallId)) {
return undefined;
}
let content = e.content;
let changed = false;
const post = { toolName: rosterToolName(toolRosterDeltas.current, e.toolName) };
if (e.isError) {
if (hooks?.postToolUseFailure) {
let clonedDetails = e.details;
try {
clonedDetails = structuredClone(e.details);
}
catch {
}
const failedName = wireOf("hook.postToolUseFailure", post);
const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(failedName, e.input, {
error: e.content
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n"),
isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
content: e.content.map((c) => ({ ...c })),
details: clonedDetails,
}, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
if (failureSeat.expired) {
notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
}
const patch = failureSeat.expired ? undefined : failureSeat.value;
if (patch?.additionalContext) {
content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
changed = true;
}
}
}
else if (hooks?.postToolUse) {
const ranName = wireOf("hook.postToolUse", post);
const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(ranName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: ranName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity, permissionMode: permissionMode.observed }));
if (successSeat.expired) {
notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
}
const patch = successSeat.expired ? undefined : successSeat.value;
if (patch?.updatedOutput) {
content = patch.updatedOutput;
changed = true;
noteHostRewrite(frameSettlement, e.toolCallId);
}
if (patch?.additionalContext) {
content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }];
changed = true;
}
}
const pre = preToolContexts.get(e.toolCallId);
if (pre) {
preToolContexts.delete(e.toolCallId);
content = [...content, ...pre.map((t) => ({ type: "text", text: formatHookFeedback(t, reminderMark) }))];
changed = true;
}
return changed ? { content } : undefined;
};
}
return undefined;
}
export function createApprovedCallScreen(input) {
const { preToolUse, hookTimeoutMs, abortController, handsCwdRef, hookEnvFace, hookIdentity, notifyOwnHookCrash, preToolContexts } = input;
if (preToolUse === undefined)
return undefined;
return async ({ toolCallId, toolName, args, decidedBy }) => {
const notifier = createSafeNotifier();
const seatInput = { event: { toolCallId, toolName, input: args }, ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}), identity: hookIdentity, onHookError: notifyOwnHookCrash, abortSignal: abortController.signal };
const screening = createPreToolUseScreening(preToolUse, seatInput, hookTimeoutMs, notifier);
const subject = `this call to "${toolName}"`;
let r;
try {
const seat = await screening.screen(args);
if (seat.expired)
return { refused: await screening.unansweredReason(subject, seat.cause) };
r = screening.screened(seat.value);
}
catch (err) {
traceHookCrash(seatInput, err, notifier);
return { refused: screening.crashReason(subject, err) };
}
if (r !== undefined && isEngineRefusal(r))
return { refused: screening.refusedReason(r) };
if (r?.action === "deny")
return { refused: screening.deniedReason(r) };
if (r !== undefined && policyCannotAnswer(r, decidedBy))
return { refused: `a PreToolUse hook requires a person's approval for ${subject}, and this approval was the deployment's policy — not executed` };
const context = r?.additionalContext;
return { args: r?.updatedInput !== undefined ? r.updatedInput : args, admit: () => {
if (context)
preToolContexts.set(toolCallId, [context]);
} };
};
}
export function chainCannotJudge(ig) {
if (ig?.requiresParentConstraint !== true)
return undefined;
const chain = ig.constraintChain;
if (chain === undefined)
return "the checkpoint predates the frozen projection chain (no constraintChain recorded)";
if (chain.some((e) => "opaque" in e))
return "the inherited chain carries an OPAQUE layer (no projection exported)";
return chain.some((e) => !("opaque" in e) && e.requiresLiveRemainder) ? "an inherited chain layer declares a live remainder beyond its projection" : undefined;
}