UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

129 lines (128 loc) • 9.85 kB
/** * PrepareTask's PROTOCOL-TOOLS phase, verbatim from the driver: the MCP materialization * with its operator face (fail-open warnings once per session, the intake drops, the announce-lane prune), the * reserved-name refusal, the roster push through the large-result wrapper, the RefreshMcpTools seam with its * refresh closure, the two run-time seats the rebuild closures reach (`rebuildHarnessToolsRef`, * `contentOriginWrapRef`) and the gate-armed honesty flag, the protocol-SHARED axis fold, and the A2A twin * (materialize, warnings straight to the sink, its own reserved-name refusal, the same fold). The interface * is the dependency list the segment had implicitly. * * ASYNC PHASE: the segment awaits the two materializers, exactly where the driver awaited them; the phase adds * no yield BEFORE the first of those awaits. Read-stability of the host handles for the call: * {@link RunInternals} (`@contract prepare.deps-read-stable`). * * The handles and the rollback stack. This phase is the first that acquires a handle INSIDE * its body: the driver used to see `mcp` / `a2a` only because they were hoisted `let`s at function scope, so * its catch could dispose a half-acquired pair. Those hoists are gone. The moment a materializer returns a * real handle the phase pushes its `dispose` onto the driver-owned `rollback`; a prepare that throws AFTER * this phase reaches the driver's catch, whose `unwindAll` disposes the peers, then the clients — the reverse * acquisition order the hoisted legs kept, each bounded and reported. The two reserved-name arms only THROW: * the release of what they refused is the rollback entry, called exactly once by that unwind (they used to * dispose inline as well — a hoist-era leftover that leaned on dispose being idempotent; the transcript pins * the single call). On the success path the stack is committed and the handles belong to `Prepared` (the * Runner's finish releases them). */ import type { ToolMarkSource } from "../ask-origin.js"; import type { AgentTool } from "../../internal/harness.js"; import { type MaterializedMcp, type McpDroppedTool, type McpReconnectResult } from "../mcp.js"; import { type MaterializedA2a } from "../a2a.js"; import type { LockedPreflight } from "../locked-config.js"; import type { RunnerDeps, TaskSpec, ToolEffect } from "../types.js"; import type { AnnounceOnceSink, IrreversibilityTier, Prepared, RollbackStack, ToolFaceSnapshot } from "./contracts.js"; import { RosterBuilder, type ToolRosterDeltaSeat } from "../tool-roster.js"; export interface PrepareProtocolToolsInput { /** borrowed-readonly — the frozen preflight; only `mcp` (the declared server entries) is read: the * materialize input, the RefreshMcpTools mount predicate, and whether a real client was acquired. */ lockedPreflight: Pick<LockedPreflight, "mcp">; /** borrowed-readonly — the REBOUND spec. Read: `principal` (the MCP header / the A2A identity), `tools` * (the caller names both reserved-name guards check against), `a2a` (the peer entries). Never mutated. */ spec: Pick<TaskSpec, "principal" | "tools" | "a2a">; /** borrowed-readonly — the deployment seats the materializers and their operator faces read: `onElicit` * and `mcpImageResizer` ride into the MCP intake; `onError` takes the A2A warnings (no ledger — a peer is * re-read every prepare); `mcpRevocations` + `onNotice` make the revocation wiring. */ deps: Pick<RunnerDeps, "onElicit" | "mcpImageResizer" | "onError" | "mcpRevocations" | "onNotice">; /** borrowed-readonly — the leg's reminder mark (the MCP intake's disclosure bounding reads it). */ reminderMark: string; /** borrowed-mutable — the reminder-disclosure counters: the MCP intake's bounding writes them here, and * the run loop's attachment lanes keep writing the same object (`Prepared.reminderDisclosureCounts`). */ reminderDisclosureCounts: Prepared["reminderDisclosureCounts"]; /** borrowed-readonly — the run's invocation id (the revocation probe-failure notice names it). */ runId: string; /** borrowed-readonly — the acquired session id (operator-line context). */ sessionId: string; /** borrowed-mutable — the once-per-session ledger: its deduplicated operator sink (`onError`, one line per * (code, message) per session) and the announce-lane prune of tools already delivered on this branch. * The ledger module owns the record; this phase writes through those two methods and nothing else. */ onceLedger: AnnounceOnceSink & { pruneDroppedTools(dropped: McpDroppedTool[]): void; }; /** borrowed-mutable — the run's shared roster (identity is the contract: `Prepared.tools`, the wire order). * Writers here: three pushes (the MCP tools through the wrapper, RefreshMcpTools, the A2A tools through the * wrapper) and, at RUN time, the refresh closure's splice within a server's name-prefix domain. Later * phases push their own mounts; the roster's full writer table is the delegation-surface phase's. */ roster: RosterBuilder; /** borrowed-mutable — the run-time delta seat: the refresh closure re-mints the roster through it after * every re-splice so the stream learns of the change. Armed by the driver after the roster is minted. */ rosterSeat: ToolRosterDeltaSeat; /** borrowed-readonly — the large-result wrapper for protocol tools (per-tool `_meta` bar, 50K default). */ remoteToolOffload: (tool: AgentTool) => AgentTool; /** borrowed-readonly — the frozen tool face; `exclude` is read at REFRESH time so a deployment-excluded * tool is never re-mounted by a model-triggered refresh. */ toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">; /** borrowed-mutable — the effect table the fold writes under a has-guard (`?? "write"` default) and the * refresh closure resets per prefix domain before re-folding; RefreshMcpTools registers itself `read`. */ toolEffects: Map<string, ToolEffect>; /** borrowed-mutable — the explicit-negative axes (a caller's vouched `never` / `egress:false`); written by * the fold and reset per prefix domain by the refresh closure. */ axisExplicitNegatives: Map<string, { irreversible?: false; egress?: false; }>; /** borrowed-mutable — the irreversibility tier, tightened to `always` by the fold (monotone). */ irreversibilityTier: IrreversibilityTier; /** borrowed-mutable — the irreversible set, added to by the fold (tighten-only). */ irreversibleTools: Set<string>; /** borrowed-mutable — the egress set, added to by the fold (tighten-only; the effect:"write" invariant). */ egressTools: Set<string>; /** borrowed-mutable — WHO marked each tool (the safety scan's map); the protocol fold adds its own source word per * axis it tightens (`mcp_annotation` for a server's self-report, `a2a_default` for a peer skill's egress default). */ toolMarkSources: Map<string, Set<ToolMarkSource>>; /** borrowed-readonly — the run's abort controller; its `signal` rides into the A2A materialization. */ abortController: Pick<AbortController, "signal">; /** borrowed-mutable — the driver-owned prepare-failure stack; this phase pushes one entry per REAL handle * acquired (the MCP clients, then the A2A peers) and nothing else touches it here. */ rollback: RollbackStack; } export interface PrepareProtocolToolsResult { /** owned — the materialized MCP handle (`Prepared.mcp`): clients, axes, server instructions, statuses, the * refresh and the `dispose` the Runner's finish calls. Its prepare-failure release is the rollback entry * this phase pushed (only when a real client was acquired — the inert stub owns nothing). */ mcp: MaterializedMcp; /** owned — the materialized A2A handle: the driver projects it onto `Prepared.a2a` only when it carries * tools. Same rollback posture as `mcp`, pushed after it (disposed before it on unwind). */ a2a: MaterializedA2a; /** borrowed-mutable — the RefreshMcpTools rebuild seam (`current` undefined until installed). Writers: the * tool-disclosure mount, by exactly one of its two mutually exclusive arms (the deferred arm's * rematerialize; the else-arm's bare setTools + fingerprint sync). Read by the refresh closure at run time. */ rebuildHarnessToolsRef: { current?: () => Promise<void>; }; /** borrowed-mutable — the content-origin re-classification seat. Writer: the memory-engine-session phase * (installs the re-runnable pass ONCE when it arms — a mounted engine-memory session or an upstream recorder * channel; undefined otherwise). Read by the rebuild closures before a swapped-in tool becomes callable. */ contentOriginWrapRef: { current?: () => void; }; /** borrowed-mutable — whether the tool_call gate handler was REGISTERED at prepare time. Writer: the driver, * once, at gate registration. Read by the refresh closure's honesty warning. */ toolCallGateArmedRef: { armed: boolean; }; /** owned — the run handle's `reconnectMcpServer` implementation, present ONLY when this leg declared * MCP servers (a leg with none has nothing to re-dial, and the stream verb refuses typed rather * than answering about a materialization that was never made). The driver projects it onto * `Prepared.mcpRedial`, from which the run's wiring binds the verb's seat. */ mcpRedial?: (server: string) => Promise<McpReconnectResult>; } /** The M5 phase body — prepareTask's protocol-tools stretch, verbatim (see the module header). */ export declare function prepareProtocolTools(input: PrepareProtocolToolsInput): Promise<PrepareProtocolToolsResult>;