@yanlinglabs/winter-agent-runtime 0.0.36 → 0.0.38

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,14 +9,14 @@ import {
9
9
  resolveEngineSession2,
10
10
  resolveProductionWinterHome2,
11
11
  resolveProductionStoreHome
12
- } from "./index-c8j57y8v.js";
12
+ } from "./index-5xe59vkt.js";
13
13
  import {
14
14
  registerDefaultChildEngineFactory,
15
15
  loadResumedChain,
16
16
  buildProductionWiring,
17
17
  withAutoSkillPermissions,
18
18
  restoreChildRoster
19
- } from "./index-83p1g9ap.js";
19
+ } from "./index-z3970wsj.js";
20
20
 
21
21
  // src/embedded.ts
22
22
  import { splitFrames, encodeFrame } from "@yanlinglabs/winter-agent-sdk";
@@ -85,7 +85,7 @@ import {
85
85
  providerCredentialRef2,
86
86
  createModelClassifier2,
87
87
  selectClassifierRoute2
88
- } from "./index-c8j57y8v.js";
88
+ } from "./index-5xe59vkt.js";
89
89
  import {
90
90
  createKeychainCredentialStore,
91
91
  createKeychainSecretReader,
@@ -311,7 +311,7 @@ function applySubagentToolPool(tools, opts) {
311
311
  return true;
312
312
  if (policyName === BACKGROUND_AGENT_ALWAYS_ALLOWED_TOOL)
313
313
  return true;
314
- if (name.startsWith("mcp__") && !twins.has(name))
314
+ if (!twins.has(name) && (name.startsWith("mcp__") || getRegisteredTool(name)?.descriptor.source === "mcp"))
315
315
  return true;
316
316
  return allow.has(policyName);
317
317
  });
@@ -492,12 +492,14 @@ function renameMcpReference(entry, renames) {
492
492
  }
493
493
  return;
494
494
  }
495
- function allocateChildScopedServers(declared, parentVisible, prefer, parentDeclared) {
495
+ function allocateChildScopedServers(declared, parentVisible, prefer, parentDeclared, reserved) {
496
496
  const claimed = [];
497
- const inUse = (name) => liveChildScopedServerNames.has(name) || parentVisible.has(name) || mcpServerHasRegistrations(name);
497
+ const reservedSet = new Set(reserved ?? []);
498
+ const inUse = (name) => liveChildScopedServerNames.has(name) || parentVisible.has(name) || mcpServerHasRegistrations(name) || reservedSet.has(name);
498
499
  const servers = {};
499
500
  const actual = new Map;
500
501
  const notes = [];
502
+ const reservedRenames = new Set;
501
503
  const claim = (name) => {
502
504
  liveChildScopedServerNames.add(name);
503
505
  claimed.push(name);
@@ -505,7 +507,7 @@ function allocateChildScopedServers(declared, parentVisible, prefer, parentDecla
505
507
  for (const [name, cfg] of Object.entries(declared)) {
506
508
  if (cfg.type === "sdk") {
507
509
  if (inUse(name)) {
508
- notes.push(parentVisible.has(name) && parentDeclared?.[name]?.type === "sdk" ? `mcpServers declares the in-process server "${name}", which this session already has -- the session's "${name}" is used` : parentVisible.has(name) ? `mcpServers declares the in-process server "${name}", but this session's "${name}" is a different server -- this agent's own in-process "${name}" is not connected, and the session's is what this agent sees` : `mcpServers declares the in-process server "${name}", which another agent in this session is using -- this agent's own in-process "${name}" is not connected`);
510
+ notes.push(parentVisible.has(name) && parentDeclared?.[name]?.type === "sdk" ? `mcpServers declares the in-process server "${name}", which this session already has -- the session's "${name}" is used` : parentVisible.has(name) ? `mcpServers declares the in-process server "${name}", but this session's "${name}" is a different server -- this agent's own in-process "${name}" is not connected, and the session's is what this agent sees` : reservedSet.has(name) && !liveChildScopedServerNames.has(name) && !mcpServerHasRegistrations(name) ? `mcpServers declares the in-process server "${name}", a name this session's host reserves -- this agent's own in-process "${name}" is not connected` : `mcpServers declares the in-process server "${name}", which another agent in this session is using -- this agent's own in-process "${name}" is not connected`);
509
511
  continue;
510
512
  }
511
513
  claim(name);
@@ -523,6 +525,8 @@ function allocateChildScopedServers(declared, parentVisible, prefer, parentDecla
523
525
  claim(pick);
524
526
  servers[pick] = cfg;
525
527
  actual.set(name, pick);
528
+ if (pick !== name && reservedSet.has(name))
529
+ reservedRenames.add(name);
526
530
  if (pick !== name)
527
531
  notes.push(`mcpServers declares "${name}", which is already in use in this session -- this agent's own "${name}" is connected as "${pick}" (its tools are named mcp__${pick}__<tool>)`);
528
532
  }
@@ -531,6 +535,7 @@ function allocateChildScopedServers(declared, parentVisible, prefer, parentDecla
531
535
  servers,
532
536
  actual,
533
537
  notes,
538
+ reservedRenames,
534
539
  release: () => {
535
540
  if (released)
536
541
  return;
@@ -775,7 +780,7 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
775
780
  ...!ownServers && parentMcp?.stateSource !== undefined ? { mcpServerStateSource: parentMcp.stateSource } : {},
776
781
  ...!ownServers && parentMcp?.controlSeam !== undefined ? { mcpControlSeam: parentMcp.controlSeam } : {},
777
782
  ...(() => {
778
- const merged = { ...parentMcp?.serverRenames ?? {}, ...Object.fromEntries(renames.map(([declared, actual]) => [actual, declared])) };
783
+ const merged = { ...parentMcp?.serverRenames ?? {}, ...Object.fromEntries(renames.filter(([declared]) => !servers.reservedRenames.has(declared)).map(([declared, actual]) => [actual, declared])) };
779
784
  return Object.keys(merged).length > 0 ? { mcpServerRenames: merged } : {};
780
785
  })(),
781
786
  ...parentMcp?.visibleServerNames !== undefined ? { inheritedMcpServerNames: ownServerNames.size === 0 ? parentMcp.visibleServerNames : () => parentMcp.visibleServerNames().filter((name) => !ownServerNames.has(name)) } : {},
@@ -886,7 +891,12 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
886
891
  const complementDeny = allToolNames.filter((name) => !allowSet.has(name));
887
892
  const disallowedTools = [...new Set([...complementDeny, ...req.definition?.disallowedTools ?? []])];
888
893
  const allowedAgentTypes = allowedAgentTypesFromTools(req.definition?.tools);
889
- const capabilities = [...new Set(effectiveTools.flatMap((name) => getRegisteredTool(name)?.descriptor.capabilityRequirements ?? []))];
894
+ const capabilities = [
895
+ ...new Set(effectiveTools.flatMap((name) => {
896
+ const descriptor = getRegisteredTool(name)?.descriptor;
897
+ return [...descriptor?.capabilityRequirements ?? [], ...descriptor?.availability.requiresAnyCapability ?? []];
898
+ }))
899
+ ];
890
900
  const definitionWarnings = req.definition !== undefined ? validateAgentDefinition(req.definition) : [];
891
901
  const parentMcp = runCtx.getParentMcpState?.();
892
902
  const parentAgents = runCtx.getParentAgents?.();
@@ -955,6 +965,8 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
955
965
  ...deps.parentBrand !== undefined ? { brand: deps.parentBrand } : {},
956
966
  ...deps.parentAutoMemory !== undefined ? { autoMemory: deps.parentAutoMemory } : {},
957
967
  ...deps.parentWeb !== undefined ? { web: deps.parentWeb } : {},
968
+ ...deps.parentReservedMcpServerNames !== undefined ? { reservedMcpServerNames: deps.parentReservedMcpServerNames } : {},
969
+ ...deps.parentLegacyToolNames !== undefined ? { legacyToolNames: deps.parentLegacyToolNames } : {},
958
970
  model: resolvedModel.effectiveModel,
959
971
  permissionMode: inherit.policy.effectiveMode,
960
972
  allowDangerouslySkipPermissions: inherit.policy.effectiveMode === "bypassPermissions",
@@ -965,6 +977,7 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
965
977
  ...req.fork === true && inherit.effectiveEffort !== undefined && typeof inherit.effectiveEffort !== "number" ? { effort: inherit.effectiveEffort } : {},
966
978
  ...req.fork === true && inherit.effectiveThinking !== undefined ? { thinking: inherit.effectiveThinking } : {},
967
979
  disallowedTools,
980
+ tools: [...effectiveTools],
968
981
  capabilities,
969
982
  ...allowedAgentTypes !== undefined ? { allowedAgentTypes } : {},
970
983
  forwardSubagentText: deps.forwardSubagentText === true,
@@ -980,7 +993,7 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
980
993
  const resolvedSystemPrompt = [inherit.systemPrompt, req.fork === true ? undefined : req.definition?.prompt].filter((s) => s !== undefined && s.length > 0).join(`
981
994
 
982
995
  `);
983
- let serverAllocation = allocateChildScopedServers(childScopedMcpServers, parentVisibleServers(), undefined, parentMcp?.declaredServers);
996
+ let serverAllocation = allocateChildScopedServers(childScopedMcpServers, parentVisibleServers(), undefined, parentMcp?.declaredServers, deps.parentReservedMcpServerNames);
984
997
  pendingServerRelease = serverAllocation.release;
985
998
  definitionWarnings.push(...serverAllocation.notes);
986
999
  const firstTurnText = req.fork === true ? buildForkDirectiveText({ prompt: req.prompt, ...req.isolation === "worktree" ? { worktree: { parentRoot: inherit.sessionRoot, worktreeRoot: workspace.root } } : {} }) : [req.definition?.initialPrompt, req.prompt, definitionWarnings.length > 0 ? `
@@ -1093,7 +1106,7 @@ async function spawnChildEngine(req, inherit, runCtx, deps) {
1093
1106
  pendingResume = undefined;
1094
1107
  if (ticket.stopped)
1095
1108
  return { status: "unavailable", messageId: msg.messageId, retryable: false, reason: `child ${agentId} was stopped while resuming` };
1096
- serverAllocation = allocateChildScopedServers(childScopedMcpServers, parentVisibleServers(), previous, parentMcp?.declaredServers);
1109
+ serverAllocation = allocateChildScopedServers(childScopedMcpServers, parentVisibleServers(), previous, parentMcp?.declaredServers, deps.parentReservedMcpServerNames);
1097
1110
  const moved = [...serverAllocation.actual].filter(([declared, name]) => previous.get(declared) !== name).map(([declared, name]) => `this agent's own "${declared}" is now connected as "${name}" (its tools are named mcp__${name}__<tool>)`);
1098
1111
  startGeneration(generationConfig(resumeMode), rebuilt, moved.length > 0 ? `${msg.body}
1099
1112
 
@@ -1159,6 +1172,8 @@ function registerDefaultChildEngineFactory(opts) {
1159
1172
  ...config.brand !== undefined ? { parentBrand: config.brand } : {},
1160
1173
  ...config.autoMemory !== undefined ? { parentAutoMemory: config.autoMemory } : {},
1161
1174
  ...config.web !== undefined ? { parentWeb: config.web } : {},
1175
+ ...config.reservedMcpServerNames !== undefined ? { parentReservedMcpServerNames: config.reservedMcpServerNames } : {},
1176
+ ...config.legacyToolNames !== undefined ? { parentLegacyToolNames: config.legacyToolNames } : {},
1162
1177
  ...opts.systemPromptAssembler !== undefined ? { systemPromptAssembler: opts.systemPromptAssembler } : {},
1163
1178
  ...opts.skillRuntime !== undefined ? { skillRuntime: opts.skillRuntime } : {},
1164
1179
  ...opts.structuredOutput !== undefined ? { structuredOutput: opts.structuredOutput } : {},
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  DEFAULT_CLASSIFIER_TIMEOUT_MS2,
56
56
  createModelClassifier2,
57
57
  selectClassifierRoute2
58
- } from "./index-c8j57y8v.js";
58
+ } from "./index-5xe59vkt.js";
59
59
  import"./index-1hef2gff.js";
60
60
  import"./index-mwew595z.js";
61
61
  import"./index-2wgfv0pa.js";
@@ -2,4 +2,5 @@ import type { McpControlSeam } from "./control-seam.js";
2
2
  import type { McpLifecycleInternals } from "./lifecycle.js";
3
3
  export declare function createMcpControlSeam(internals: McpLifecycleInternals, opts?: {
4
4
  reservedServerName?: string;
5
+ hostReservedServerNames?: readonly string[];
5
6
  }): McpControlSeam;
@@ -85,6 +85,7 @@ export declare function resolveMcpServerSources(sources: readonly McpServerSourc
85
85
  strictMcpConfig?: boolean;
86
86
  trustedWorkspace: boolean;
87
87
  reservedServerName?: string;
88
+ hostReservedServerNames?: readonly string[];
88
89
  }): ResolveMcpServerSourcesResult;
89
90
  /** The text budget of an MCP result that carries images (≈ the default `MAX_MCP_OUTPUT_TOKENS` of 25000 at 4 characters a token). */
90
91
  export declare const MCP_IMAGE_RESULT_TEXT_CHARS = 100000;
@@ -111,6 +112,8 @@ export interface McpLifecycleDeps {
111
112
  * `addAndConnect` guard is the second door onto the same rule. Defaults to `WINTER_SERVER_NAME`.
112
113
  */
113
114
  reservedServerName?: string;
115
+ /** `RuntimeConfig.reservedMcpServerNames`, for the control seam's live `mcp_set_servers` guard: a non-sdk server is refused under any of them. */
116
+ hostReservedServerNames?: readonly string[];
114
117
  /** WS-23: the session cwd every stdio server of this lifecycle is spawned in (see `ConnectMcpServerOptions.cwd`). */
115
118
  cwd?: string;
116
119
  /**
@@ -27,7 +27,7 @@ export declare const P5_FIXTURE_SKILL_NAME = "p5probe";
27
27
  * `{"answer":"42","doubled":"4242"}` on every leg.
28
28
  */
29
29
  export declare const P5_WORKFLOW_SCRIPT: string;
30
- export type TestProviderName = "boom" | "tooluse" | "hang" | "reflect" | "modeswitch" | "bgtask" | "lanea" | "laneb" | "lanec" | "laned" | "lanee" | "mcpsdk" | "subagent" | "childmsg" | "subagentperm" | "toolsearch" | "p5compact" | "p5structured" | "p5structuredfail" | "p5skill" | "p5checkpoint" | "p5workflow" | "readimage";
30
+ export type TestProviderName = "boom" | "tooluse" | "hang" | "reflect" | "modeswitch" | "bgtask" | "lanea" | "laneb" | "lanec" | "laned" | "lanee" | "mcpsdk" | "subagent" | "childmsg" | "subagentperm" | "toolsearch" | "p5compact" | "p5structured" | "p5structuredfail" | "p5skill" | "p5checkpoint" | "p5workflow" | "readimage" | "calls";
31
31
  export declare function isTestProviderName(v: string): v is TestProviderName;
32
32
  /**
33
33
  * The name the reserved `winter-test/<name>` namespace uses for the plain echo double.
@@ -478,7 +478,7 @@ export declare class TranscriptWriter implements SessionPersistence {
478
478
  private dialectRecord;
479
479
  static readBack(store: SessionStore, key: SessionKey): Promise<SessionStoreEntry[]>;
480
480
  }
481
- export declare const RUNTIME_ENGINE_VERSION = "0.0.36";
481
+ export declare const RUNTIME_ENGINE_VERSION = "0.0.38";
482
482
  export interface ResolvedEngineSession {
483
483
  config: RuntimeConfig;
484
484
  store: SessionPersistence | undefined;
@@ -130,6 +130,10 @@ export interface ChildEngineFactoryDeps {
130
130
  */
131
131
  parentAutoMemory?: RuntimeConfig["autoMemory"];
132
132
  parentWeb?: RuntimeConfig["web"];
133
+ /** `RuntimeConfig.reservedMcpServerNames`: an agent's own inline server never takes one (it is connected under a free name instead). */
134
+ parentReservedMcpServerNames?: RuntimeConfig["reservedMcpServerNames"];
135
+ /** `RuntimeConfig.legacyToolNames`, so a child resolves the host's old tool names as its parent does. */
136
+ parentLegacyToolNames?: RuntimeConfig["legacyToolNames"];
133
137
  /**
134
138
  * The session's pricing, stated-model and tool-secret seams, so a CHILD's engine has what its
135
139
  * parent's has. `priceUsage` is what makes a child's generations priced at all; the cost then
@@ -189,6 +193,27 @@ export interface ChildEngineFactoryDeps {
189
193
  */
190
194
  compactionControllerFactory?: () => CompactionController;
191
195
  }
196
+ type ChildMcpServers = NonNullable<RuntimeConfig["mcpServers"]>;
197
+ interface ChildServerAllocation {
198
+ /** The servers this generation connects itself, under the names it holds. */
199
+ servers: ChildMcpServers;
200
+ /** Declared name -> the name it is connected under (only for servers this generation connects). */
201
+ actual: Map<string, string>;
202
+ /** One legible line per server not connected under its declared name. */
203
+ notes: string[];
204
+ /**
205
+ * Declared names renamed because the HOST reserves them (`RuntimeConfig.reservedMcpServerNames`). Such a
206
+ * server is NOT "the server the definition named" for permission purposes: its declared spelling is the
207
+ * host's own server's, so it never becomes an extra identity (`mcpServerRenames`) — a saved rule on the
208
+ * host's `mcp__<reserved>__<tool>` must not reach it. Only its connected name governs it.
209
+ */
210
+ reservedRenames: Set<string>;
211
+ /** Releases every name this allocation claimed. Idempotent. */
212
+ release: () => void;
213
+ }
214
+ export declare function allocateChildScopedServers(declared: ChildMcpServers, parentVisible: ReadonlySet<string>, prefer?: ReadonlyMap<string, string>, parentDeclared?: Readonly<Record<string, {
215
+ type?: string;
216
+ }>>, reserved?: readonly string[]): ChildServerAllocation;
192
217
  export declare function createChildEngineFactory(deps: ChildEngineFactoryDeps): ChildEngineFactory;
193
218
  /**
194
219
  * What a child reports when the SPENDING CEILING ended it (whole-branch review MINOR 9). One constant,
@@ -196,3 +221,4 @@ export declare function createChildEngineFactory(deps: ChildEngineFactoryDeps):
196
221
  * not be told a retry is worth trying: the ceiling is the session's, so a retry hits it again.
197
222
  */
198
223
  export declare const CHILD_BUDGET_STOP_TEXT = "The agent stopped because the session reached its spending limit (maxBudgetUsd), so no further model calls could be made. Retrying will not help -- continue with what it produced, or ask the user to raise the session's budget.";
224
+ export {};
package/dist/testing.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  runEngine2,
4
4
  echoProvider2,
5
5
  resolveEngineSession2
6
- } from "./index-c8j57y8v.js";
6
+ } from "./index-5xe59vkt.js";
7
7
  import"./index-1hef2gff.js";
8
8
  import"./index-mwew595z.js";
9
9
  import"./index-2wgfv0pa.js";
@@ -14,7 +14,7 @@ import {
14
14
  buildProductionWiring,
15
15
  withAutoSkillPermissions,
16
16
  restoreChildRoster
17
- } from "./index-83p1g9ap.js";
17
+ } from "./index-z3970wsj.js";
18
18
  import {
19
19
  Queue
20
20
  } from "./index-97t2rmtf.js";
@@ -48,6 +48,7 @@ import "./task-output.ts";
48
48
  import "./task-stop.ts";
49
49
  import "./task-update.ts";
50
50
  import "./todo-write.ts";
51
+ import "./search.ts";
51
52
  import "./tool-search.ts";
52
53
  import "./wait-for-mcp-servers.ts";
53
54
  import "./web-fetch.ts";
@@ -0,0 +1,4 @@
1
+ export declare const SEARCH_CANONICAL_NAME = "Search";
2
+ export declare const SEARCH_ANSWER_CAPABILITY = "winter.search-answer";
3
+ /** The Winter daemon's own description, verbatim (it is the interface the models were shown). */
4
+ export declare const SEARCH_DESCRIPTION = "Search the web and get back a written answer with its sources, in a single call. Ask a real question, not keywords \u2014 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.";
@@ -31,6 +31,8 @@ export interface ExaSearchHit {
31
31
  /** The result's highlight text, capped at `EXA_HIGHLIGHTS_MAX_CHARACTERS`. Empty when the backend sent none. */
32
32
  highlight: string;
33
33
  publishedDate?: string;
34
+ /** The result's `favicon` when the backend sent one (Exa's /search reference: "The URL of the favicon for the search result's domain"), https only. Never model-visible: `WebSearch` hands it to the HOST as `ToolResultPayload.siteIcons`. Absent from the basic text form and, as measured 2026-09-18, from the advanced JSON form too -- read when present. */
35
+ favicon?: string;
34
36
  }
35
37
  export interface ExaSearchParams {
36
38
  query: string;
@@ -0,0 +1,17 @@
1
+ import type { ToolResultSiteIcon } from "../registry.js";
2
+ export declare const SITE_ICONS_MAX = 10;
3
+ export declare const SITE_ICON_URL_MAX_LENGTH = 2048;
4
+ /** A url a host may be handed: https, parseable, no credentials, re-serialised, within the cap. */
5
+ export declare function siteIconHttpsUrl(value: unknown): string | undefined;
6
+ /** A clean list: both urls of each entry checked, one entry per page url (first wins), capped. */
7
+ export declare function collectSiteIcons(entries: Iterable<{
8
+ url: unknown;
9
+ iconUrl: unknown;
10
+ }>): ToolResultSiteIcon[] | undefined;
11
+ /**
12
+ * The icon `html` declares for the page at `pageUrl`, resolved against it: `apple-touch-icon` (large
13
+ * and opaque) over `icon` / `shortcut icon`, never a `mask-icon` (a monochrome stencil), an SVG (most
14
+ * hosts' image decoders cannot draw one) or a `data:` url. With none declared, the page origin's own
15
+ * `/favicon.ico`. Undefined when the page itself is not https (nothing is handed out for it).
16
+ */
17
+ export declare function pageIconUrl(html: string, pageUrl: string): string | undefined;
@@ -9,6 +9,8 @@ export interface WebFetchCacheEntry {
9
9
  contentType: string;
10
10
  /** The URL actually fetched (after the https upgrade and any auto-followed same-host redirect) -- may differ from the cache key. */
11
11
  finalUrl: string;
12
+ /** The page's icon url (`_site-icons.ts`'s `pageIconUrl`), kept so a cache HIT still reports it to the host. */
13
+ iconUrl?: string;
12
14
  }
13
15
  export declare const WEB_FETCH_CACHE_TTL_MS: number;
14
16
  export declare const WEB_FETCH_CACHE_MAX_BYTES: number;
@@ -34,4 +34,5 @@ import "./skill.ts";
34
34
  import "./workflow.ts";
35
35
  import "./web-fetch.ts";
36
36
  import "./web-search.ts";
37
+ import "./search.ts";
37
38
  export declare const EXECUTORS_WIRED = true;
@@ -0,0 +1,25 @@
1
+ import "../descriptors/search.ts";
2
+ import { type ToolExecutor } from "../registry.js";
3
+ /** `/answer` SYNTHESIZES -- it runs a search and then writes a grounded answer over the results -- so it
4
+ * is materially slower than a `/search` round trip; the daemon's 45 s, kept. */
5
+ export declare const SEARCH_REQUEST_TIMEOUT_MS = 45000;
6
+ /** The synthesized answer itself. */
7
+ export declare const SEARCH_ANSWER_CHARS = 24000;
8
+ /** Rendered as a sources list; the backend decides how many it used. */
9
+ export declare const SEARCH_MAX_CITATIONS = 20;
10
+ /** The whole response: the model's ENTIRE view of the search (a chat has no page-reading tool). */
11
+ export declare const SEARCH_TOTAL_OUTPUT_CHARS = 30000;
12
+ export declare const EXA_ANSWER_URL = "https://api.exa.ai/answer";
13
+ /** One sentence per documented failure, each naming what clears it -- never the response body, which can
14
+ * echo request headers (and so the key) and is third-party text besides. */
15
+ export declare function searchStatusMessage(status: number): string;
16
+ export declare const SEARCH_NO_KEY_MESSAGE = "Search needs an Exa API key (from exa.ai), and none is configured for this session";
17
+ export interface SearchExecutorDeps {
18
+ /** Test seam: defaults to the global `fetch`. Tests point it at a loopback fixture. */
19
+ fetchFn?: typeof fetch;
20
+ /** Test seam: defaults to `EXA_ANSWER_URL`. */
21
+ endpoint?: string;
22
+ /** Test seam: defaults to `SEARCH_REQUEST_TIMEOUT_MS`. */
23
+ timeoutMs?: number;
24
+ }
25
+ export declare function createSearchExecutor(deps?: SearchExecutorDeps): ToolExecutor;
@@ -40,7 +40,11 @@ export interface AvailabilityPredicate {
40
40
  requiresToolSearchEnabled?: true;
41
41
  hiddenWhenFamilyTaskNative?: true;
42
42
  insideSubagent?: false;
43
+ optIn?: true;
44
+ requiresAnyCapability?: readonly string[];
43
45
  }
46
+ /** Derived by the engine when the host's `deferTools` names anything: a built-in may then be deferred. */
47
+ export declare const DEFERRED_BUILTINS_CAPABILITY = "winter.deferred-builtins";
44
48
  export type ToolDisposition = "implement-now" | "implement-later" | "correctly-absent" | "winter-backed-equivalent" | "winter-backed-later";
45
49
  export interface ToolDescriptor {
46
50
  canonicalName: string;
@@ -61,6 +65,7 @@ export interface ToolDescriptor {
61
65
  alwaysLoad?: boolean;
62
66
  _meta?: Record<string, unknown>;
63
67
  interaction?: "required";
68
+ mcpName?: string;
64
69
  }
65
70
  export interface ToolResultPayload {
66
71
  output: string;
@@ -75,6 +80,19 @@ export interface ToolResultPayload {
75
80
  */
76
81
  blocks?: ToolResultBlock[];
77
82
  isError?: boolean;
83
+ /**
84
+ * HOST-ONLY metadata, never model-visible: the sites this result names and the icon the tool knows
85
+ * for each (`WebFetch`: the page's own declared icon; `WebSearch`: Exa's `favicon`). The engine
86
+ * writes it ONLY onto the host-facing frame's `tool_result` block, as `winter_site_icons:
87
+ * [{url, icon_url}]` -- never into the model-facing content, the history, the transcript or a
88
+ * provider request. Built through `tools/impl/_site-icons.ts`'s `collectSiteIcons` (bounded, https).
89
+ */
90
+ siteIcons?: ToolResultSiteIcon[];
91
+ }
92
+ /** One `ToolResultPayload.siteIcons` entry: a page url the result names and its icon url. */
93
+ export interface ToolResultSiteIcon {
94
+ url: string;
95
+ iconUrl: string;
78
96
  }
79
97
  /**
80
98
  * One block of a multimodal tool result (see `ToolResultPayload.blocks`). Structurally the engine's own
@@ -315,6 +333,20 @@ export interface McpToolDefinition {
315
333
  _meta?: Record<string, unknown>;
316
334
  }
317
335
  export declare function onRegistryChange(cb: () => void): () => void;
336
+ /**
337
+ * A HOST'S PLAIN NAMES for some of one in-process server's tools (`McpSdkServerConfig.toolNames`):
338
+ * `{ <tool as the server lists it>: <plain name> }`. See that field's doc for the contract.
339
+ */
340
+ export type McpServerToolNames = Readonly<Record<string, string>>;
341
+ /** The search tool's name (descriptors/tool-search.ts): never deferred, and its absence from `tools` switches deferral off. */
342
+ export declare const TOOL_SEARCH_TOOL_NAME = "ToolSearch";
343
+ /** The `mcp__<server>__<tool>` spelling a plain-named MCP tool keeps as its equivalent identity, or `undefined`. */
344
+ export declare function mcpSpellingOf(name: string): string | undefined;
345
+ /**
346
+ * The registry name of `mcp__<server>__<tool>`: its plain name when the host renamed it, else the name itself.
347
+ * The inverse of `mcpSpellingOf`, for a reader handed the MCP spelling (a permission rule, a hook matcher).
348
+ */
349
+ export declare function registeredNameForMcpSpelling(name: string): string;
318
350
  export declare function rebrandStandingServerTools(renames: ReadonlyArray<{
319
351
  from: string;
320
352
  to: string;
@@ -322,6 +354,7 @@ export declare function rebrandStandingServerTools(renames: ReadonlyArray<{
322
354
  export declare function registerMcpServerTools(server: string, incomingTools: readonly McpToolDefinition[], opts: {
323
355
  alwaysLoad?: boolean;
324
356
  deferredDefault: boolean | readonly PermissionMode[];
357
+ toolNames?: McpServerToolNames;
325
358
  }): void;
326
359
  /**
327
360
  * Fix round 20: the live MCP server that registered `canonicalName` through `registerMcpServerTools`,
@@ -354,6 +387,14 @@ export interface DeferralActivation {
354
387
  };
355
388
  providerSupportsToolSearch: boolean;
356
389
  deferrableContextShare: number;
390
+ /**
391
+ * The host's `deferTools` (`RuntimeConfig.deferTools`): names that START DEFERRED while deferral is
392
+ * active -- the one way a BUILT-IN defers (claude's own per-tool `shouldDefer`, chosen by the host here).
393
+ * `ToolSearch` is never deferred whatever this says, and an MCP tool's `alwaysLoad` still wins. Lives
394
+ * on the activation because every reader of `resolveDeferral` (the partition, the load-first boundary,
395
+ * ToolSearch's pool) already takes it, so none of them can disagree about it.
396
+ */
397
+ deferTools?: readonly string[];
357
398
  }
358
399
  export declare function isDeferralActive(activation: DeferralActivation): boolean;
359
400
  export declare function resolveDeferral(descriptor: ToolDescriptor, mode: PermissionMode, activation: DeferralActivation): "eager" | "deferred" | "hidden";
@@ -392,6 +433,21 @@ export declare function deriveRuntimeCapabilities(facts: SessionCapabilityFacts)
392
433
  export declare function resolveSessionCapabilities(hostSupplied: readonly string[] | undefined, facts: SessionCapabilityFacts): string[];
393
434
  export declare function isBareDenied(canonicalName: string, disallowedTools: readonly string[] | undefined): boolean;
394
435
  export declare function buildAdvertisedSet(cfg: AdvertisedSetInputs): ToolDescriptor[];
436
+ /**
437
+ * `cfg.tools` (claude's own `tools` option -- `RuntimeConfig.tools`): it names the BUILT-IN set, so it
438
+ * filters built-ins only. An MCP server's tools (a plain-named in-process one included), the standing
439
+ * server's twins and host-generated tools are never named there and never removed by it -- the twins
440
+ * follow their native spelling through the alias pass instead (`hideAliasExcludedTwins`: a native the
441
+ * list leaves out is not advertised, so its twin is hidden). An OPT-IN built-in (`availability.optIn`)
442
+ * needs the list to name it; absent, the list is claude's default set, which carries none.
443
+ */
444
+ export declare function isRequestedTool(descriptor: ToolDescriptor, tools: readonly string[] | undefined): boolean;
445
+ /**
446
+ * A bare deny on the descriptor's own name OR on the `mcp__<server>__<tool>` spelling a plain-named
447
+ * in-process tool keeps (`ToolDescriptor.mcpName`): either spelling hides it, from advertisement and the
448
+ * ToolSearch pool alike (both read `buildAdvertisedSet`).
449
+ */
450
+ export declare function isDescriptorBareDenied(descriptor: ToolDescriptor, disallowedTools: readonly string[] | undefined): boolean;
395
451
  export interface EngineToolCall {
396
452
  id: string;
397
453
  name: string;
@@ -408,6 +464,8 @@ export interface EngineToolResult {
408
464
  * success.
409
465
  */
410
466
  isError?: boolean;
467
+ /** See `ToolResultPayload.siteIcons`: host-only, written onto the host-facing frame and nowhere else. */
468
+ siteIcons?: ToolResultSiteIcon[];
411
469
  }
412
470
  export interface EngineFacingToolExecutor {
413
471
  /** Phase 6 Task 3 (R6-6): `opts.signal` is the engine's per-turn abort. It reaches a real executor as `ToolExecutionContext.signal`. */
@@ -6,6 +6,8 @@ export interface ExposureQuery {
6
6
  platform?: NodeJS.Platform;
7
7
  capabilities?: readonly string[];
8
8
  disallowedTools?: readonly string[];
9
+ /** The host's `tools` (claude's option, `RuntimeConfig.tools`): the built-in set -- the same filter `init.tools` applies. */
10
+ tools?: readonly string[];
9
11
  insideSubagent?: boolean;
10
12
  familyMetadata?: {
11
13
  taskNative?: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { PermissionMode } from "@yanlinglabs/winter-agent-sdk";
2
- import type { DeferralActivation, ToolDescriptor } from "../tools/registry.js";
2
+ import { type DeferralActivation, type ToolDescriptor } from "../tools/registry.js";
3
3
  import type { McpServerStateSource } from "../mcp/state.js";
4
4
  export interface FailedMcpServerEntry {
5
5
  name: string;
@@ -26,6 +26,10 @@ export interface ToolSearchDeps {
26
26
  platform?: NodeJS.Platform;
27
27
  capabilities?: readonly string[];
28
28
  disallowedTools?: readonly string[];
29
+ /** The host's `tools` (`RuntimeConfig.tools`): the built-in set, filtered exactly as `init.tools` is. */
30
+ tools?: readonly string[];
31
+ /** The engine's `currentToolName`: an old name (a renamed tool's MCP spelling, a host `legacyToolNames` entry) to the tool it means now. Default: the MCP-spelling half alone. */
32
+ resolveName?: (name: string) => string;
29
33
  insideSubagent?: boolean;
30
34
  familyMetadata?: {
31
35
  taskNative?: boolean;
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.0.36";
1
+ export declare const RUNTIME_VERSION = "0.0.38";
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var RUNTIME_VERSION = "0.0.36";
2
+ var RUNTIME_VERSION = "0.0.38";
3
3
  export {
4
4
  RUNTIME_VERSION
5
5
  };
@@ -74,6 +74,14 @@ export declare function inheritedWebSessionFacts(sessionId: string): Pick<WebSes
74
74
  export declare function resetWebSessionRuntimesForTest(): void;
75
75
  /** `winter.search-backend`'s session fact: the host has not switched the backend off. */
76
76
  export declare function searchBackendUsable(runtime: Pick<WebSessionRuntime, "web">): boolean;
77
+ /**
78
+ * `winter.search-answer`'s session fact (the `Search` built-in): the backend is not switched off AND a
79
+ * key is NAMED (`web.search.authRef`) and there is a resolver to read it through. `/answer` has no
80
+ * anonymous tier, so a session without a key must never be offered a tool whose every call fails --
81
+ * whether the named key actually HOLDS material is only knowable asynchronously, and a missing one is a
82
+ * typed, actionable result at the first call.
83
+ */
84
+ export declare function searchAnswerUsable(runtime: Pick<WebSessionRuntime, "web" | "resolveToolSecret">): boolean;
77
85
  /**
78
86
  * `winter.fetch-extractor`'s session fact: a digest model resolves.
79
87
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-runtime",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -73,9 +73,9 @@
73
73
  "@modelcontextprotocol/client": "^2.1.0",
74
74
  "ajv": "^8.20.0",
75
75
  "ignore": "7.0.5",
76
- "@yanlinglabs/winter-provider-catalog": "0.0.36",
77
- "@yanlinglabs/winter-provider-runtime": "0.0.36",
78
- "@yanlinglabs/winter-agent-sdk": "0.0.36"
76
+ "@yanlinglabs/winter-agent-sdk": "0.0.38",
77
+ "@yanlinglabs/winter-provider-runtime": "0.0.38",
78
+ "@yanlinglabs/winter-provider-catalog": "0.0.38"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@modelcontextprotocol/server": "^2.1.0"