UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

62 lines (61 loc) • 4.11 kB
import { deliverEngineNotice } from "../types.js"; import { thrownText } from "../thrown-value.js"; export function applyMcpRosterDelta(input, delta) { const { toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input; const excludedSet = new Set(toolFaceSnapshot.exclude ?? []); const pushable = delta.tools.filter((t) => !excludedSet.has(t.name)); const excluded = delta.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name); const domainSnapshot = (m) => new Map([...m].filter(([name]) => name.startsWith(delta.prefix))); const restoreDomain = (m, snap) => { for (const name of [...m.keys()]) if (name.startsWith(delta.prefix)) m.delete(name); for (const [name, v] of snap) m.set(name, v); }; const priorDomainEffects = domainSnapshot(toolEffects); const priorDomainNegatives = domainSnapshot(axisExplicitNegatives); for (const name of priorDomainEffects.keys()) toolEffects.delete(name); for (const name of priorDomainNegatives.keys()) axisExplicitNegatives.delete(name); try { foldProtocolAxes(delta.axes.filter((a) => !excludedSet.has(a.name)), "MCP"); } catch (foldErr) { restoreDomain(toolEffects, priorDomainEffects); restoreDomain(axisExplicitNegatives, priorDomainNegatives); return { applied: false, foldError: thrownText(foldErr) }; } roster.replaceDomain(delta.prefix, withFaces(pushable, delta.server).map((t) => remoteToolOffload(t)), { source: "mcp", mountedBy: "optional", origin: { peer: delta.server } }); return { applied: true, pushed: pushable.length, excluded }; } export async function commitMcpRosterChanges(input) { await input.rebuildHarnessToolsRef.current?.(); input.rosterSeat.republish(); if (!input.toolCallGateArmedRef.armed && (input.irreversibleTools.size > 0 || input.egressTools.size > 0)) { return "WARNING: refreshed tools declare irreversible/egress safety hints, but this task started with no approval gate registered (no policy/hooks and no gated tools at start) — these hints cannot arm a gate mid-task (RB-46); the refreshed tools run ungated on this deployment shape."; } return undefined; } export async function redialMcpServer(input, server) { const result = await input.mcp.reconnect(server); if (result.tools !== undefined) { const outcome = applyMcpRosterDelta(input, { server: result.server, prefix: result.prefix, tools: result.tools, axes: result.axes ?? [], dropped: result.dropped ?? [] }); if (!outcome.applied) input.deps.onError?.(Object.assign(new Error(`MCP re-dial of "${server}" connected but its safety-axis fold refused, so its tools were not mounted: ${outcome.foldError}`), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId }); const ungated = await commitMcpRosterChanges(input); if (ungated !== undefined) input.deps.onError?.(Object.assign(new Error(ungated), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId }); } for (const w of result.warnings ?? []) input.deps.onError?.(Object.assign(new Error(w), { code: "mcp.server_unavailable" }), { phase: "mcp", sessionId: input.sessionId }); deliverEngineNotice(input.deps.onNotice, { code: "mcp.server_redialed", message: result.outcome === "reconnected" ? `MCP server "${server}" was re-dialed at the host's request: ${result.toolCount} tool${result.toolCount === 1 ? "" : "s"} mounted (${result.added.length} added, ${result.removed.length} removed)` : `MCP server "${server}" was NOT re-dialed (${result.outcome})${result.reason !== undefined ? `: ${result.reason}` : ""}`, detail: { server, outcome: result.outcome, added: result.added.length, removed: result.removed.length, ...(result.reason !== undefined ? { reason: result.reason } : {}), sessionId: input.sessionId, runId: input.runId }, }); return result; }