UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

103 lines (102 loc) • 7.01 kB
/** * The per-call hook stations a tool call passes OUTSIDE the tool gate. ONE rule for every executed call, live or * approved on a parked checkpoint: before it runs, the PreToolUse screening seat; after it runs, the `tool_result` * station (the PostToolUse / PostToolUseFailure seats and the PreToolUse context drain). * · a LIVE call meets the screening inside the gate and the station through the loop's finalize (the gate-stations * phase registers the station on the harness); * · an APPROVED call meets {@link createApprovedCallScreen} — the SAME seat (`createPreToolUseScreening`), folded under * the decide that approved it — and the station through `AgentHarness.finalizeToolCall`, the loop's own finalize. */ import type { DocumentContent, ImageContent, TextContent } from "../../internal/llm.js"; import { type HookEnvCapabilities, type HookInvocationIdentity } from "../hooks.js"; import { type LegModBus } from "../mods/stations.js"; import type { PermissionModeController } from "../mode-policy.js"; import type { TaskSpec } from "../types.js"; import type { CwdRef } from "../../tools/fs/fs-shared.js"; import { type ToolRosterDeltaSeat } from "../tool-roster.js"; import { type FrameSettlement } from "../frame-settlement.js"; import type { Prepared } from "./contracts.js"; import type { Checkpoint } from "../checkpoint-store.js"; /** One executed call's result as the station reads it (the harness `tool_result` event's members). */ export interface ToolResultSeatEvent { toolCallId: string; toolName: string; input: Record<string, unknown>; content: Array<TextContent | ImageContent | DocumentContent>; details: unknown; isError: boolean; } /** The station: the patch the loop applies to the result (`undefined` = unchanged). */ export type ToolResultStation = (e: ToolResultSeatEvent) => Promise<{ content: Array<TextContent | ImageContent | DocumentContent>; } | undefined>; export interface ToolResultStationInput { /** borrowed-readonly — the leg's mods bus: `classic.PostToolUse` / `classic.PostToolUseFailure` are raised on it, and a * `classic.PreToolUse` handler arms the station too (its context is drained onto the result here). */ modBus: LegModBus; /** borrowed-mutable — the blocked/suspended call ids. Writer here: the one-shot `delete`. */ blockedToolCalls: Set<string>; /** borrowed-readonly — the leg's delta seat: the post events carry the call's wire name off its current roster. */ toolRosterDeltas: ToolRosterDeltaSeat; /** borrowed-mutable — PreToolUse `additionalContext` per call id. Writer here: the one-shot drain (`delete`). */ preToolContexts: Map<string, string[]>; /** borrowed-readonly — prepare's abort seat; `.signal` is the post events' abort and the failure event's interrupt fact. */ abortController: { readonly signal: AbortSignal; }; /** borrowed-readonly — the REBOUND spec's `signal` (the failure event's interrupt fact). */ spec: Pick<TaskSpec, "signal">; /** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per post event. */ handsCwdRef: CwdRef | undefined; /** borrowed-readonly — the env face the post events carry, or undefined. */ hookEnvFace: HookEnvCapabilities | undefined; /** borrowed-readonly — the leg's identity envelope every post event carries. */ hookIdentity: HookInvocationIdentity; /** borrowed-readonly — the leg's live permission-mode seat (each event reads `observed`). */ permissionMode: PermissionModeController; /** borrowed-readonly — the leg's reminder provenance mark (every hook context line carries it). */ reminderMark: string; /** borrowed-mutable — the leg's settlement seam. Writer here: a carrier whose output a host hook rewrote (`noteHostRewrite`). */ frameSettlement: FrameSettlement; } /** The `tool_result` station, or undefined when no post / screening hook is wired (nothing to register). */ export declare function createToolResultStation(input: ToolResultStationInput): ToolResultStation | undefined; /** What the resumed leg does with the approved call: refuse it with a sentence (it is NOT executed), or run these bytes. * `admit` stashes the screen's context for the result's drain — called only once the call is past every belt and about * to run (a refused call stashes nothing, the live gate's rule). */ export type ApprovedCallScreen = NonNullable<Prepared["approvedCallScreen"]>; export type ApprovedCallVerdict = Awaited<ReturnType<ApprovedCallScreen>>; export interface ApprovedCallScreenInput { /** borrowed-readonly — the leg's mods bus (the re-screen raises `classic.PreToolUse` on it, as the gate does). */ modBus: LegModBus; /** borrowed-readonly — the leg's live permission-mode seat (each screening e reads `observed`). */ permissionMode: PermissionModeController; /** borrowed-readonly — prepare's abort seat (the screening is abort-ended on it). */ abortController: { readonly signal: AbortSignal; }; /** borrowed-readonly — the hand's live tracked cwd, or undefined. */ handsCwdRef: CwdRef | undefined; /** borrowed-readonly — the read-only env capability face, or undefined. */ hookEnvFace: HookEnvCapabilities | undefined; /** borrowed-readonly — the leg's identity envelope. */ hookIdentity: HookInvocationIdentity; /** borrowed-mutable — PreToolUse `additionalContext` per call id. Writer here: `admit`'s stash (the station drains it). */ preToolContexts: Map<string, string[]>; } /** * The approved call's PreToolUse screening — the gate's own screening, its answer FOLDED UNDER THE DECIDE that approved * the call (the decide is the upper bound; the screen can only narrow): a deny, a failed or cancelled screening, or a * refused answer refuses the call in the live gate's own sentences; an ask is the question the decide already answered — * unless it demands a person and the decide was the deployment's policy (the one predicate every resume station holds a * decide against); a rewrite is what executes when the decide carried none of its own (the person's bytes are the upper * bound); context rides the result. Undefined when no handler hangs on `classic.PreToolUse`. */ export declare function createApprovedCallScreen(input: ApprovedCallScreenInput): ApprovedCallScreen | undefined; /** * Why the frozen inherited chain cannot judge bytes no gate judged (an approver's edit, the re-screen's rewrite): the * row predates the chain, a layer is opaque, or a layer declares a live remainder — undefined when the frozen * projections can judge them, or when no parent constraint binds. ONE rule for the pre-claim edit rung (which refuses * the edit, the row pending) and the resumed leg's belt (which refuses a rewrite, the approval spent). */ export declare function chainCannotJudge(ig: Checkpoint["state"]["inheritedGate"]): string | undefined;