@sema-agent/core
Version:
Stateless, task-oriented AI agent core
90 lines (89 loc) • 6.16 kB
JavaScript
import { valveLeaves } from "../../agents/tool-filter.js";
import { instructionsTaught, instructionsTold, tellInstructions, toolNamesOfServer, withdrawInstructionAdds } from "../mounted-mcp.js";
import { deliverEngineNotice } from "../types.js";
import { thrownText } from "../thrown-value.js";
export function rosterHolding(input, serverName) {
const mounted = input.declared.some((d) => d.name === serverName) ? toolNamesOfServer(input.roster.tools.map((t) => t.name), serverName) : [];
return { mounted, added: [], removed: [], excluded: [] };
}
export function settledOnRoster(result, reading, roster) {
const settled = { ...result, toolCount: reading.mounted.length, added: reading.added, removed: reading.removed };
if (result.tools === undefined)
return settled;
const held = new Set(reading.mounted);
return { ...settled, tools: roster.tools.filter((t) => held.has(t.name)), ...(result.axes !== undefined ? { axes: result.axes.filter((a) => held.has(a.name)) } : {}) };
}
function settleInstructions(lane, delta, taught) {
if (!taught)
return withdrawInstructionAdds(lane, delta.server);
if (delta.instructions !== undefined && !instructionsTold(lane, delta.server) && !lane.pendingAdds.some((e) => e.server === delta.server))
tellInstructions(lane, delta.instructions);
}
export function applyMcpRosterDelta(input, delta) {
const { spec, toolFaceSnapshot, withFaces, remoteToolOffload, foldProtocolAxes, toolEffects, axisExplicitNegatives, roster } = input;
const domainNow = () => toolNamesOfServer(roster.tools.map((t) => t.name), delta.server);
const before = domainNow();
const leaves = valveLeaves(spec, toolFaceSnapshot.selector);
const pushable = delta.tools.filter(leaves);
const valveExcluded = delta.tools.filter((t) => !leaves(t)).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) => !valveExcluded.includes(a.name)), "MCP");
}
catch (foldErr) {
restoreDomain(toolEffects, priorDomainEffects);
restoreDomain(axisExplicitNegatives, priorDomainNegatives);
settleInstructions(input.instructionsDelta, delta, instructionsTaught(leaves, delta.server, roster.tools, delta.tools));
return { applied: false, foldError: thrownText(foldErr), ...rosterHolding(input, delta.server) };
}
roster.replaceDomain(delta.prefix, withFaces(pushable, delta.server).map((t) => remoteToolOffload(t)), { source: "mcp", mountedBy: "optional", origin: { peer: delta.server } });
const mounted = domainNow();
const heldNow = new Set(mounted);
const heldBefore = new Set(before);
settleInstructions(input.instructionsDelta, delta, instructionsTaught(leaves, delta.server, roster.tools, delta.tools));
return { applied: true, mounted, added: mounted.filter((n) => !heldBefore.has(n)), removed: before.filter((n) => !heldNow.has(n)), excluded: delta.tools.map((t) => t.name).filter((n) => !heldNow.has(n)) };
}
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 listed = await input.mcp.reconnect(server);
let result = settledOnRoster(listed, rosterHolding(input, listed.server), input.roster);
if (listed.tools !== undefined) {
const outcome = applyMcpRosterDelta(input, { server: listed.server, prefix: listed.prefix, tools: listed.tools, axes: listed.axes ?? [], dropped: listed.dropped ?? [] });
result = settledOnRoster(listed, outcome, input.roster);
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;
}