UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

135 lines (134 loc) • 7.57 kB
import { createPreToolUseScreening, formatHookFeedback } from "../hooks.js"; import { classicEventBase, dispatchClassic, stringMembersAnswer, stringMembersOf } from "../mods/stations.js"; import { policyCannotAnswer } from "../real-approval.js"; import { rosterToolName } from "../tool-roster.js"; import { wireOf } from "../tool-name-readings.js"; import { noteHostRewrite } from "../frame-settlement.js"; function postToolUseAnswer(answer) { const strings = stringMembersAnswer("classic.PostToolUse", ["additionalContext"])(answer); if (strings !== undefined || typeof answer !== "object" || answer === null) return strings; const out = Reflect.get(answer, "updatedOutput"); return out === undefined || Array.isArray(out) ? undefined : "answered a non-array \"updatedOutput\"; it replaces the content array"; } function updatedOutputOf(answer) { if (typeof answer !== "object" || answer === null) return undefined; const out = Reflect.get(answer, "updatedOutput"); return Array.isArray(out) ? out.filter(isContentBlock) : undefined; } function isContentBlock(c) { return typeof c === "object" && c !== null && typeof Reflect.get(c, "type") === "string"; } export function createToolResultStation(input) { const { modBus, blockedToolCalls, toolRosterDeltas, preToolContexts, abortController, spec, handsCwdRef, hookEnvFace, hookIdentity, permissionMode, reminderMark, frameSettlement } = input; const toolResultStationArmed = modBus.hasHandlers("classic.PostToolUse") || modBus.hasHandlers("classic.PreToolUse") || modBus.hasHandlers("classic.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) }; const callFacts = (tool) => ({ ...classicEventBase(hookIdentity, permissionMode.observed), tool, tool_use_id: e.toolCallId, input: e.input, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), }); if (e.isError) { if (modBus.hasHandlers("classic.PostToolUseFailure")) { let clonedDetails = e.details; try { clonedDetails = structuredClone(e.details); } catch { } const failure = { 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, }; const outcome = await dispatchClassic(modBus, "classic.PostToolUseFailure", { ...callFacts(wireOf("hook.postToolUseFailure", post)), failure }, { signal: abortController.signal, checkAnswer: stringMembersAnswer("classic.PostToolUseFailure", ["additionalContext"]) }).catch(() => undefined); const patch = outcome?.kind === "answered" ? stringMembersOf(outcome.answer, ["additionalContext"]) : undefined; if (patch?.additionalContext) { content = [...content, { type: "text", text: formatHookFeedback(patch.additionalContext, reminderMark) }]; changed = true; } } } else if (modBus.hasHandlers("classic.PostToolUse")) { const outcome = await dispatchClassic(modBus, "classic.PostToolUse", { ...callFacts(wireOf("hook.postToolUse", post)), output: { content: e.content, details: e.details, isError: e.isError } }, { signal: abortController.signal, checkAnswer: postToolUseAnswer }).catch(() => undefined); const answer = outcome?.kind === "answered" ? outcome.answer : undefined; const updatedOutput = updatedOutputOf(answer); const patch = stringMembersOf(answer, ["additionalContext"]); if (updatedOutput) { content = 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 { modBus, permissionMode, abortController, handsCwdRef, hookEnvFace, hookIdentity, preToolContexts } = input; if (!modBus.hasHandlers("classic.PreToolUse")) return undefined; return async ({ toolCallId, toolName, args, decidedBy }) => { const screening = createPreToolUseScreening({ event: { toolCallId, toolName, input: {} }, modBus, observedPermissionMode: () => permissionMode.observed, ...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}), identity: hookIdentity, abortSignal: abortController.signal, }); if (screening === undefined) return { args, admit: () => undefined }; const subject = `this call to "${toolName}"`; const screened = await screening.screen(args, subject); if (screened.kind === "refused") return { refused: screened.reason }; const r = screened.answer; 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; }