@sema-agent/core
Version:
Stateless, task-oriented AI agent core
110 lines (109 loc) • 5.91 kB
TypeScript
/**
* 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>;