UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

119 lines (118 loc) • 7.52 kB
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; }