@sema-agent/core 1.397.0 → 1.399.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ import { buildAutoModePrompt, renderAutoModeAction, renderAutoModeWindow } from
9
9
  import { resolveModel, resolveTaskModel, roleModelIfSet } from "../roles.js";
10
10
  import { primaryActivityArg } from "../arg-summary.js";
11
11
  import { materializeMcpTools } from "../mcp.js";
12
+ import { Type } from "typebox";
12
13
  import { brainToRuntime } from "../runtime.js";
13
14
  import { MemoryEngine } from "../memory-engine/engine.js";
14
15
  import { deriveControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
@@ -933,7 +934,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
933
934
  }
934
935
  mcp = spec.mcp?.length
935
936
  ? await materializeMcpTools(spec.mcp, spec.principal, deps.onElicit, deps.mcpImageResizer)
936
- : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], dispose: async () => { } };
937
+ : { tools: [], toolAxes: [], warnings: [], serverInstructions: [], instructionsDelta: { pendingAdds: [], pendingRemovals: [] }, droppedTools: [], statuses: [], refresh: async () => [], dispose: async () => { } };
937
938
  for (const w of mcp.warnings)
938
939
  deps.onError?.(w, { phase: "mcp", sessionId });
939
940
  for (const d of mcp.droppedTools) {
@@ -950,22 +951,83 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
950
951
  }
951
952
  }
952
953
  tools.push(...mcp.tools.map((t) => mcpOffload(t)));
953
- for (const axis of mcp.toolAxes) {
954
- if (axis.irreversibility === "always") {
955
- irreversibilityTier.set(axis.name, "always");
956
- irreversibleTools.add(axis.name);
957
- }
958
- if (axis.egress) {
959
- if (axis.effect !== undefined && axis.effect !== "write") {
960
- const e = new Error(`MCP tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
961
- e.code = "config.egress_requires_write_effect";
962
- throw e;
954
+ const rebuildHarnessToolsRef = {};
955
+ if (spec.mcp?.length) {
956
+ tools.push({
957
+ name: "RefreshMcpTools",
958
+ label: "RefreshMcpTools",
959
+ description: "Refresh the tool list of connected MCP servers. Never dials or re-dials connections — it only re-reads the tool list over the existing connection. The refreshed tools are available immediately; you can call them on your next step. Use when a server's expected tool is missing, or the tool list looks stale after a connection recovered. Omit `server` to refresh every connected server.",
960
+ parameters: Type.Object({
961
+ server: Type.Optional(Type.String({ description: "Name of a single MCP server to refresh; omit to refresh all connected servers." })),
962
+ }),
963
+ execute: async (_toolCallId, params) => {
964
+ const serverArg = params?.server;
965
+ const results = await mcp.refresh(typeof serverArg === "string" && serverArg.length > 0 ? serverArg : undefined);
966
+ const lines = [];
967
+ let changed = false;
968
+ for (const r of results) {
969
+ if (r.status !== "refreshed" || r.tools === undefined) {
970
+ lines.push(`${r.server}: ${r.status}${r.error !== undefined ? ` (${r.error})` : ""}`);
971
+ continue;
972
+ }
973
+ const excludedSet = new Set(toolFaceSnapshot.exclude ?? []);
974
+ const pushable = r.tools.filter((t) => !excludedSet.has(t.name));
975
+ const excludedNow = r.tools.filter((t) => excludedSet.has(t.name)).map((t) => t.name);
976
+ try {
977
+ foldMcpAxes((r.axes ?? []).filter((a) => !excludedSet.has(a.name)));
978
+ }
979
+ catch (foldErr) {
980
+ lines.push(`${r.server}: failed (${foldErr instanceof Error ? foldErr.message : String(foldErr)})`);
981
+ continue;
982
+ }
983
+ for (let i = tools.length - 1; i >= 0; i--) {
984
+ const t = tools[i];
985
+ if (t.name.startsWith(r.prefix)) {
986
+ toolEffects.delete(t.name);
987
+ tools.splice(i, 1);
988
+ }
989
+ }
990
+ tools.push(...pushable.map((t) => mcpOffload(t)));
991
+ changed = true;
992
+ const detail = [];
993
+ const shownAdded = r.added.filter((n) => !excludedSet.has(n));
994
+ if (shownAdded.length > 0)
995
+ detail.push(`added: ${shownAdded.join(", ")}`);
996
+ if (r.removed.length > 0)
997
+ detail.push(`removed: ${r.removed.join(", ")}`);
998
+ if (excludedNow.length > 0)
999
+ detail.push(`excluded by deployment config (not mounted): ${excludedNow.join(", ")}`);
1000
+ if ((r.dropped?.length ?? 0) > 0)
1001
+ detail.push(`dropped (invalid schema): ${r.dropped.map((d) => d.tool).join(", ")}`);
1002
+ lines.push(`${r.server}: refreshed — ${pushable.length} tool${pushable.length === 1 ? "" : "s"}${detail.length > 0 ? ` (${detail.join("; ")})` : ""}`);
1003
+ }
1004
+ if (changed)
1005
+ await rebuildHarnessToolsRef.current?.();
1006
+ const text = lines.length === 0 ? "No connected MCP servers to refresh." : lines.join("\n");
1007
+ return { content: [{ type: "text", text }], details: { results }, terminate: false };
1008
+ },
1009
+ });
1010
+ toolEffects.set("RefreshMcpTools", "read");
1011
+ }
1012
+ const foldMcpAxes = (axes) => {
1013
+ for (const axis of axes) {
1014
+ if (axis.irreversibility === "always") {
1015
+ irreversibilityTier.set(axis.name, "always");
1016
+ irreversibleTools.add(axis.name);
1017
+ }
1018
+ if (axis.egress) {
1019
+ if (axis.effect !== undefined && axis.effect !== "write") {
1020
+ const e = new Error(`MCP tool "${axis.name}" resolves to egress:true with effect:"${axis.effect}" — an egress tool (external write) must have effect:"write". Clear egress (toolAxes egress:false) if it is a pure read, or set effect:"write".`);
1021
+ e.code = "config.egress_requires_write_effect";
1022
+ throw e;
1023
+ }
1024
+ egressTools.add(axis.name);
963
1025
  }
964
- egressTools.add(axis.name);
1026
+ if (!toolEffects.has(axis.name))
1027
+ toolEffects.set(axis.name, axis.effect ?? "write");
965
1028
  }
966
- if (!toolEffects.has(axis.name))
967
- toolEffects.set(axis.name, axis.effect ?? "write");
968
- }
1029
+ };
1030
+ foldMcpAxes(mcp.toolAxes);
969
1031
  const callCapOn = spec.limits?.callCapByDeadline !== false;
970
1032
  const callCapRef = spec.limits?.timeoutSec &&
971
1033
  spec.limits.timeoutSec > 0 &&
@@ -1814,6 +1876,15 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
1814
1876
  }
1815
1877
  const activeTools = new Set();
1816
1878
  const fpRef = {};
1879
+ if (rebuildHarnessToolsRef.current === undefined) {
1880
+ rebuildHarnessToolsRef.current = async () => {
1881
+ const list = [...tools];
1882
+ await harnessRef.current.setTools(list, list.map((t) => t.name));
1883
+ if (fpRef.current)
1884
+ fpRef.current.tools = toolsToFingerprintInputs(list);
1885
+ turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
1886
+ };
1887
+ }
1817
1888
  const turnSnapshotRef = {};
1818
1889
  let harnessTools = tools;
1819
1890
  let toolsDeltaRef;
@@ -1847,7 +1918,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
1847
1918
  const deltaRef = toolsDeltaRef;
1848
1919
  const rematerialize = async (active) => {
1849
1920
  const list = buildToolList(active);
1850
- await harnessRef.current.setTools(list);
1921
+ await harnessRef.current.setTools(list, list.map((t) => t.name));
1851
1922
  if (fpRef.current)
1852
1923
  fpRef.current.tools = toolsToFingerprintInputs(list);
1853
1924
  turnSnapshotRef.current?.refreshTools(toolsToFingerprintInputs(list));
@@ -1858,6 +1929,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
1858
1929
  }
1859
1930
  }
1860
1931
  };
1932
+ rebuildHarnessToolsRef.current = () => rematerialize(activeTools);
1861
1933
  toolSearch = makeToolSearchTool({
1862
1934
  registry,
1863
1935
  active: activeTools,