@yanlinglabs/winter-agent-runtime 0.0.36 → 0.0.39

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.
@@ -934,6 +934,7 @@ function validatePermissionRuleString(raw, direction) {
934
934
  }
935
935
 
936
936
  // src/tools/registry.ts
937
+ var DEFERRED_BUILTINS_CAPABILITY = "winter.deferred-builtins";
937
938
  var registry = new Map;
938
939
  function registerTool2(t) {
939
940
  if (registry.has(t.descriptor.canonicalName)) {
@@ -996,8 +997,9 @@ function onRegistryChange(cb) {
996
997
  registryChangeListeners.delete(cb);
997
998
  };
998
999
  }
999
- function buildMcpToolDescriptor(server, tool, opts) {
1000
- const canonicalName = `mcp__${server}__${tool.name}`;
1000
+ function buildMcpToolDescriptor(server, tool, opts, plainName) {
1001
+ const mcpSpelling = `mcp__${server}__${tool.name}`;
1002
+ const canonicalName = plainName ?? mcpSpelling;
1001
1003
  const requiresInteraction = tool._meta?.["anthropic/requiresUserInteraction"] === true;
1002
1004
  const perToolAlwaysLoad = tool._meta?.["anthropic/alwaysLoad"] === true;
1003
1005
  const alwaysLoadResolved = perToolAlwaysLoad || opts.alwaysLoad === true ? true : opts.alwaysLoad;
@@ -1017,9 +1019,23 @@ function buildMcpToolDescriptor(server, tool, opts) {
1017
1019
  ...alwaysLoadResolved !== undefined ? { alwaysLoad: alwaysLoadResolved } : {},
1018
1020
  deferred: opts.deferredDefault,
1019
1021
  ...tool._meta !== undefined ? { _meta: tool._meta } : {},
1020
- ...requiresInteraction ? { interaction: "required" } : {}
1022
+ ...requiresInteraction ? { interaction: "required" } : {},
1023
+ ...plainName !== undefined ? { mcpName: mcpSpelling } : {}
1021
1024
  };
1022
1025
  }
1026
+ var TOOL_SEARCH_TOOL_NAME = "ToolSearch";
1027
+ var PLAIN_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
1028
+ function mcpSpellingOf(name) {
1029
+ return registry.get(name)?.descriptor.mcpName;
1030
+ }
1031
+ function registeredNameForMcpSpelling(name) {
1032
+ if (registry.has(name))
1033
+ return name;
1034
+ for (const [registered, entry] of registry)
1035
+ if (entry.descriptor.mcpName === name)
1036
+ return registered;
1037
+ return name;
1038
+ }
1023
1039
  var RESERVED_MCP_SERVER_NAMES = new Set([WINTER_BRAND.mcpServerName]);
1024
1040
  var STANDING_REBRANDS = new Map;
1025
1041
  function rebrandStandingServerTools(renames, serverName) {
@@ -1101,13 +1117,36 @@ function registerMcpServerTools(server, incomingTools, opts) {
1101
1117
  throw new Error(`registerMcpServerTools: "${server}" is a RESERVED server name -- the standing Winter server ` + `(its name is defined in mcp/winter-server.ts) owns this identity and is registry-native; it is never installed through ` + `this live-mutation function, and nothing else may register live tools under it either.`);
1102
1118
  }
1103
1119
  const tools = dedupeSameBatch(server, incomingTools);
1104
- const newNames = new Set(tools.map((t) => `mcp__${server}__${t.name}`));
1120
+ const plainNameFor = (tool) => opts.toolNames !== undefined && Object.hasOwn(opts.toolNames, tool.name) ? opts.toolNames[tool.name] : undefined;
1121
+ const registeredName = (tool) => plainNameFor(tool) ?? `mcp__${server}__${tool.name}`;
1122
+ const newNames = new Set(tools.map(registeredName));
1105
1123
  const previouslyOwned = mcpServerOwnedNames.get(server);
1106
1124
  if (tools.length === 0 && (previouslyOwned === undefined || previouslyOwned.size === 0))
1107
1125
  return;
1126
+ const plainNamesSeen = new Set;
1108
1127
  for (const tool of tools) {
1109
- const canonicalName = `mcp__${server}__${tool.name}`;
1128
+ const plain = plainNameFor(tool);
1129
+ if (plain !== undefined) {
1130
+ if (typeof plain !== "string" || !PLAIN_TOOL_NAME.test(plain) || plain.startsWith("mcp__")) {
1131
+ throw new Error(`registerMcpServerTools: server "${server}" names its tool "${tool.name}" ${JSON.stringify(plain)} -- a plain tool name must match ${String(PLAIN_TOOL_NAME)} and must not start with "mcp__"`);
1132
+ }
1133
+ if (plainNamesSeen.has(plain)) {
1134
+ throw new Error(`registerMcpServerTools: server "${server}" gives two of its tools the same plain name "${plain}"`);
1135
+ }
1136
+ plainNamesSeen.add(plain);
1137
+ }
1138
+ const canonicalName = registeredName(tool);
1110
1139
  const existingOwner = mcpToolOwner.get(canonicalName);
1140
+ const spelling = plain !== undefined ? `mcp__${server}__${tool.name}` : undefined;
1141
+ for (const [name, entry] of registry) {
1142
+ const foreign = mcpToolOwner.get(name) !== server;
1143
+ if (foreign && entry.descriptor.mcpName !== undefined && (entry.descriptor.mcpName === canonicalName || entry.descriptor.mcpName === spelling)) {
1144
+ throw new Error(`registerMcpServerTools: "${canonicalName}" collides with the old spelling "${entry.descriptor.mcpName}" that "${name}" keeps -- refusing to register a second tool under it`);
1145
+ }
1146
+ if (foreign && spelling !== undefined && name === spelling) {
1147
+ throw new Error(`registerMcpServerTools: the old spelling "${spelling}" of server "${server}"'s renamed tool "${plain}" is already a registered tool -- refusing to register a second tool under it`);
1148
+ }
1149
+ }
1111
1150
  if (registry.has(canonicalName) && existingOwner === undefined) {
1112
1151
  throw new Error(`registerMcpServerTools: "${canonicalName}" is already registered by a non-live-MCP mechanism ` + `(e.g. a static WS-06 descriptor stub, or the standing winter server's own advisor identity) -- ` + `refusing to overwrite it. If this name is meant to be a live-connected MCP tool, its static ` + `registration must be removed first.`);
1113
1152
  }
@@ -1125,9 +1164,9 @@ function registerMcpServerTools(server, incomingTools, opts) {
1125
1164
  }
1126
1165
  const nowOwned = new Set;
1127
1166
  for (const tool of tools) {
1128
- const canonicalName = `mcp__${server}__${tool.name}`;
1167
+ const canonicalName = registeredName(tool);
1129
1168
  const existingEntry = registry.get(canonicalName);
1130
- const descriptor = buildMcpToolDescriptor(server, tool, opts);
1169
+ const descriptor = buildMcpToolDescriptor(server, tool, opts, plainNameFor(tool));
1131
1170
  registry.set(canonicalName, existingEntry ? { ...existingEntry, descriptor } : { descriptor });
1132
1171
  mcpToolOwner.set(canonicalName, server);
1133
1172
  nowOwned.add(canonicalName);
@@ -1206,12 +1245,15 @@ function resolveDeferral(descriptor, mode, activation) {
1206
1245
  const modes = descriptor.availability.modes;
1207
1246
  if (modes !== undefined && !modes.includes(mode))
1208
1247
  return "hidden";
1209
- if (descriptor.source === "builtin")
1248
+ if (descriptor.canonicalName === TOOL_SEARCH_TOOL_NAME)
1210
1249
  return "eager";
1211
1250
  if (descriptor.alwaysLoad === true)
1212
1251
  return "eager";
1252
+ const hostDeferred = activation.deferTools?.includes(descriptor.canonicalName) === true;
1253
+ if (descriptor.source === "builtin" && !hostDeferred)
1254
+ return "eager";
1213
1255
  const declared = descriptor.deferred;
1214
- const eligible = declared === true ? true : Array.isArray(declared) ? declared.includes(mode) : false;
1256
+ const eligible = hostDeferred || (declared === true ? true : Array.isArray(declared) ? declared.includes(mode) : false);
1215
1257
  if (!eligible)
1216
1258
  return "eager";
1217
1259
  return isDeferralActive(activation) ? "deferred" : "eager";
@@ -1250,6 +1292,8 @@ function isAvailable(descriptor, cfg) {
1250
1292
  return false;
1251
1293
  if (a.insideSubagent === false && cfg.insideSubagent === true)
1252
1294
  return false;
1295
+ if (a.requiresAnyCapability !== undefined && !a.requiresAnyCapability.some((c) => cfg.capabilities?.includes(c) === true))
1296
+ return false;
1253
1297
  if (!descriptor.capabilityRequirements.every((c) => cfg.capabilities?.includes(c) === true))
1254
1298
  return false;
1255
1299
  return true;
@@ -1276,8 +1320,17 @@ function isBareDenied(canonicalName, disallowedTools) {
1276
1320
  });
1277
1321
  }
1278
1322
  function buildAdvertisedSet(cfg) {
1279
- const requested = cfg.tools;
1280
- return listRegisteredTools().map((t) => t.descriptor).filter((d) => d.disposition !== "correctly-absent").filter((d) => d.exposure !== "hidden").filter((d) => requested === undefined || requested.includes(d.canonicalName)).filter((d) => isAvailable(d, cfg)).filter((d) => !isBareDenied(d.canonicalName, cfg.disallowedTools));
1323
+ return listRegisteredTools().map((t) => t.descriptor).filter((d) => d.disposition !== "correctly-absent").filter((d) => d.exposure !== "hidden").filter((d) => isRequestedTool(d, cfg.tools)).filter((d) => isAvailable(d, cfg)).filter((d) => !isDescriptorBareDenied(d, cfg.disallowedTools));
1324
+ }
1325
+ function isRequestedTool(descriptor, tools) {
1326
+ if (descriptor.source !== "builtin")
1327
+ return true;
1328
+ if (tools === undefined)
1329
+ return descriptor.availability.optIn !== true;
1330
+ return tools.includes(descriptor.canonicalName);
1331
+ }
1332
+ function isDescriptorBareDenied(descriptor, disallowedTools) {
1333
+ return isBareDenied(descriptor.canonicalName, disallowedTools) || descriptor.mcpName !== undefined && isBareDenied(descriptor.mcpName, disallowedTools);
1281
1334
  }
1282
1335
  function unknownToolResult(name) {
1283
1336
  return { output: `Error: unknown tool "${name}" -- no descriptor is registered under that name`, isError: true };
@@ -1295,7 +1348,12 @@ function notYetExecutableResult(name) {
1295
1348
  };
1296
1349
  }
1297
1350
  function foldResult(result) {
1298
- return { output: result.output, ...result.blocks !== undefined ? { blocks: result.blocks } : {}, ...result.isError === true ? { isError: true } : {} };
1351
+ return {
1352
+ output: result.output,
1353
+ ...result.blocks !== undefined ? { blocks: result.blocks } : {},
1354
+ ...result.isError === true ? { isError: true } : {},
1355
+ ...result.siteIcons !== undefined && result.siteIcons.length > 0 ? { siteIcons: result.siteIcons } : {}
1356
+ };
1299
1357
  }
1300
1358
  function unavailableResult(name) {
1301
1359
  return {
@@ -2462,6 +2520,8 @@ function renderPlanModeBlock(input) {
2462
2520
  }
2463
2521
 
2464
2522
  // src/context/attachments.ts
2523
+ var DEFERRED_TOOLS_ADDED_HEADER = "The following deferred tools are now available via ToolSearch:";
2524
+ var DEFERRED_TOOLS_REMOVED_HEADER = "The following deferred tools are no longer available (their MCP server disconnected). Do not search for them — ToolSearch will return no match:";
2465
2525
  var AGENT_LISTING_INITIAL_HEADER = "Available agent types for the Agent tool:";
2466
2526
  var AGENT_LISTING_ADDED_HEADER = "New agent types are now available for the Agent tool:";
2467
2527
  var AGENT_LISTING_REMOVED_HEADER = "The following agent types are no longer available:";
@@ -2511,6 +2571,44 @@ ${removedTypes.map((t) => `- ${t}`).join(`
2511
2571
 
2512
2572
  `));
2513
2573
  });
2574
+ registerAttachmentRenderer("deferred_tools_delta", (a) => {
2575
+ const addedLines = stringArray(a["addedLines"]);
2576
+ const removedNames = stringArray(a["removedNames"]);
2577
+ const parts = [];
2578
+ if (addedLines.length > 0)
2579
+ parts.push(`${DEFERRED_TOOLS_ADDED_HEADER}
2580
+ ${addedLines.join(`
2581
+ `)}`);
2582
+ if (removedNames.length > 0)
2583
+ parts.push(`${DEFERRED_TOOLS_REMOVED_HEADER}
2584
+ ${removedNames.join(`
2585
+ `)}`);
2586
+ return parts.length === 0 ? undefined : neutralizeReminderTags(parts.join(`
2587
+
2588
+ `));
2589
+ });
2590
+ function announcedDeferredTools(messages) {
2591
+ const announced = new Set;
2592
+ for (const a of attachmentsIn(messages)) {
2593
+ if (a.type !== "deferred_tools_delta")
2594
+ continue;
2595
+ if (Array.isArray(a["addedLines"]))
2596
+ for (const name of stringArray(a["addedNames"]))
2597
+ announced.add(name);
2598
+ for (const name of stringArray(a["removedNames"]))
2599
+ announced.delete(name);
2600
+ }
2601
+ return announced;
2602
+ }
2603
+ function computeDeferredToolsDelta(deferred, offered, history) {
2604
+ const announced = announcedDeferredTools(history);
2605
+ const offeredNow = new Set([...offered, ...deferred]);
2606
+ const added = [...new Set(deferred)].filter((name) => !announced.has(name)).sort();
2607
+ const removed = [...announced].filter((name) => !offeredNow.has(name)).sort();
2608
+ if (added.length === 0 && removed.length === 0)
2609
+ return;
2610
+ return { type: "deferred_tools_delta", addedNames: added, addedLines: added, removedNames: removed };
2611
+ }
2514
2612
  registerAttachmentRenderer("skill_listing", (a) => {
2515
2613
  const content = typeof a["content"] === "string" ? a["content"] : "";
2516
2614
  if (content.length === 0)
@@ -5057,7 +5155,25 @@ function createDefaultMessagingRuntime(opts = {}) {
5057
5155
  subscribers,
5058
5156
  now
5059
5157
  });
5060
- return { seam, adapter, notifications, loopGuard: createLoopGuard(), subscribers, now, peers, classifyDeliveryError };
5158
+ const hostPorts = new Map;
5159
+ return {
5160
+ seam,
5161
+ adapter,
5162
+ notifications,
5163
+ loopGuard: createLoopGuard(),
5164
+ subscribers,
5165
+ now,
5166
+ peers,
5167
+ classifyDeliveryError,
5168
+ hostMessaging: (sessionId) => hostPorts.get(sessionId),
5169
+ registerHostMessagingPort(sessionId, port) {
5170
+ hostPorts.set(sessionId, port);
5171
+ return () => {
5172
+ if (hostPorts.get(sessionId) === port)
5173
+ hostPorts.delete(sessionId);
5174
+ };
5175
+ }
5176
+ };
5061
5177
  }
5062
5178
  function getDefaultMessagingRuntime() {
5063
5179
  const runtime = getMessagingRuntime();
@@ -6470,6 +6586,7 @@ ${body}
6470
6586
  // src/mcp/control.ts
6471
6587
  var REPLACE_ELIGIBLE_ORIGINS = new Set(["explicit", "dynamic"]);
6472
6588
  function createMcpControlSeam(internals, opts = {}) {
6589
+ const hostReserved = new Set(opts.hostReservedServerNames ?? []);
6473
6590
  const reservedServerName = opts.reservedServerName ?? WINTER_SERVER_NAME;
6474
6591
  return {
6475
6592
  async reconnect(serverName) {
@@ -6498,6 +6615,10 @@ function createMcpControlSeam(internals, opts = {}) {
6498
6615
  errors[name] = `"${reservedServerName}" is a reserved server identity (RULING P4-B, the standing Winter server) -- no source may configure a live MCP server under this name`;
6499
6616
  continue;
6500
6617
  }
6618
+ if (hostReserved.has(name) && raw?.type !== "sdk") {
6619
+ errors[name] = `"${name}" is a server name this session's host reserves for its own in-process server -- only an in-process (type "sdk") server may be configured under it`;
6620
+ continue;
6621
+ }
6501
6622
  const result = validateServerConfig(raw);
6502
6623
  if (!result.ok) {
6503
6624
  errors[name] = result.reason;
@@ -6573,6 +6694,7 @@ function validateServerConfig(raw) {
6573
6694
  }
6574
6695
  function resolveMcpServerSources(sources, opts) {
6575
6696
  const reservedServerName = opts.reservedServerName ?? WINTER_SERVER_NAME;
6697
+ const hostReserved = new Set(opts.hostReservedServerNames ?? []);
6576
6698
  const resolved = [];
6577
6699
  const shadowed = [];
6578
6700
  const rejected = [];
@@ -6597,6 +6719,10 @@ function resolveMcpServerSources(sources, opts) {
6597
6719
  rejected.push({ name, origin, code: "reserved_name", reason: `"${reservedServerName}" is a reserved server identity (RULING P4-B, the standing Winter server) -- no source may configure a live MCP server under this name` });
6598
6720
  continue;
6599
6721
  }
6722
+ if (hostReserved.has(name) && (origin !== "explicit" || raw?.type !== "sdk")) {
6723
+ rejected.push({ name, origin, code: "reserved_name", reason: `"${name}" is a server name this session's host reserves for its own in-process server -- no ${origin} source may configure a server under this name` });
6724
+ continue;
6725
+ }
6600
6726
  const validated = validateServerConfig(raw);
6601
6727
  if (!validated.ok) {
6602
6728
  rejected.push({ name, origin, code: "invalid_config", reason: validated.reason });
@@ -7168,7 +7294,7 @@ function createMcpLifecycle(deps) {
7168
7294
  };
7169
7295
  return {
7170
7296
  stateSource,
7171
- controlSeam: createMcpControlSeam(internals, { ...deps.reservedServerName !== undefined ? { reservedServerName: deps.reservedServerName } : {} }),
7297
+ controlSeam: createMcpControlSeam(internals, { ...deps.reservedServerName !== undefined ? { reservedServerName: deps.reservedServerName } : {}, ...deps.hostReservedServerNames !== undefined ? { hostReservedServerNames: deps.hostReservedServerNames } : {} }),
7172
7298
  start,
7173
7299
  launch,
7174
7300
  dispose,
@@ -7286,6 +7412,40 @@ function transformChildFrame(frame, correlation, forwardSubagentText) {
7286
7412
  }
7287
7413
  var SESSION_LEVEL_SYSTEM_SUBTYPES = new Set(["task_started", "task_progress", "task_notification", "task_updated", "background_tasks_changed"]);
7288
7414
 
7415
+ // src/messaging/host-port.ts
7416
+ import { HOST_MESSAGE_LIST_SUBTYPE, HOST_MESSAGE_SEND_SUBTYPE, HOST_SESSION_STOP_SUBTYPE } from "@yanlinglabs/winter-agent-sdk";
7417
+ import { isHostMessageSendAnswer, isHostSessionStopAnswer, normaliseHostMessageListAnswer } from "@yanlinglabs/winter-agent-sdk/messaging";
7418
+ var HOST_MESSAGE_SEND_TIMEOUT_MS = 180000;
7419
+ var HOST_MESSAGE_LIST_TIMEOUT_MS = 15000;
7420
+ var HOST_SESSION_STOP_TIMEOUT_MS = 30000;
7421
+
7422
+ class HostMessagingAnswerError extends Error {
7423
+ constructor(message) {
7424
+ super(message);
7425
+ this.name = "HostMessagingAnswerError";
7426
+ }
7427
+ }
7428
+ function createHostMessagingPort(sender) {
7429
+ return {
7430
+ async send(request, opts) {
7431
+ const answer = await sender.request(HOST_MESSAGE_SEND_SUBTYPE, request, { timeoutMs: HOST_MESSAGE_SEND_TIMEOUT_MS, ...opts?.signal !== undefined ? { signal: opts.signal } : {} });
7432
+ if (!isHostMessageSendAnswer(answer))
7433
+ throw new HostMessagingAnswerError("the host answered host_message_send with a malformed outcome");
7434
+ return answer;
7435
+ },
7436
+ async list(request, opts) {
7437
+ const answer = await sender.request(HOST_MESSAGE_LIST_SUBTYPE, request, { timeoutMs: HOST_MESSAGE_LIST_TIMEOUT_MS, ...opts?.signal !== undefined ? { signal: opts.signal } : {} });
7438
+ return normaliseHostMessageListAnswer(answer) ?? { sessions: [] };
7439
+ },
7440
+ async stop(request, opts) {
7441
+ const answer = await sender.request(HOST_SESSION_STOP_SUBTYPE, request, { timeoutMs: HOST_SESSION_STOP_TIMEOUT_MS, ...opts?.signal !== undefined ? { signal: opts.signal } : {} });
7442
+ if (!isHostSessionStopAnswer(answer))
7443
+ throw new HostMessagingAnswerError("the host answered host_session_stop with a malformed answer");
7444
+ return answer;
7445
+ }
7446
+ };
7447
+ }
7448
+
7289
7449
  // src/permissions/ruleset.ts
7290
7450
  import {
7291
7451
  readFileSync as readFileSync6,
@@ -11012,13 +11172,15 @@ function capHookText(text, limit = MAX_HOOK_TEXT_CHARS) {
11012
11172
 
11013
11173
  // src/hooks/runner.ts
11014
11174
  function mcpToolProvenance(toolName) {
11015
- const server = mcpServerOwningTool(toolName);
11175
+ const registered = registeredNameForMcpSpelling(toolName);
11176
+ const server = mcpServerOwningTool(registered);
11016
11177
  if (server === undefined)
11017
11178
  return;
11179
+ const spelled = mcpSpellingOf(registered) ?? registered;
11018
11180
  const prefix = `mcp__${server}__`;
11019
- if (!toolName.startsWith(prefix) || toolName.length === prefix.length)
11181
+ if (!spelled.startsWith(prefix) || spelled.length === prefix.length)
11020
11182
  return;
11021
- return { server, tool: toolName.slice(prefix.length) };
11183
+ return { server, tool: spelled.slice(prefix.length) };
11022
11184
  }
11023
11185
  function mcpServerIdentity(toolName, renames) {
11024
11186
  const name = mcpServerOwningTool(toolName);
@@ -14739,6 +14901,26 @@ stub({
14739
14901
  disposition: "implement-now"
14740
14902
  });
14741
14903
 
14904
+ // src/tools/descriptors/search.ts
14905
+ var SEARCH_CANONICAL_NAME = "Search";
14906
+ var SEARCH_ANSWER_CAPABILITY = "winter.search-answer";
14907
+ var SEARCH_DESCRIPTION = "Search the web and get back a written answer with its sources, in a single call. Ask a real question, not keywords — a search engine answers it and the answer comes back already synthesized, followed by the pages it came from. Use it freely whenever a fact might be newer than you are, or when the user asks about something current, and cite the URLs you used.";
14908
+ var descriptor = {
14909
+ canonicalName: SEARCH_CANONICAL_NAME,
14910
+ advertisedName: SEARCH_CANONICAL_NAME,
14911
+ source: "builtin",
14912
+ inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, description: "The question to answer" } }, required: ["query"] },
14913
+ description: SEARCH_DESCRIPTION,
14914
+ searchHint: "answer a question from the web with sources",
14915
+ annotations: { readOnlyHint: true, openWorldHint: true },
14916
+ exposure: "eager",
14917
+ permissionClass: "network",
14918
+ availability: { optIn: true },
14919
+ capabilityRequirements: [SEARCH_ANSWER_CAPABILITY],
14920
+ disposition: "implement-now"
14921
+ };
14922
+ stub(descriptor);
14923
+
14742
14924
  // src/tools/descriptors/tool-search.ts
14743
14925
  stub({
14744
14926
  canonicalName: "ToolSearch",
@@ -14752,11 +14934,13 @@ stub({
14752
14934
  },
14753
14935
  required: ["query"]
14754
14936
  },
14755
- description: "Fetches full schema definitions for deferred tools so they can be called.",
14937
+ description: `Fetches full schema definitions for deferred tools so they can be called.
14938
+
14939
+ ` + "Deferred tools appear by name in <system-reminder> messages. Until fetched, only the name is known — there is no parameter schema, so the tool cannot be invoked. " + 'Query forms: "select:Read,Edit,Grep" fetches these exact tools by name; "notebook jupyter" is a keyword search over the deferred tools.',
14756
14940
  exposure: "eager",
14757
14941
  permissionClass: "read",
14758
- availability: { requiresToolSearchEnabled: true },
14759
- capabilityRequirements: ["winter.mcp"],
14942
+ availability: { requiresToolSearchEnabled: true, requiresAnyCapability: ["winter.mcp", DEFERRED_BUILTINS_CAPABILITY] },
14943
+ capabilityRequirements: [],
14760
14944
  disposition: "implement-now"
14761
14945
  });
14762
14946
 
@@ -14993,7 +15177,7 @@ var WEB_SEARCH_INPUT_SCHEMA_FIRST_PARTY = {
14993
15177
  function webSearchInputSchemaFor(firstPartyAnthropic) {
14994
15178
  return firstPartyAnthropic ? WEB_SEARCH_INPUT_SCHEMA_FIRST_PARTY : WEB_SEARCH_INPUT_SCHEMA_PORTABLE;
14995
15179
  }
14996
- var descriptor = {
15180
+ var descriptor2 = {
14997
15181
  canonicalName: WEB_SEARCH_CANONICAL_NAME,
14998
15182
  advertisedName: WEB_SEARCH_CANONICAL_NAME,
14999
15183
  source: "builtin",
@@ -15007,14 +15191,14 @@ var descriptor = {
15007
15191
  capabilityRequirements: ["winter.search-backend"],
15008
15192
  disposition: "implement-now"
15009
15193
  };
15010
- Object.defineProperty(descriptor, "description", {
15194
+ Object.defineProperty(descriptor2, "description", {
15011
15195
  enumerable: true,
15012
15196
  configurable: true,
15013
15197
  get() {
15014
15198
  return webSearchDescription(false);
15015
15199
  }
15016
15200
  });
15017
- stub(descriptor);
15201
+ stub(descriptor2);
15018
15202
 
15019
15203
  // src/tools/descriptors/winter-list-agents.ts
15020
15204
  import { WINTER_BRAND as WINTER_BRAND13, mcpToolName } from "@yanlinglabs/winter-agent-sdk";
@@ -18670,6 +18854,7 @@ replaceExecutor("TaskOutput", taskOutputExecutor);
18670
18854
 
18671
18855
  // src/tools/impl/task-stop.ts
18672
18856
  import { randomUUID as randomUUID15 } from "node:crypto";
18857
+ import { boundedHostNote } from "@yanlinglabs/winter-agent-sdk/messaging";
18673
18858
  function parseTaskStopInput(input) {
18674
18859
  if (typeof input !== "object" || input === null)
18675
18860
  return { error: "TaskStop: input must be an object" };
@@ -18699,6 +18884,9 @@ var taskStopExecutor = {
18699
18884
  const id = parsed.task_id ?? parsed.shell_id;
18700
18885
  const task = getTask(id);
18701
18886
  if (!task) {
18887
+ const host = getMessagingRuntime()?.hostMessaging?.(ctx.sessionId);
18888
+ if (host !== undefined)
18889
+ return stopHostSession(host, id, ctx.agentId, ctx.signal);
18702
18890
  return { output: `Error: TaskStop: unknown task_id "${id}"`, isError: true };
18703
18891
  }
18704
18892
  if (task.status !== "running") {
@@ -18720,6 +18908,24 @@ var taskStopExecutor = {
18720
18908
  return { output: formatResult(`stopped task ${id}`, id, task.kind, task.command) };
18721
18909
  }
18722
18910
  };
18911
+ var HOST_SESSION_TASK_TYPE = "session";
18912
+ async function stopHostSession(host, id, agentId, signal) {
18913
+ let answer;
18914
+ try {
18915
+ answer = await host.stop({ id, ...agentId !== undefined ? { fromAgentId: agentId } : {} }, signal !== undefined ? { signal } : {});
18916
+ } catch (err) {
18917
+ return { output: `Error: TaskStop: the host did not answer the stop for "${id}" (${err instanceof Error ? err.name : "error"}); it may or may not have stopped`, isError: true };
18918
+ }
18919
+ const note = boundedHostNote(answer.note);
18920
+ const withNote = (message) => note === undefined ? message : `${message}. ${note}`;
18921
+ if (answer.status === "stopped")
18922
+ return { output: JSON.stringify({ message: withNote(`stopped session ${id}: its running turn was interrupted`), task_id: id, task_type: HOST_SESSION_TASK_TYPE }) };
18923
+ if (answer.status === "not_running")
18924
+ return { output: JSON.stringify({ message: withNote(`session ${id} has no turn running -- nothing to stop`), task_id: id, task_type: HOST_SESSION_TASK_TYPE }) };
18925
+ if (answer.status === "not_found")
18926
+ return { output: `Error: TaskStop: unknown task_id "${id}" (${answer.reason ?? "not found"})`, isError: true };
18927
+ return { output: `Error: TaskStop: ${answer.reason ?? answer.status}`, isError: true };
18928
+ }
18723
18929
  replaceExecutor("TaskStop", taskStopExecutor);
18724
18930
 
18725
18931
  // src/tools/impl/todo-write.ts
@@ -19083,6 +19289,7 @@ function computeExposurePartition(query) {
19083
19289
  ...query.platform !== undefined ? { platform: query.platform } : {},
19084
19290
  ...query.capabilities !== undefined ? { capabilities: query.capabilities } : {},
19085
19291
  ...query.disallowedTools !== undefined ? { disallowedTools: query.disallowedTools } : {},
19292
+ ...query.tools !== undefined ? { tools: query.tools } : {},
19086
19293
  ...query.insideSubagent !== undefined ? { insideSubagent: query.insideSubagent } : {},
19087
19294
  ...query.familyMetadata !== undefined ? { familyMetadata: query.familyMetadata } : {}
19088
19295
  };
@@ -19142,6 +19349,7 @@ function exposureQuery(deps) {
19142
19349
  ...deps.platform !== undefined ? { platform: deps.platform } : {},
19143
19350
  ...deps.capabilities !== undefined ? { capabilities: deps.capabilities } : {},
19144
19351
  ...deps.disallowedTools !== undefined ? { disallowedTools: deps.disallowedTools } : {},
19352
+ ...deps.tools !== undefined ? { tools: deps.tools } : {},
19145
19353
  ...deps.insideSubagent !== undefined ? { insideSubagent: deps.insideSubagent } : {},
19146
19354
  ...deps.familyMetadata !== undefined ? { familyMetadata: deps.familyMetadata } : {},
19147
19355
  ...deps.toolAliases !== undefined ? { toolAliases: deps.toolAliases } : {},
@@ -19175,7 +19383,9 @@ async function executeToolSearch(input, deps) {
19175
19383
  const query = rec.query;
19176
19384
  const maxResults = typeof rec.max_results === "number" && Number.isFinite(rec.max_results) && rec.max_results > 0 ? Math.floor(rec.max_results) : DEFAULT_MAX_RESULTS;
19177
19385
  const pendingWaitMs = deps.pendingWaitMs ?? DEFAULT_PENDING_WAIT_MS;
19178
- const selectNames = parseSelect(query);
19386
+ const resolveName = deps.resolveName ?? registeredNameForMcpSpelling;
19387
+ const parsedSelect = parseSelect(query);
19388
+ const selectNames = parsedSelect === undefined ? undefined : [...new Set(parsedSelect.map(resolveName))];
19179
19389
  let attempt = runAttempt(query, selectNames, maxResults, deps);
19180
19390
  const incomplete = selectNames !== undefined ? attempt.matches.length < selectNames.length : attempt.matches.length === 0;
19181
19391
  if (incomplete && deps.stateSource !== undefined) {
@@ -19215,7 +19425,7 @@ function getToolSearchSessionRuntime(sessionId) {
19215
19425
  }
19216
19426
 
19217
19427
  // src/tools/impl/tool-search.ts
19218
- var TOOL_SEARCH_TOOL_NAME = "ToolSearch";
19428
+ var TOOL_SEARCH_TOOL_NAME2 = "ToolSearch";
19219
19429
  function notWiredResult() {
19220
19430
  return {
19221
19431
  output: "Error: ToolSearch has no session runtime registered for this session (WS-09 §8) -- engine.ts must call registerToolSearchSessionRuntime(sessionId, ...) once per run (toolsearch/search.ts); this is a host wiring gap, not a model input error.",
@@ -19233,7 +19443,7 @@ var toolSearchExecutor = {
19233
19443
  return { output: JSON.stringify(outcome.result) };
19234
19444
  }
19235
19445
  };
19236
- replaceExecutor(TOOL_SEARCH_TOOL_NAME, toolSearchExecutor);
19446
+ replaceExecutor(TOOL_SEARCH_TOOL_NAME2, toolSearchExecutor);
19237
19447
 
19238
19448
  // src/tools/impl/wait-for-mcp-servers.ts
19239
19449
  var WAIT_FOR_MCP_SERVERS_TOOL_NAME = "WaitForMcpServers";
@@ -19312,7 +19522,8 @@ function callerContextFrom(ctx) {
19312
19522
  return {
19313
19523
  sessionId: ctx.sessionId,
19314
19524
  ...ctx.agentId !== undefined ? { agentId: ctx.agentId } : {},
19315
- ...ctx.toolUseId !== undefined ? { toolUseId: ctx.toolUseId } : {}
19525
+ ...ctx.toolUseId !== undefined ? { toolUseId: ctx.toolUseId } : {},
19526
+ ...ctx.signal !== undefined ? { signal: ctx.signal } : {}
19316
19527
  };
19317
19528
  }
19318
19529
 
@@ -19845,6 +20056,9 @@ function inheritedWebSessionFacts(sessionId) {
19845
20056
  function searchBackendUsable(runtime) {
19846
20057
  return runtime.web.search.enabled;
19847
20058
  }
20059
+ function searchAnswerUsable(runtime) {
20060
+ return runtime.web.search.enabled && runtime.web.search.authRef !== undefined && runtime.resolveToolSecret !== undefined;
20061
+ }
19848
20062
  function digestModelResolves(runtime) {
19849
20063
  const tag = runtime.web.fetch.digestModel;
19850
20064
  if (tag === undefined)
@@ -20937,6 +21151,85 @@ async function runInnerModel(ctx, request, runtime) {
20937
21151
  }
20938
21152
  }
20939
21153
 
21154
+ // src/tools/impl/_site-icons.ts
21155
+ var SITE_ICONS_MAX = 10;
21156
+ var SITE_ICON_URL_MAX_LENGTH = 2048;
21157
+ var ICON_SCAN_CHARS = 256 * 1024;
21158
+ function siteIconHttpsUrl(value) {
21159
+ if (typeof value !== "string" || value.length === 0 || value.length > SITE_ICON_URL_MAX_LENGTH)
21160
+ return;
21161
+ let parsed;
21162
+ try {
21163
+ parsed = new URL(value.trim());
21164
+ } catch {
21165
+ return;
21166
+ }
21167
+ if (parsed.protocol !== "https:" || parsed.username !== "" || parsed.password !== "" || !isPublicName(parsed.hostname))
21168
+ return;
21169
+ const href = parsed.href;
21170
+ return href.length <= SITE_ICON_URL_MAX_LENGTH && /^https:\/\/[!-~]+$/.test(href) ? href : undefined;
21171
+ }
21172
+ var PRIVATE_NAME_SUFFIX = /\.(?:localhost|local|internal|lan|home|home\.arpa|corp|intranet|private)$/;
21173
+ function isPublicName(hostname) {
21174
+ const host = hostname.toLowerCase().replace(/\.$/, "");
21175
+ if (host.length === 0 || host.length > 253 || host.startsWith("[") || !host.includes("."))
21176
+ return false;
21177
+ if (host === "localhost" || PRIVATE_NAME_SUFFIX.test(host))
21178
+ return false;
21179
+ const labels = host.split(".");
21180
+ return /[a-z]/.test(labels[labels.length - 1]) && labels.every((l) => l.length > 0 && l.length <= 63);
21181
+ }
21182
+ function collectSiteIcons(entries) {
21183
+ const out = [];
21184
+ const seen = new Set;
21185
+ for (const e of entries) {
21186
+ if (out.length >= SITE_ICONS_MAX)
21187
+ break;
21188
+ const url = siteIconHttpsUrl(e.url);
21189
+ const iconUrl = siteIconHttpsUrl(e.iconUrl);
21190
+ if (url === undefined || iconUrl === undefined || seen.has(url))
21191
+ continue;
21192
+ seen.add(url);
21193
+ out.push({ url, iconUrl });
21194
+ }
21195
+ return out.length > 0 ? out : undefined;
21196
+ }
21197
+ var LINK_TAG = /<link\b[^>]*>/gi;
21198
+ var ATTRIBUTE = /([A-Za-z][\w:-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
21199
+ function pageIconUrl(html, pageUrl) {
21200
+ const page = siteIconHttpsUrl(pageUrl);
21201
+ if (page === undefined)
21202
+ return;
21203
+ const head = html.length > ICON_SCAN_CHARS ? html.slice(0, ICON_SCAN_CHARS) : html;
21204
+ let best;
21205
+ for (const tag of head.matchAll(LINK_TAG)) {
21206
+ const attrs = {};
21207
+ for (const m of tag[0].matchAll(ATTRIBUTE))
21208
+ attrs[m[1].toLowerCase()] = m[2] ?? m[3] ?? m[4] ?? "";
21209
+ const rel = (attrs["rel"] ?? "").toLowerCase().split(/\s+/).filter((t) => t.length > 0);
21210
+ const href = decodeHtmlEntities(attrs["href"] ?? "").trim();
21211
+ const lowered = href.toLowerCase();
21212
+ if (href.length === 0 || rel.includes("mask-icon") || lowered.startsWith("data:") || /\.svg(?:[?#]|$)/.test(lowered))
21213
+ continue;
21214
+ const rank = rel.includes("apple-touch-icon") || rel.includes("apple-touch-icon-precomposed") ? 3 : rel.includes("icon") ? 2 : 0;
21215
+ if (rank === 0)
21216
+ continue;
21217
+ if (best === undefined || rank > best.rank) {
21218
+ const resolved = resolveIcon(href, page);
21219
+ if (resolved !== undefined)
21220
+ best = { rank, href: resolved };
21221
+ }
21222
+ }
21223
+ return best?.href ?? siteIconHttpsUrl(new URL("/favicon.ico", page).href);
21224
+ }
21225
+ function resolveIcon(href, page) {
21226
+ try {
21227
+ return siteIconHttpsUrl(new URL(href, page).href);
21228
+ } catch {
21229
+ return;
21230
+ }
21231
+ }
21232
+
20940
21233
  // src/tools/impl/web-fetch.ts
20941
21234
  var DIGEST_CONTENT_CAP = 1e5;
20942
21235
  var RESULT_CAP = 50000;
@@ -20993,6 +21286,10 @@ async function raceAgainstAbort(promise, signal) {
20993
21286
  });
20994
21287
  });
20995
21288
  }
21289
+ function siteIconsFor(pageUrl, iconUrl) {
21290
+ const siteIcons = iconUrl !== undefined ? collectSiteIcons([{ url: pageUrl, iconUrl }]) : undefined;
21291
+ return siteIcons !== undefined ? { siteIcons } : {};
21292
+ }
20996
21293
  function capResult(text) {
20997
21294
  if (text.length <= RESULT_CAP)
20998
21295
  return text;
@@ -21156,11 +21453,15 @@ function createWebFetchExecutor(deps = {}) {
21156
21453
  const preapproved = isPreapprovedUrl(originalUrl);
21157
21454
  let content;
21158
21455
  let contentType;
21456
+ let pageUrl;
21457
+ let iconUrl;
21458
+ let addressPublic;
21159
21459
  const cached = cache.get(ctx.sessionId, inputUrlString);
21160
21460
  if (cached !== undefined) {
21161
21461
  const addressVerdict = await raceAgainstAbort(classifyHostname(stripIpv6Brackets(originalUrl.hostname), resolveHost), ctx.signal);
21162
21462
  if (addressVerdict === "aborted")
21163
21463
  return { output: "WebFetch was interrupted.", isError: true };
21464
+ addressPublic = addressVerdict.class !== "private";
21164
21465
  if (addressVerdict.class === "private") {
21165
21466
  const refusal = privateAddressRefusal(originalUrl.hostname, policy, addressVerdict.reason);
21166
21467
  if (refusal !== undefined)
@@ -21173,6 +21474,7 @@ function createWebFetchExecutor(deps = {}) {
21173
21474
  const finalVerdict = await raceAgainstAbort(classifyHostname(stripIpv6Brackets(cachedHost), resolveHost), ctx.signal);
21174
21475
  if (finalVerdict === "aborted")
21175
21476
  return { output: "WebFetch was interrupted.", isError: true };
21477
+ addressPublic &&= finalVerdict.class !== "private";
21176
21478
  if (finalVerdict.class === "private") {
21177
21479
  const refusal = privateAddressRefusal(cachedHost, policy, finalVerdict.reason);
21178
21480
  if (refusal !== undefined)
@@ -21181,6 +21483,8 @@ function createWebFetchExecutor(deps = {}) {
21181
21483
  }
21182
21484
  content = cached.content;
21183
21485
  contentType = cached.contentType;
21486
+ pageUrl = cached.finalUrl;
21487
+ iconUrl = cached.iconUrl;
21184
21488
  } else {
21185
21489
  const outcome = await performWebFetch(inputUrlString, prompt, { blockedDomains, privateAddressPolicy: policy, userAgent: winterUserAgent(), ...ctx.signal !== undefined ? { signal: ctx.signal } : {} }, netDeps);
21186
21490
  switch (outcome.kind) {
@@ -21226,9 +21530,12 @@ The response body was not retrieved. If this URL requires authentication, use an
21226
21530
  }
21227
21531
  const kind = classifyContentType(outcome.contentType);
21228
21532
  if (kind === "html") {
21229
- content = await convertFetchedHtml(new TextDecoder("utf-8", { fatal: false }).decode(outcome.body));
21533
+ const html = new TextDecoder("utf-8", { fatal: false }).decode(outcome.body);
21534
+ iconUrl = pageIconUrl(html, outcome.finalUrl);
21535
+ content = await convertFetchedHtml(html);
21230
21536
  } else if (kind === "text") {
21231
21537
  content = new TextDecoder("utf-8", { fatal: false }).decode(outcome.body);
21538
+ iconUrl = pageIconUrl("", outcome.finalUrl);
21232
21539
  } else {
21233
21540
  const savedPath = await saveBinaryToTemp(ctx, originalUrl, outcome.body);
21234
21541
  const sizeLabel = `${outcome.body.byteLength.toLocaleString("en-US")} bytes`;
@@ -21236,14 +21543,18 @@ The response body was not retrieved. If this URL requires authentication, use an
21236
21543
  return { output: note };
21237
21544
  }
21238
21545
  contentType = outcome.contentType;
21239
- const entry = { bytes: Buffer.byteLength(content, "utf8"), code: outcome.status, codeText: outcome.statusText, content, contentType, finalUrl: outcome.finalUrl };
21546
+ pageUrl = outcome.finalUrl;
21547
+ addressPublic = policy !== "allow";
21548
+ const entry = { bytes: Buffer.byteLength(content, "utf8"), code: outcome.status, codeText: outcome.statusText, content, contentType, finalUrl: outcome.finalUrl, ...iconUrl !== undefined ? { iconUrl } : {} };
21240
21549
  cache.set(ctx.sessionId, inputUrlString, entry);
21241
21550
  }
21551
+ if (!addressPublic)
21552
+ iconUrl = undefined;
21242
21553
  if (preapproved && contentType.toLowerCase().includes("text/markdown") && content.length < 1e5) {
21243
- return { output: capResult(content) };
21554
+ return { output: capResult(content), ...siteIconsFor(pageUrl, iconUrl) };
21244
21555
  }
21245
21556
  const digest = await runDigest(ctx, runtime, content, prompt, preapproved);
21246
- return { output: capResult(digest.output), ...digest.isError ? { isError: true } : {} };
21557
+ return { output: capResult(digest.output), ...digest.isError ? { isError: true } : siteIconsFor(pageUrl, iconUrl) };
21247
21558
  }
21248
21559
  return {
21249
21560
  async execute(rawInput, ctx) {
@@ -21387,7 +21698,8 @@ function hitFromRecord(record) {
21387
21698
  const title = capText(typeof record["title"] === "string" && record["title"].trim().length > 0 ? record["title"].trim() : url, EXA_TITLE_MAX_CHARACTERS);
21388
21699
  const highlight = highlightFrom(record["highlights"]) || highlightFrom(record["summary"]) || highlightFrom(record["text"]);
21389
21700
  const published = typeof record["publishedDate"] === "string" && record["publishedDate"].length > 0 ? record["publishedDate"] : undefined;
21390
- return { title, url, highlight: capText(highlight.trim(), EXA_HIGHLIGHTS_MAX_CHARACTERS), ...published !== undefined ? { publishedDate: published } : {} };
21701
+ const favicon = siteIconHttpsUrl(record["favicon"]);
21702
+ return { title, url, highlight: capText(highlight.trim(), EXA_HIGHLIGHTS_MAX_CHARACTERS), ...published !== undefined ? { publishedDate: published } : {}, ...favicon !== undefined ? { favicon } : {} };
21391
21703
  }
21392
21704
  var FIELD_LINE = /^(Title|URL|Published(?: Date)?|Author|Highlights|Text|Summary|Content):[ \t]*(.*)$/i;
21393
21705
  function hitsFromPlainText(text) {
@@ -21946,6 +22258,7 @@ function createWebSearchExecutor(deps = {}) {
21946
22258
  return { output: `Error: web search failed unexpectedly (${err instanceof Error ? err.name : "unknown error"}).`, isError: true };
21947
22259
  }
21948
22260
  const events = [];
22261
+ const iconEntries = [];
21949
22262
  let successfulSearches = 0;
21950
22263
  let attemptedSearches = 0;
21951
22264
  let lastFailureMessage;
@@ -21970,6 +22283,9 @@ function createWebSearchExecutor(deps = {}) {
21970
22283
  }
21971
22284
  successfulSearches += 1;
21972
22285
  events.push({ type: "search_result", hits: outcome.hits.map((h) => ({ title: h.title, url: h.url })) });
22286
+ for (const h of outcome.hits)
22287
+ if (h.favicon !== undefined)
22288
+ iconEntries.push({ url: h.url, iconUrl: h.favicon });
21973
22289
  }
21974
22290
  if (pass !== undefined && !pass.ok) {
21975
22291
  events.push({ type: "text", text: `
@@ -21981,12 +22297,150 @@ ${innerFailureText(pass)}` });
21981
22297
 
21982
22298
  ${lastFailureMessage}` });
21983
22299
  }
21984
- return { output: assembleWebSearchOutputCapped(query, events, resultCap) };
22300
+ const siteIcons = collectSiteIcons(iconEntries);
22301
+ return { output: assembleWebSearchOutputCapped(query, events, resultCap), ...siteIcons !== undefined ? { siteIcons } : {} };
21985
22302
  }
21986
22303
  return { execute };
21987
22304
  }
21988
22305
  replaceExecutor(WEB_SEARCH_CANONICAL_NAME, createWebSearchExecutor());
21989
22306
 
22307
+ // src/tools/impl/search.ts
22308
+ var SEARCH_REQUEST_TIMEOUT_MS = 45000;
22309
+ var SEARCH_ANSWER_CHARS = 24000;
22310
+ var SEARCH_MAX_CITATIONS = 20;
22311
+ var SEARCH_TOTAL_OUTPUT_CHARS = 30000;
22312
+ var EXA_ANSWER_URL = "https://api.exa.ai/answer";
22313
+ function isValidCitations(value) {
22314
+ if (value === undefined)
22315
+ return true;
22316
+ if (!Array.isArray(value))
22317
+ return false;
22318
+ return value.every((item) => item !== null && typeof item === "object");
22319
+ }
22320
+ function searchStatusMessage(status) {
22321
+ switch (status) {
22322
+ case 401:
22323
+ case 403:
22324
+ return "search failed: the configured Exa API key was rejected — it needs to be replaced before Search can work";
22325
+ case 402:
22326
+ return "search failed: this Exa account is out of credits or over its budget — top it up at exa.ai, or answer from what you already know and say the search was unavailable";
22327
+ case 429:
22328
+ return "search failed: the search service is rate-limiting this key — wait a little before searching again, and do not retry in a loop";
22329
+ case 400:
22330
+ return "search failed: the search service rejected the request as malformed — try a plainer question";
22331
+ default:
22332
+ return `search failed: the search service is unavailable (HTTP ${status})`;
22333
+ }
22334
+ }
22335
+ var SEARCH_NO_KEY_MESSAGE = "Search needs an Exa API key (from exa.ai), and none is configured for this session";
22336
+ function errorResult6(output) {
22337
+ return { output, isError: true };
22338
+ }
22339
+ function createSearchExecutor(deps = {}) {
22340
+ const endpoint = deps.endpoint ?? EXA_ANSWER_URL;
22341
+ const timeoutMs = deps.timeoutMs ?? SEARCH_REQUEST_TIMEOUT_MS;
22342
+ async function execute(rawInput, ctx) {
22343
+ const record = typeof rawInput === "object" && rawInput !== null ? rawInput : {};
22344
+ const query = typeof record["query"] === "string" ? record["query"] : "";
22345
+ if (query.trim().length === 0)
22346
+ return errorResult6("Error: Missing query");
22347
+ const runtime = webSessionRuntimeFor(ctx);
22348
+ if (runtime === undefined)
22349
+ return errorResult6("Error: Search is not available for this session (no search runtime is wired up for it -- a host wiring gap, not an input error).");
22350
+ if (!runtime.web.search.enabled)
22351
+ return { output: "Web search is turned off for this session." };
22352
+ const resolveKey = exaKeyResolverFor(runtime);
22353
+ if (resolveKey === undefined)
22354
+ return errorResult6(SEARCH_NO_KEY_MESSAGE);
22355
+ let key;
22356
+ try {
22357
+ const secret = await resolveKey();
22358
+ if (secret.status === "missing")
22359
+ return errorResult6(SEARCH_NO_KEY_MESSAGE);
22360
+ if (secret.status === "unreadable")
22361
+ return errorResult6(`search failed: an Exa API key is configured but could not be used: ${secret.message}`);
22362
+ key = secret.key;
22363
+ } catch (err) {
22364
+ return errorResult6(`search failed: the key resolver failed with ${err instanceof Error ? err.name : "an unknown error"}`);
22365
+ }
22366
+ if (key.length === 0)
22367
+ return errorResult6(SEARCH_NO_KEY_MESSAGE);
22368
+ const fetchFn = deps.fetchFn ?? fetch;
22369
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
22370
+ const signal = ctx.signal !== undefined ? AbortSignal.any([ctx.signal, timeoutSignal]) : timeoutSignal;
22371
+ let res;
22372
+ try {
22373
+ res = await fetchFn(endpoint, {
22374
+ method: "POST",
22375
+ headers: { "x-api-key": key, "content-type": "application/json" },
22376
+ body: JSON.stringify({ query }),
22377
+ redirect: "manual",
22378
+ signal
22379
+ });
22380
+ } catch (err) {
22381
+ const name = err instanceof Error ? err.name : "";
22382
+ if (name === "AbortError" || name === "TimeoutError") {
22383
+ return errorResult6(ctx.signal?.aborted === true ? "Search was interrupted." : `search timed out for ${query}`);
22384
+ }
22385
+ const rawMessage = err instanceof Error ? err.message : String(err);
22386
+ console.error(`Search: network error (${name || "Error"}) — ${rawMessage.replaceAll(key, "<redacted>")}`);
22387
+ return errorResult6("search failed: could not reach the search service");
22388
+ }
22389
+ if (res.status !== 200) {
22390
+ try {
22391
+ await res.body?.cancel();
22392
+ } catch {}
22393
+ return errorResult6(searchStatusMessage(res.status));
22394
+ }
22395
+ let data;
22396
+ try {
22397
+ data = await res.json();
22398
+ } catch (err) {
22399
+ return errorResult6(`search failed: could not parse response (${err instanceof Error ? err.message : String(err)})`);
22400
+ }
22401
+ if (data === null || typeof data !== "object" || !isValidCitations(data.citations))
22402
+ return errorResult6("search failed: malformed response from search service");
22403
+ if (data.answer !== undefined && typeof data.answer !== "string")
22404
+ return errorResult6("search failed: malformed response from search service");
22405
+ const fullAnswer = (data.answer ?? "").trim();
22406
+ const answer = fullAnswer.length > SEARCH_ANSWER_CHARS ? fullAnswer.slice(0, SEARCH_ANSWER_CHARS) + `
22407
+
22408
+ [answer truncated]` : fullAnswer;
22409
+ const hostFloor = Array.isArray(record["blocked_domains"]) ? record["blocked_domains"].filter((d) => typeof d === "string" && d.trim().length > 0).slice(0, 1000) : [];
22410
+ const floor = hostFloor.length > 0 ? [...runtime.web.blockedDomains, ...hostFloor] : runtime.web.blockedDomains;
22411
+ const rawCitations = (data.citations ?? []).slice(0, SEARCH_MAX_CITATIONS);
22412
+ let withheld = 0;
22413
+ const citations = rawCitations.filter((c) => {
22414
+ if (typeof c.url !== "string" || c.url.length === 0 || !isDomainBlocked(c.url, floor))
22415
+ return true;
22416
+ withheld++;
22417
+ return false;
22418
+ });
22419
+ const withheldNote = withheld > 0 ? `
22420
+
22421
+ [${withheld} source${withheld === 1 ? "" : "s"} withheld — matched the dangerous-domain list]` : "";
22422
+ const siteIcons = collectSiteIcons(citations.map((c) => ({ url: c.url, iconUrl: c.favicon })));
22423
+ const withIcons = (output) => ({ output, ...siteIcons !== undefined ? { siteIcons } : {} });
22424
+ if (answer === "")
22425
+ return withIcons(`no answer for ${query}${withheldNote}`);
22426
+ const sources = citations.length === 0 ? `
22427
+
22428
+ [unsourced — ${withheld > 0 ? "every source was withheld by the dangerous-domain list" : "the search service returned no sources"}; say so if you repeat this]` : `
22429
+
22430
+ Sources:
22431
+ ` + citations.map((c, i) => `${i + 1}. ${(typeof c.title === "string" ? c.title.trim() : "") || "-"}
22432
+ ${typeof c.url === "string" && c.url.length > 0 ? c.url : "-"}`).join(`
22433
+ `);
22434
+ const rendered = answer + sources;
22435
+ const capped = rendered.length > SEARCH_TOTAL_OUTPUT_CHARS ? rendered.slice(0, SEARCH_TOTAL_OUTPUT_CHARS) + `
22436
+
22437
+ [truncated]` : rendered;
22438
+ return withIcons(capped + withheldNote);
22439
+ }
22440
+ return { execute };
22441
+ }
22442
+ replaceExecutor(SEARCH_CANONICAL_NAME, createSearchExecutor());
22443
+
21990
22444
  // src/tools/read-state.ts
21991
22445
  import { isAbsolute as isAbsolute6, join as join24, normalize as normalize3 } from "node:path";
21992
22446
  function createSessionReadState(opts) {
@@ -22207,6 +22661,18 @@ function toolResultsForHost(content) {
22207
22661
  };
22208
22662
  return blank(content) ?? content;
22209
22663
  }
22664
+ function withHostSiteIcons(content, icons) {
22665
+ if (icons.size === 0)
22666
+ return content;
22667
+ return content.map((block) => {
22668
+ if (block.type !== "tool_result")
22669
+ return block;
22670
+ const forCall = icons.get(block.tool_use_id);
22671
+ if (forCall === undefined || forCall.length === 0)
22672
+ return block;
22673
+ return { ...block, winter_site_icons: forCall.map((icon) => ({ url: icon.url, icon_url: icon.iconUrl })) };
22674
+ });
22675
+ }
22210
22676
  function asRebuiltContent(content) {
22211
22677
  return content.length === 1 && content[0].type === "text" ? content[0].text : content;
22212
22678
  }
@@ -22349,11 +22815,48 @@ function toolOutputReplacement(value) {
22349
22815
  return capHookText(String(value), cap);
22350
22816
  }
22351
22817
  }
22818
+ function withLegacyToolNameRules(opts) {
22819
+ const legacy = opts.config.legacyToolNames;
22820
+ if (legacy === undefined || Object.keys(legacy).length === 0)
22821
+ return opts;
22822
+ const expand = (rules) => {
22823
+ if (rules === undefined)
22824
+ return;
22825
+ const out = [...rules];
22826
+ for (const raw of rules) {
22827
+ const head = /^([^(]+)(.*)$/s.exec(raw.trim());
22828
+ if (head === null)
22829
+ continue;
22830
+ const target = Object.hasOwn(legacy, head[1]) ? legacy[head[1]] : undefined;
22831
+ if (typeof target === "string" && target.length > 0)
22832
+ out.push(`${target}${head[2]}`);
22833
+ }
22834
+ return [...new Set(out)];
22835
+ };
22836
+ const config = opts.config;
22837
+ const permissions = config.permissions;
22838
+ return {
22839
+ ...opts,
22840
+ config: {
22841
+ ...config,
22842
+ ...config.allowedTools !== undefined ? { allowedTools: expand(config.allowedTools) } : {},
22843
+ ...config.disallowedTools !== undefined ? { disallowedTools: expand(config.disallowedTools) } : {},
22844
+ ...permissions !== undefined ? {
22845
+ permissions: {
22846
+ ...permissions,
22847
+ ...permissions.allow !== undefined ? { allow: expand(permissions.allow) } : {},
22848
+ ...permissions.ask !== undefined ? { ask: expand(permissions.ask) } : {},
22849
+ ...permissions.deny !== undefined ? { deny: expand(permissions.deny) } : {}
22850
+ }
22851
+ } : {}
22852
+ }
22853
+ };
22854
+ }
22352
22855
  async function runEngine2(opts) {
22353
22856
  const facetDisposers = [];
22354
22857
  let sweepBackgroundShellTasksOnExit;
22355
22858
  try {
22356
- return await runEngineBody(opts, facetDisposers, (fn) => {
22859
+ return await runEngineBody(withLegacyToolNameRules(opts), facetDisposers, (fn) => {
22357
22860
  sweepBackgroundShellTasksOnExit = fn;
22358
22861
  });
22359
22862
  } finally {
@@ -22434,6 +22937,16 @@ async function runEngineBody(opts, facetDisposers, registerBackgroundShellSweep)
22434
22937
  resolveToolSecret: resolveToolSecretRoute,
22435
22938
  onPricedGeneration
22436
22939
  } = opts;
22940
+ const legacyToolNames = config.legacyToolNames ?? {};
22941
+ const currentToolName = (name) => {
22942
+ if (getRegisteredTool(name) !== undefined)
22943
+ return name;
22944
+ const legacy = Object.hasOwn(legacyToolNames, name) ? legacyToolNames[name] : undefined;
22945
+ if (typeof legacy === "string" && getRegisteredTool(legacy) !== undefined)
22946
+ return legacy;
22947
+ return name.startsWith("mcp__") ? registeredNameForMcpSpelling(name) : name;
22948
+ };
22949
+ const legacyNameOf = (name) => Object.entries(legacyToolNames).find(([old, current]) => current === name && getRegisteredTool(old) === undefined)?.[0];
22437
22950
  registerBackgroundShellSweep(() => {
22438
22951
  const swept = stopSessionShellTasks({ sessionId: config.sessionId, ...config.agentId !== undefined ? { agentId: config.agentId } : {} });
22439
22952
  if (swept.length > 0) {
@@ -22795,6 +23308,7 @@ ${escapeAttributionText(msg.body)}
22795
23308
  opts.onChildRosterReady?.(() => childRoster);
22796
23309
  opts.onForegroundChildrenReady?.(() => [...foregroundChildren]);
22797
23310
  const removeChildRosterSource = ensureDefaultMessagingRuntimeRegistered().addChildRosterSource(() => childRoster);
23311
+ const removeHostMessagingPort = config.hostMessaging === true && config.agentId === undefined ? getDefaultMessagingRuntime()?.registerHostMessagingPort?.(config.sessionId, createHostMessagingPort(bridge)) : undefined;
22798
23312
  const sandboxSettingsForSession = config.sandbox ?? DEFAULT_SANDBOX_SETTINGS;
22799
23313
  const bashPostureFor = (call, dangerouslyDisableSandbox) => {
22800
23314
  const command = call.input["command"];
@@ -23148,7 +23662,7 @@ ${escapeAttributionText(msg.body)}
23148
23662
  });
23149
23663
  return {
23150
23664
  policy: policyResult,
23151
- tools: req.definition?.tools !== undefined && !req.definition.tools.includes("*") ? req.definition.tools.filter((name) => currentAdvertisedCanonicalNames.includes(name)) : [...currentAdvertisedCanonicalNames],
23665
+ tools: req.definition?.tools !== undefined && !req.definition.tools.includes("*") ? [...new Set(req.definition.tools.map(currentToolName))].filter((name) => currentAdvertisedCanonicalNames.includes(name)) : [...currentAdvertisedCanonicalNames],
23152
23666
  ...childSlot,
23153
23667
  effort: req.definition?.effort !== undefined ? String(req.definition.effort) : "inherit",
23154
23668
  thinking: undefined,
@@ -23323,7 +23837,7 @@ ${escapeAttributionText(msg.body)}
23323
23837
  const denyEntry = findAgentDenyRule(evalCtx.policy.rules, agentType, denyOpts);
23324
23838
  if (denyEntry !== undefined)
23325
23839
  return agentTypeDeniedMessage(agentType, denyEntry);
23326
- if (isBuiltinAllToolsDenied(def, currentAdvertisedCanonicalNames)) {
23840
+ if (isBuiltinAllToolsDenied({ ...def.tools !== undefined ? { tools: def.tools.map(currentToolName) } : {} }, currentAdvertisedCanonicalNames)) {
23327
23841
  return `Agent type '${agentType}' is unavailable because every tool it may use is denied by the current permission settings.`;
23328
23842
  }
23329
23843
  return;
@@ -23343,11 +23857,12 @@ ${escapeAttributionText(msg.body)}
23343
23857
  if (serverCfg.type !== "sdk" || !serverCfg.tools || serverCfg.tools.length === 0)
23344
23858
  continue;
23345
23859
  const toolDefs = serverCfg.tools;
23346
- registerMcpServerTools(serverName, toolDefs, { deferredDefault: true });
23860
+ const toolNames = serverCfg.toolNames;
23861
+ registerMcpServerTools(serverName, toolDefs, { deferredDefault: true, ...toolNames !== undefined ? { toolNames } : {} });
23347
23862
  sdkMcpServerNames.push(serverName);
23348
23863
  const perServerTimeoutMs = serverCfg.timeout;
23349
23864
  for (const tool of toolDefs) {
23350
- const canonicalName = `mcp__${serverName}__${tool.name}`;
23865
+ const canonicalName = toolNames !== undefined && Object.hasOwn(toolNames, tool.name) ? toolNames[tool.name] : `mcp__${serverName}__${tool.name}`;
23351
23866
  replaceExecutor(canonicalName, {
23352
23867
  async execute(input) {
23353
23868
  const timeoutMs = perServerTimeoutMs ?? mcpEnvConfig.toolTimeoutMs ?? 120000;
@@ -23379,13 +23894,15 @@ ${escapeAttributionText(msg.body)}
23379
23894
  const resolvedSources = resolveMcpServerSources(sources, {
23380
23895
  ...config.strictMcpConfig !== undefined ? { strictMcpConfig: config.strictMcpConfig } : {},
23381
23896
  trustedWorkspace,
23382
- reservedServerName: sessionBrand.mcpServerName
23897
+ reservedServerName: sessionBrand.mcpServerName,
23898
+ ...config.reservedMcpServerNames !== undefined ? { hostReservedServerNames: config.reservedMcpServerNames } : {}
23383
23899
  });
23384
23900
  mcpLifecycle = createMcpLifecycle({
23385
23901
  servers: resolvedSources.resolved,
23386
23902
  envConfig: mcpEnvConfig,
23387
23903
  elicitationAsk: createElicitationAsker2(bridge),
23388
23904
  reservedServerName: sessionBrand.mcpServerName,
23905
+ ...config.reservedMcpServerNames !== undefined ? { hostReservedServerNames: config.reservedMcpServerNames } : {},
23389
23906
  cwd: config.cwd,
23390
23907
  ...mcpOAuthStore !== undefined ? { oauth: createSessionMcpOAuth({ store: mcpOAuthStore, sender: bridge, brand: sessionBrand, ...config.hostCredentials === true ? { hostOwnsRefresh: true } : {} }) } : {}
23391
23908
  });
@@ -23395,11 +23912,13 @@ ${escapeAttributionText(msg.body)}
23395
23912
  const effectiveMcpStateSource = mcpServerStateSource ?? mcpLifecycle?.stateSource;
23396
23913
  const effectiveMcpControlSeam = mcpControlSeam ?? mcpLifecycle?.controlSeam;
23397
23914
  const sessionHasMcp = () => (effectiveMcpStateSource?.snapshot().length ?? 0) > 0;
23398
- const enableToolSearch = config.toolSearchEnabled === true ? "true" : config.toolSearchEnabled === false ? "false" : mcpEnvConfig.enableToolSearch;
23915
+ const toolSearchExcludedByTools = config.tools !== undefined && !config.tools.includes(TOOL_SEARCH_TOOL_NAME);
23916
+ const enableToolSearch = toolSearchExcludedByTools ? "false" : config.toolSearchEnabled === true ? "true" : config.toolSearchEnabled === false ? "false" : mcpEnvConfig.enableToolSearch;
23399
23917
  const deferralActivation = {
23400
23918
  enableToolSearch,
23401
23919
  providerSupportsToolSearch: providerSupportsToolSearch ?? true,
23402
- deferrableContextShare: deferrableContextShare ?? 0
23920
+ deferrableContextShare: deferrableContextShare ?? 0,
23921
+ ...config.deferTools !== undefined ? { deferTools: config.deferTools } : {}
23403
23922
  };
23404
23923
  const toolSearchEnabledDerived = isDeferralActive(deferralActivation);
23405
23924
  const enableFileCheckpointing = config.enableFileCheckpointing === true;
@@ -23432,7 +23951,7 @@ ${escapeAttributionText(msg.body)}
23432
23951
  return availableAgentNames(defs, {
23433
23952
  isDenied: (agentType) => findAgentDenyRule(evalCtx.policy.rules, agentType, denyOpts) !== undefined,
23434
23953
  ...config.allowedAgentTypes !== undefined ? { allowedAgentTypes: config.allowedAgentTypes } : {},
23435
- isAllToolsDenied: (def) => isBuiltinAllToolsDenied(def, currentAdvertisedCanonicalNames)
23954
+ isAllToolsDenied: (def) => isBuiltinAllToolsDenied({ ...def.tools !== undefined ? { tools: def.tools.map(currentToolName) } : {} }, currentAdvertisedCanonicalNames)
23436
23955
  });
23437
23956
  }
23438
23957
  function sessionAvailableAgentDefinitions(at = {}) {
@@ -23503,11 +24022,14 @@ ${escapeAttributionText(msg.body)}
23503
24022
  facetDisposers.push(forgetSessionWebState);
23504
24023
  const WEB_DERIVED_CAPABILITIES = [
23505
24024
  { token: "winter.search-backend", probeTool: "WebSearch", fact: () => searchBackendUsable(webSessionRuntime) },
23506
- { token: "winter.fetch-extractor", probeTool: "WebFetch", fact: () => digestModelResolves(webSessionRuntime) }
24025
+ { token: "winter.fetch-extractor", probeTool: "WebFetch", fact: () => digestModelResolves(webSessionRuntime) },
24026
+ { token: SEARCH_ANSWER_CAPABILITY, probeTool: SEARCH_CANONICAL_NAME, fact: () => searchAnswerUsable(webSessionRuntime) }
23507
24027
  ];
23508
24028
  const resolveLiveSessionCapabilities = () => {
23509
24029
  const base = resolveSessionCapabilities(config.capabilities, { hasMcpServers: sessionHasMcp() });
23510
24030
  const derived = reviewerResolves() ? [ADVISOR_CAPABILITY] : [];
24031
+ if ((config.deferTools?.length ?? 0) > 0)
24032
+ derived.push(DEFERRED_BUILTINS_CAPABILITY);
23511
24033
  for (const cap of WEB_DERIVED_CAPABILITIES) {
23512
24034
  if (getRegisteredTool(cap.probeTool)?.executor !== undefined && cap.fact())
23513
24035
  derived.push(cap.token);
@@ -23521,6 +24043,7 @@ ${escapeAttributionText(msg.body)}
23521
24043
  mode: policyStateStore.getState().mode,
23522
24044
  platform: process.platform,
23523
24045
  ...config.disallowedTools !== undefined ? { disallowedTools: config.disallowedTools } : {},
24046
+ ...config.tools !== undefined ? { tools: config.tools } : {},
23524
24047
  capabilities: sessionCapabilities,
23525
24048
  toolSearchEnabled: toolSearchEnabledDerived,
23526
24049
  ...config.insideSubagent !== undefined ? { insideSubagent: config.insideSubagent } : {},
@@ -23606,6 +24129,8 @@ ${escapeAttributionText(msg.body)}
23606
24129
  return visibleServerToolFilter();
23607
24130
  },
23608
24131
  ...config.disallowedTools !== undefined ? { disallowedTools: config.disallowedTools } : {},
24132
+ ...config.tools !== undefined ? { tools: config.tools } : {},
24133
+ resolveName: currentToolName,
23609
24134
  ...config.insideSubagent !== undefined ? { insideSubagent: config.insideSubagent } : {},
23610
24135
  ...config.familyMetadata !== undefined ? { familyMetadata: config.familyMetadata } : {},
23611
24136
  ...config.toolAliases !== undefined ? { toolAliases: config.toolAliases } : {},
@@ -24638,6 +25163,12 @@ ${escapeAttributionText(msg.body)}
24638
25163
  return { system: joinSystemBlocks(systemBlocks), systemBlocks };
24639
25164
  };
24640
25165
  const requestMessages = (context, effort, toolChanges) => buildRequestMessages(messages, context.userContextText, { systemReminders: systemRemindersOnWire(), ...effort !== undefined ? { effort } : {}, ...toolChanges !== undefined ? { toolChanges } : {} });
25166
+ const deferredToolNamesToAnnounce = () => {
25167
+ refreshAdvertisedPartition();
25168
+ const eagerNames = new Set(advertisedPartition.eager.map((d) => d.canonicalName));
25169
+ const twins = new Set(Object.entries(suppressionAliasTable).filter(([source]) => eagerNames.has(source)).map(([, target]) => target));
25170
+ return [...new Set(advertisedPartition.deferred.filter((d) => !twins.has(d.canonicalName)).map((d) => d.advertisedName))];
25171
+ };
24641
25172
  const systemRemindersOnWire = () => currentModelDescription()?.wire?.midConversationSystem === true;
24642
25173
  const currentModelDescription = () => {
24643
25174
  const key = currentProviderIdentity?.modelKey ?? currentModel;
@@ -24793,6 +25324,13 @@ ${escapeAttributionText(msg.body)}
24793
25324
  produced.push(needsAuth);
24794
25325
  }
24795
25326
  }
25327
+ if (exactRequestLayout === undefined) {
25328
+ const deferredNow = deferredToolNamesToAnnounce();
25329
+ const offeredNow = [...advertisedPartition.eager, ...advertisedPartition.deferred].map((d) => d.advertisedName);
25330
+ const deferredDelta = computeDeferredToolsDelta(deferredNow, offeredNow, messages);
25331
+ if (deferredDelta !== undefined)
25332
+ produced.push(deferredDelta);
25333
+ }
24796
25334
  if (phase === "tool-round") {
24797
25335
  const attachment = taskNotificationAttachment(notifications.drainFor(config.agentId, { maxPriority: "next" }), {
24798
25336
  inHumanTurn: config.agentId === undefined && !turnStartedByNotification
@@ -25031,15 +25569,19 @@ ${escapeAttributionText(msg.body)}
25031
25569
  return cached ? { name: identity.name, configName: identity.configName } : identity;
25032
25570
  };
25033
25571
  const declaredMcpIdentity = (toolName) => {
25034
- if (mcpServerRenames === undefined)
25035
- return;
25036
- for (const [actual, declared] of Object.entries(mcpServerRenames)) {
25037
- if (toolName === `mcp__${actual}`)
25038
- return `mcp__${declared}`;
25039
- if (toolName.startsWith(`mcp__${actual}__`))
25040
- return `mcp__${declared}__${toolName.slice(`mcp__${actual}__`.length)}`;
25572
+ const legacy = legacyNameOf(toolName);
25573
+ if (legacy !== undefined)
25574
+ return legacy;
25575
+ const spelled = mcpSpellingOf(toolName) ?? toolName;
25576
+ if (mcpServerRenames !== undefined) {
25577
+ for (const [actual, declared] of Object.entries(mcpServerRenames)) {
25578
+ if (spelled === `mcp__${actual}`)
25579
+ return `mcp__${declared}`;
25580
+ if (spelled.startsWith(`mcp__${actual}__`))
25581
+ return `mcp__${declared}__${spelled.slice(`mcp__${actual}__`.length)}`;
25582
+ }
25041
25583
  }
25042
- return;
25584
+ return spelled !== toolName ? spelled : undefined;
25043
25585
  };
25044
25586
  const postToolHookSubject = (event, toolName) => {
25045
25587
  const declared = declaredMcpIdentity(toolName);
@@ -25612,10 +26154,13 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
25612
26154
  ...generationEffort ?? {}
25613
26155
  });
25614
26156
  const resultBlocks = [];
26157
+ const hostSiteIcons = new Map;
25615
26158
  let toolThrowText = null;
25616
26159
  let structuredTerminated = false;
25617
26160
  const truncatedByOutputLimit = turn.stopReason === "max_tokens";
25618
- for (const call of turn.calls) {
26161
+ for (const emitted of turn.calls) {
26162
+ const resolvedName = currentToolName(emitted.name);
26163
+ const call = resolvedName !== emitted.name ? { ...emitted, name: resolvedName } : emitted;
25619
26164
  const stoppedBy = currentTurnStop();
25620
26165
  if (stoppedBy !== undefined) {
25621
26166
  resultBlocks.push({ type: "tool_result", tool_use_id: call.id, content: `[not executed: the ${stoppedBy.hookName} hook stopped the turn]`, error: true });
@@ -25847,6 +26392,8 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
25847
26392
  ...loadedTools.length > 0 ? { loadedTools } : {},
25848
26393
  ...loadedToolDefinitions.length > 0 ? { loadedToolDefinitions } : {}
25849
26394
  });
26395
+ if (raced.value.siteIcons !== undefined && raced.value.siteIcons.length > 0)
26396
+ hostSiteIcons.set(call.id, raced.value.siteIcons);
25850
26397
  const postToolUseHookOutcome = raced.value.isError === true ? await fireObservationalHook("PostToolUseFailure", {
25851
26398
  toolUseID: call.id,
25852
26399
  toolName: postToolHookSubject("PostToolUseFailure", call.name),
@@ -25908,7 +26455,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
25908
26455
  }
25909
26456
  }
25910
26457
  }
25911
- output.write({ type: "data", message: { type: "user", message: { content: toolResultsForHost(resultBlocks.map((b) => b.type === "tool_result" && b.loadedTools !== undefined ? withoutLoadedTools(b) : b)) } } });
26458
+ output.write({ type: "data", message: { type: "user", message: { content: withHostSiteIcons(toolResultsForHost(resultBlocks.map((b) => b.type === "tool_result" && b.loadedTools !== undefined ? withoutLoadedTools(b) : b)), hostSiteIcons) } } });
25912
26459
  messages.push({ role: "tool", content: resultBlocks });
25913
26460
  await recordUser(resultBlocks);
25914
26461
  await flushPendingHookAttachments();
@@ -25968,6 +26515,7 @@ ${reason}` : ""}` : `${promptStop.hookName} hook stopped continuation${reason.le
25968
26515
  if (config.agentId === undefined)
25969
26516
  clearNotificationQueue(config.sessionId);
25970
26517
  removeChildRosterSource();
26518
+ removeHostMessagingPort?.();
25971
26519
  facetSelfPeer?.();
25972
26520
  facetNoticeForwarder?.();
25973
26521
  disposeSessionMcpLifecycle?.();
@@ -26168,8 +26716,43 @@ var TEST_PROVIDER_NAMES = new Set([
26168
26716
  "p5skill",
26169
26717
  "p5checkpoint",
26170
26718
  "p5workflow",
26171
- "readimage"
26719
+ "readimage",
26720
+ "calls"
26172
26721
  ]);
26722
+ var CALL_LINE = /^CALL\s+(\S+)(?:\s+(.*))?$/;
26723
+ function promptedCallsProvider() {
26724
+ return {
26725
+ async generate({ messages }) {
26726
+ let at = -1;
26727
+ for (let i = messages.length - 1;i >= 0; i--) {
26728
+ const m = messages[i];
26729
+ if (m.role === "user" && userMessageText(m).split(`
26730
+ `).some((line) => CALL_LINE.test(line.trim()))) {
26731
+ at = i;
26732
+ break;
26733
+ }
26734
+ }
26735
+ if (at < 0)
26736
+ return { kind: "text", text: "[]" };
26737
+ const script = userMessageText(messages[at]).split(`
26738
+ `).map((line) => CALL_LINE.exec(line.trim())).filter((m) => m !== null).map((m) => ({ name: m[1], input: m[2] !== undefined && m[2].trim() !== "" ? JSON.parse(m[2]) : {} }));
26739
+ const after = messages.slice(at + 1);
26740
+ const made = after.filter((m) => m.role === "assistant" && Array.isArray(m.content) && m.content.some((b) => b.type === "tool_use")).length;
26741
+ if (made < script.length) {
26742
+ const next = script[made];
26743
+ return { kind: "tool_use", calls: [{ id: `calls-${at + 1}-${made + 1}`, name: next.name, input: next.input }] };
26744
+ }
26745
+ const results = after.flatMap((m) => Array.isArray(m.content) ? m.content : []).filter((b) => b.type === "tool_result");
26746
+ const named = new Map(after.flatMap((m) => m.role === "assistant" && Array.isArray(m.content) ? m.content : []).flatMap((b) => b.type === "tool_use" ? [[b.id, b.name]] : []));
26747
+ const text = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.map((b) => b !== null && typeof b === "object" && typeof b.text === "string" ? b.text : "").join(`
26748
+ `) : String(content);
26749
+ return {
26750
+ kind: "text",
26751
+ text: JSON.stringify(results.map((r) => ({ name: named.get(r.tool_use_id) ?? null, isError: r.is_error === true || r.error === true, content: text(r.content) })))
26752
+ };
26753
+ }
26754
+ };
26755
+ }
26173
26756
  function isTestProviderName2(v) {
26174
26757
  return TEST_PROVIDER_NAMES.has(v);
26175
26758
  }
@@ -26334,6 +26917,8 @@ function rawTestProviderByName(name) {
26334
26917
  }
26335
26918
  };
26336
26919
  }
26920
+ case "calls":
26921
+ return promptedCallsProvider();
26337
26922
  case "mcpsdk":
26338
26923
  return scriptedProvider2([
26339
26924
  { kind: "tool_use", calls: [{ id: "mcpsdk-call-1", name: MCP_SDK_TEST_TOOL_NAME2, input: { x: 1 } }] },
@@ -27090,7 +27675,7 @@ class TranscriptWriter2 {
27090
27675
  return entries;
27091
27676
  }
27092
27677
  }
27093
- var RUNTIME_ENGINE_VERSION2 = "0.0.36";
27678
+ var RUNTIME_ENGINE_VERSION2 = "0.0.39";
27094
27679
  function withPermissionJournal(writer, location) {
27095
27680
  return {
27096
27681
  transcriptPath: join26(location.winterHome, "projects", location.projectKey, `${location.sessionId}.jsonl`),