@sema-agent/core
Version:
Stateless, task-oriented AI agent core
107 lines (106 loc) • 7.27 kB
TypeScript
/**
* 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, type Hooks } from "../hooks.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 resolved hook record: the two post seats and the failure seat, called through the record. */
hooks: Hooks | undefined;
/** 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 seats receive 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 — the validated per-prepare hook seat bound. */
hookTimeoutMs: number;
/** borrowed-readonly — prepare's abort seat; `.signal` bounds the post seats and is the failure seat's interrupt fact. */
abortController: {
readonly signal: AbortSignal;
};
/** borrowed-readonly — the REBOUND spec's `signal` (the failure seat's interrupt fact). */
spec: Pick<TaskSpec, "signal">;
/** borrowed-readonly — the hand's live tracked cwd, or undefined; `current` is read per post seat. */
handsCwdRef: CwdRef | undefined;
/** borrowed-readonly — the env face the post seats' contexts carry, or undefined. */
hookEnvFace: HookEnvCapabilities | undefined;
/** borrowed-readonly — the leg's identity envelope every HookToolContext carries. */
hookIdentity: HookInvocationIdentity;
/** borrowed-readonly — the leg's live permission-mode seat (each context reads `observed`). */
permissionMode: PermissionModeController;
/** borrowed-readonly — where an expired post seat is reported (the leg's own hook-crash lane). */
notifyOwnHookCrash: (err: unknown) => void;
/** 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 face the gate screens with (the observational wrapper when declared; the leg's mode on every context). */
preToolUse: Hooks["preToolUse"] | undefined;
/** borrowed-readonly — the validated per-prepare hook seat bound. */
hookTimeoutMs: number;
/** borrowed-readonly — prepare's abort seat (the screening seat 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-readonly — the own hook-crash reporter (a crashing or timed-out screening face is reported through it). */
notifyOwnHookCrash: (err: unknown) => void;
/** 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 seat, its answer FOLDED UNDER THE DECIDE that approved the
* call (the decide is the upper bound; the screen can only narrow): a deny, a crash, a silence 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 face is wired.
*/
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;