UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

110 lines (109 loc) • 5.91 kB
/** * The LIVE MCP roster lane: applying one server's intake result to a RUNNING roster, and the * host-driven re-dial that is the second caller of it. * * Two faces change a task's MCP roster after prepare has finished — the model's `RefreshMcpTools` * tool and the run handle's `reconnectMcpServer` verb — and what they must do to the roster is the * same list in the same order: drop the deployment-excluded names, re-fold the safety axes (with the * non-monotone restore arm on refusal), replace the server's whole name-prefix domain at its own * anchor, rebuild the harness toolset, and re-mint the roster delta. That list lived inside the * refresh tool's execute closure, where the second face could only have copied it; a copy would have * been a second answer to "what does a mid-task roster change consist of". * * It is machinery, not a phase: the protocol-tools phase reaches DOWN for it to build the refresh * tool, and the run's verb wiring reaches down for the same functions. */ import type { AgentTool } from "../../internal/harness.js"; import type { McpDroppedTool, McpReconnectResult, MaterializedMcp, McpToolAxis } from "../mcp.js"; import type { RunnerDeps, ToolEffect } from "../types.js"; import type { IrreversibilityTier, ToolFaceSnapshot } from "./contracts.js"; import type { RosterBuilder, ToolRosterDeltaSeat } from "../tool-roster.js"; /** One server's assertion about its own prefix domain — the shape both faces reduce to. `tools` is * the roster the domain must END UP with (empty withdraws it); `added`/`removed` are the reporting * half and never drive the swap. */ export interface McpRosterDelta { server: string; prefix: string; tools: AgentTool[]; axes: McpToolAxis[]; dropped: McpDroppedTool[]; } /** The seats a roster application writes through. Every one of them is borrowed from the phase that * minted it — this module owns nothing. */ export interface McpRosterApplyInput { /** borrowed-readonly — the frozen tool face; `exclude` keeps a deployment-excluded tool from ever * being re-mounted by a face the model or a host can trigger. */ toolFaceSnapshot: Pick<ToolFaceSnapshot, "exclude">; /** borrowed-readonly — the per-server face overlay (the phase's `withFaces`). */ withFaces: (tools: AgentTool[], server: string) => AgentTool[]; /** borrowed-readonly — the large-result wrapper every protocol tool is mounted through. */ remoteToolOffload: (tool: AgentTool) => AgentTool; /** borrowed-readonly — the phase's axis fold (throws on a config contradiction). */ foldProtocolAxes: (axes: McpToolAxis[], label: string) => void; /** borrowed-mutable — the two NON-MONOTONE tables the fold writes and this applier snapshots. */ toolEffects: Map<string, ToolEffect>; axisExplicitNegatives: Map<string, { irreversible?: false; egress?: false; }>; /** borrowed-mutable — the run's shared roster. */ roster: RosterBuilder; } /** What one application did, for the caller's receipt. `applied:false` means the axis fold refused * and the domain was left exactly as it was (the tables restored) — no roster write happened. */ export type McpRosterApplyOutcome = { applied: true; pushed: number; excluded: string[]; } | { applied: false; foldError: string; }; /** * Apply one server's delta to the live roster. Behavior-identical to the body this was extracted * from; the comments are the extracted reasoning, not new claims. */ export declare function applyMcpRosterDelta(input: McpRosterApplyInput, delta: McpRosterDelta): McpRosterApplyOutcome; /** The seats the post-application commit writes through. */ export interface McpRosterCommitInput { rebuildHarnessToolsRef: { current?: () => Promise<void>; }; rosterSeat: ToolRosterDeltaSeat; toolCallGateArmedRef: { armed: boolean; }; irreversibleTools: ReadonlySet<string>; egressTools: ReadonlySet<string>; } /** * Publish whatever the applications above changed: rebuild the harness toolset (the content-origin * re-wrap runs inside it), then re-mint the roster delta so the stream's rows describe the LIVE * objects. Batched deliberately — a refresh spanning several servers rebuilds once. * * Returns the gate-honesty line when one is due. The built-in integrity policies are composed * UNCONDITIONALLY, so `armed` is always true in this engine and the line never fires today; the arm * stays as a zero-cost guard for a refactor that would make the composition conditional. */ export declare function commitMcpRosterChanges(input: McpRosterCommitInput): Promise<string | undefined>; /** The re-dial's own seats: the handle it drives, the two operator sinks, and the run identity the * disclosure names. */ export interface McpRedialInput extends McpRosterApplyInput, McpRosterCommitInput { mcp: MaterializedMcp; deps: Pick<RunnerDeps, "onError" | "onNotice">; sessionId: string; runId: string; irreversibilityTier: IrreversibilityTier; } /** * The run handle's `reconnectMcpServer` verb, phase-side: re-dial one server, put whatever it * asserted about its prefix domain onto the live roster, and disclose. * * The roster is written IFF the result carries `tools` — the one rule this lane reads. That covers * both directions with no second condition: a successful re-dial asserts its fresh roster, a failed * DIAL asserts the empty one (the connection it tore down is gone, so leaving its names mounted would * leave the model holding tools that can only fail), and an arm that never touched the connection — * unknown name, revoked server, re-dial already in flight, disposed materialization — asserts nothing * and the mounted roster is left alone. */ export declare function redialMcpServer(input: McpRedialInput, server: string): Promise<McpReconnectResult>;