@sema-agent/core
Version:
Stateless, task-oriented AI agent core
62 lines (61 loc) • 4.11 kB
JavaScript
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;
}