zidane 3.3.6 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ A small, hookable core with sensible defaults so most consumers don't write a si
16
16
 
17
17
  - 🧠 **Multi-provider, multi-auth** — Anthropic, OpenAI Codex, OpenRouter, Cerebras, plus a generic `openaiCompat` factory (Baseten, Fireworks, Groq, local servers). OAuth + API key, auto-refreshing tokens. Anthropic accepts opt-in `extraBetas` and `contextManagement` for first-party features.
18
18
  - 🪝 **Streaming, hookable turn loop** — text/thinking deltas, tool calls, MCP, sessions, skills, spawn, OAuth, validation, budgets — all observable (and most mutatable) via typed hook events. Per-request `system:transform` hook for runtime-derived prompt sections.
19
- - 🛠 **Tools first-class** — `shell`, `read_file`, `write_file`, `edit`, `multi_edit`, `glob`, `grep`, `spawn`, human-in-the-loop, plus any [MCP](https://modelcontextprotocol.io) server. Sequential or parallel, per-call gates (`tool:gate` with writable `block` / `result` / `runToolCounts`), validation auto-coerce (`"true"` → `true`), hallucinated-tool fallback (`tool:unknown`), error rewriting (`tool:error` → `result`).
19
+ - 🛠 **Tools first-class** — `shell`, `read_file`, `write_file`, `edit`, `multi_edit`, `glob`, `grep`, `spawn`, human-in-the-loop, plus any [MCP](https://modelcontextprotocol.io) server. Sequential or parallel, per-call gates (`tool:gate` with writable `block` / `result` / `runToolCounts`), validation auto-coerce (`"true"` → `true`), hallucinated-tool fallback (`tool:unknown`), error rewriting (`tool:error` → `result`). Optional **progressive disclosure** (`behavior.toolDisclosure: 'lazy'`) hides MCP schemas behind a `tool_search` native tool when context budget matters more than upfront discovery — gated server-side and in-loop so a model can't bypass.
20
20
  - ✂️ **Token-aware ergonomics** — paginated reads with a "how to page" footer, 8 KB tail-truncated `shell`, idempotent `write_file`; `outputBytes` surfaced on every tool/MCP hook. `behavior.toolOutputBudget` injects a "summarize" nudge when a turn's outputs exceed the cap; `behavior.toolBudgets` caps per-tool call counts (`'steer'` or `'block'`); `behavior.thinkingDecay` tapers reasoning budget per turn.
21
21
  - 🗜 **Context discipline** — auto-injected `cache_control` breakpoints (Anthropic + OpenRouter); server-side compaction via `context-management-2025-06-27` on Anthropic, `behavior.compactStrategy: 'tail'` on everyone else. Per-session `read_file` dedup + opt-in `requireReadBeforeEdit` guard kill stale-content edits; `behavior.dedupTools` generalizes the same pattern to arbitrary tools (`todowrite`, `execute_sql`, …).
22
22
  - 🎯 **Reasoning + structured output** — thinking levels (`off` / `minimal` / `low` / `medium` / `high` / `adaptive`) with optional exact budgets; force the final answer to a JSON Schema (Zod v4 interop), no brittle parsing.
@@ -24,8 +24,6 @@ A small, hookable core with sensible defaults so most consumers don't write a si
24
24
  - 🧵 **Sub-agents + execution contexts** — delegate to child agents with inherited or overridden preset (child events bubble to the parent); run tools in-process, Docker, or any `SandboxProvider` (E2B / Rivet / custom). Parallel MCP bootstrap with `agent.warmup()` + `eager: true` to hide cold starts.
25
25
  - 🧭 **Typed errors + 1000+ tests** — `AgentContextExceededError` / `AgentProviderError` / `AgentAbortedError` instead of sniffing strings. Suite runs in under 2s with mock providers + mock execution contexts, zero API keys.
26
26
 
27
- > Upgrading from 2.x? See [`docs/migrate-from-v2.md`](./docs/migrate-from-v2.md) for the full list of behavior changes.
28
-
29
27
  ## Quickstart
30
28
 
31
29
  ```bash
@@ -77,6 +75,8 @@ createAgent({
77
75
  compactStrategy: 'off', // client-side tail compaction for non-Anthropic providers — 'off' | 'tail' (default: 'off')
78
76
  compactThreshold: 131_072, // bytes threshold that triggers tail compaction (default: 128 KiB)
79
77
  compactKeepTurns: 4, // trailing turns left intact during compaction (default: 4)
78
+ toolDisclosure: 'eager', // 'eager' | 'lazy' — hide MCP schemas behind tool_search (default: 'eager')
79
+ toolSearch: { tool: true, limit: 20 }, // tune the auto-injected tool_search (no-op in eager mode)
80
80
  },
81
81
  execution: createProcessContext(), // where tools run
82
82
  mcpServers: [], // MCP tool servers
@@ -744,13 +744,13 @@ const agent = createAgent({
744
744
  provider,
745
745
  mcpServers: [
746
746
  { name: 'fs', transport: 'stdio', command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '.'], bootstrapTimeout: 10_000 },
747
- { name: 'api', transport: 'streamable-http', url: 'http://localhost:3002/mcp' },
747
+ { name: 'api', transport: 'streamable-http', url: 'http://localhost:3002/mcp', disclosure: 'lazy' },
748
748
  ],
749
749
  })
750
750
  ```
751
751
 
752
752
  MCP servers can live on a preset too (they're just `AgentOptions` fields). Connections are lazy (first `run()`) and reused.
753
- Set `bootstrapTimeout` to cap how long a slow `connect + listTools` phase can delay the first model request.
753
+ Set `bootstrapTimeout` to cap how long a slow `connect + listTools` phase can delay the first model request. Per-server `disclosure: 'lazy' | 'eager'` overrides the agent-wide `behavior.toolDisclosure` (see [Progressive tool disclosure](#progressive-tool-disclosure)).
754
754
 
755
755
  ### Hiding bootstrap latency
756
756
 
@@ -785,6 +785,48 @@ agent.hooks.hook('mcp:bootstrap:end', (ctx) => {
785
785
 
786
786
  Use these to attribute cold-start latency per server — the only way to know if a specific MCP (e.g. a remote GitHub MCP) is the one stretching your first `run()`.
787
787
 
788
+ ## Progressive tool disclosure
789
+
790
+ When MCP brings hundreds of tools, every turn ships every schema in the tool list. `behavior.toolDisclosure: 'lazy'` flips MCP tools to a name-only catalog in the system prompt and auto-injects a `tool_search` native tool the model uses to load schemas on demand. Native (non-MCP) tools and skill tools are always eager — only MCP tools are eligible for lazy disclosure.
791
+
792
+ ```ts
793
+ const agent = createAgent({
794
+ ...basic,
795
+ provider,
796
+ mcpServers: [
797
+ { name: 'github', transport: 'stdio', command: 'gh-mcp' }, // 200+ tools
798
+ { name: 'fs', transport: 'stdio', command: 'fs-mcp', disclosure: 'eager' }, // override per-server
799
+ ],
800
+ behavior: {
801
+ toolDisclosure: 'lazy',
802
+ toolSearch: { limit: 20 }, // default cap on results per call
803
+ },
804
+ })
805
+ ```
806
+
807
+ The catalog appended to the system prompt looks like:
808
+
809
+ ```
810
+ <searchable_tools>
811
+ <server name="github">
812
+ <tool name="mcp_github_search_issues">Search GitHub issues by query.</tool>
813
+ <tool name="mcp_github_create_pr">Open a pull request.</tool>
814
+
815
+ </server>
816
+ </searchable_tools>
817
+ ```
818
+
819
+ `tool_search` accepts `query` (substring), `names` (explicit), `server` (bulk-unlock one server), and `limit`. Surfaced tools persist for the rest of the run; the loop rebuilds the wire-level tool list each turn so the next provider call advertises them.
820
+
821
+ Two hard guarantees:
822
+
823
+ - **Hard gate.** A `tool:gate` middleware refuses dispatch on lazy tools the model hasn't surfaced yet — production providers already enforce this server-side, but the in-loop gate covers custom / mock / lenient providers and any path where a model quotes a name straight from the catalog. Refusal text points the model at `tool_search` so it self-corrects.
824
+ - **Aliasing-safe.** Catalog and `tool_search` results show the **wire** (`toolAliases`-rewritten) name — the only one the provider accepts. The unlock set is keyed by canonical name so dispatch / `session.turns` / hook contexts stay alias-stable.
825
+
826
+ Cost model: each `tool_search` call appends to the wire-level tool list, advancing the provider's tool-list cache breakpoint. That costs one cache miss per discovery wave; subsequent turns with the same unlocked set hit cache normally. With many lazy tools and few discovery waves, this still beats eager (which always sends every schema) — but it's not a free optimisation.
827
+
828
+ Opt out via `behavior.toolSearch.tool: false` (the catalog still emits, the call-to-action prose drops). Pre-existing host tool named `tool_search` shadows the auto-injection — see the JSDoc on `behavior.toolSearch` for the host-defined-tool semantics.
829
+
788
830
  ## Skills
789
831
 
790
832
  Reusable instruction packages following the [Agent Skills](https://agentskills.io/specification) open standard.
@@ -208,6 +208,15 @@ interface McpServerConfig {
208
208
  description?: string | null;
209
209
  inputSchema?: unknown;
210
210
  }) => boolean;
211
+ /**
212
+ * Per-server override for {@link AgentBehavior.toolDisclosure}. When set,
213
+ * this server's tools follow this disclosure mode regardless of the
214
+ * agent-wide default. Useful when one big MCP server (200+ tools) should
215
+ * stay lazy while smaller servers stay eager.
216
+ *
217
+ * Default: inherits from `behavior.toolDisclosure`.
218
+ */
219
+ disclosure?: 'eager' | 'lazy';
211
220
  }
212
221
  type ToolExecutionMode = 'sequential' | 'parallel';
213
222
  interface AgentBehavior {
@@ -464,6 +473,61 @@ interface AgentBehavior {
464
473
  * Default: `false`.
465
474
  */
466
475
  elideStaleReads?: boolean;
476
+ /**
477
+ * Tool disclosure strategy. Controls whether the model sees every tool's
478
+ * full `inputSchema` in its tool list every turn ("eager") or whether MCP
479
+ * tools are advertised as a name+description catalog in the system prompt
480
+ * and only get full schemas after being surfaced via the `tool_search`
481
+ * native tool ("lazy" / progressive disclosure).
482
+ *
483
+ * Native tools (those passed to `createAgent({ tools })`) and skill tools
484
+ * are always eager — they are core to the agent and cheap. Only MCP tools
485
+ * are eligible for lazy disclosure.
486
+ *
487
+ * When `'lazy'`, the agent:
488
+ * - Appends a `<searchable_tools>` section to the system prompt listing
489
+ * every MCP tool by `name` + `description` only (no `inputSchema`).
490
+ * - Auto-injects a `tool_search` native tool (opt out via
491
+ * {@link AgentBehavior.toolSearch}) the model uses to load schemas on
492
+ * demand. Surfaced tools persist for the rest of the run.
493
+ * - Rebuilds the wire-level tool list each turn, appending newly-unlocked
494
+ * tools at the end so the prefix-cache breakpoint advances cleanly.
495
+ *
496
+ * Trade-off: every `tool_search` invocation expands the tool list and
497
+ * invalidates the tool-list cache breakpoint for one turn. With many
498
+ * MCP servers, the savings on cold turns (fewer schemas in context) are
499
+ * substantial; with one tiny MCP server, the overhead may not pay back.
500
+ *
501
+ * Default: `'eager'`.
502
+ */
503
+ toolDisclosure?: 'eager' | 'lazy';
504
+ /**
505
+ * Fine-grained config for the `tool_search` tool auto-injected when
506
+ * {@link AgentBehavior.toolDisclosure} is `'lazy'`. No-op in eager mode.
507
+ *
508
+ * - `tool: false` — opt out of the auto-injection entirely. Use when the
509
+ * host wants to ship a custom discovery tool. Note that the catalog
510
+ * text drops the call-to-action prose in this case so the model isn't
511
+ * pointed at a non-existent tool.
512
+ * - `limit` — default cap on results returned per `tool_search` call when
513
+ * the model omits the parameter. Default: `20`.
514
+ *
515
+ * Note on host-defined `tool_search`: a tool the host registers under the
516
+ * name `tool_search` (or under any alias whose canonical is `tool_search`)
517
+ * will shadow the auto-injected one — the catalog text will point at the
518
+ * host's wire name, but driving the unlock flow requires either using
519
+ * `createToolSearchTool({ catalog, unlocked })` from `tools/tool-search`
520
+ * (which internally mutates the unlock set) or fully opting out via
521
+ * `toolSearch.tool: false` and treating discovery as a host-side concern.
522
+ * A bare host tool that doesn't touch the unlock set will not advance the
523
+ * lazy disclosure state and the hard gate will keep refusing lazy calls.
524
+ *
525
+ * Default: `undefined` (auto-inject with the default limit).
526
+ */
527
+ toolSearch?: {
528
+ tool?: false;
529
+ limit?: number;
530
+ };
467
531
  }
468
532
  /**
469
533
  * One block of a multimodal user prompt.
@@ -251,6 +251,15 @@ function installAllowedToolsGate(hooks, state) {
251
251
  };
252
252
  }
253
253
 
254
+ // src/xml.ts
255
+ var RE_AMP = /&/g;
256
+ var RE_LT = /</g;
257
+ var RE_GT = />/g;
258
+ var RE_QUOT = /"/g;
259
+ function escapeXml(str) {
260
+ return str.replace(RE_AMP, "&amp;").replace(RE_LT, "&lt;").replace(RE_GT, "&gt;").replace(RE_QUOT, "&quot;");
261
+ }
262
+
254
263
  // src/skills/catalog.ts
255
264
  function buildCatalog(skills, options = {}) {
256
265
  if (skills.length === 0)
@@ -315,13 +324,6 @@ function buildCatalog(skills, options = {}) {
315
324
  }
316
325
  return parts.join("\n");
317
326
  }
318
- var RE_AMP = /&/g;
319
- var RE_LT = /</g;
320
- var RE_GT = />/g;
321
- var RE_QUOT = /"/g;
322
- function escapeXml(str) {
323
- return str.replace(RE_AMP, "&amp;").replace(RE_LT, "&lt;").replace(RE_GT, "&gt;").replace(RE_QUOT, "&quot;");
324
- }
325
327
 
326
328
  // src/skills/discovery.ts
327
329
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
@@ -814,6 +816,7 @@ export {
814
816
  isToolAllowedByUnion,
815
817
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
816
818
  installAllowedToolsGate,
819
+ escapeXml,
817
820
  buildCatalog,
818
821
  parseFrontmatter,
819
822
  parseSkillFile,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  validateSkillForWrite
3
- } from "./chunk-X3VOTPVM.js";
3
+ } from "./chunk-6STZTA4N.js";
4
4
 
5
5
  // src/skills/index.ts
6
6
  function defineSkill(config) {
@@ -1,11 +1,12 @@
1
1
  import {
2
2
  buildCatalog,
3
3
  createSkillActivationState,
4
+ escapeXml,
4
5
  installAllowedToolsGate,
5
6
  interpolateShellCommands,
6
7
  resolveSkills,
7
8
  validateResourcePath
8
- } from "./chunk-X3VOTPVM.js";
9
+ } from "./chunk-6STZTA4N.js";
9
10
  import {
10
11
  createProcessContext
11
12
  } from "./chunk-UD25QF3H.js";
@@ -1202,13 +1203,6 @@ function createSkillsRunScriptTool(options) {
1202
1203
 
1203
1204
  // src/tools/skills-use.ts
1204
1205
  var MAX_RESOURCE_LIST = 50;
1205
- var AMP_RE = /&/g;
1206
- var LT_RE = /</g;
1207
- var GT_RE = />/g;
1208
- var QUOT_RE = /"/g;
1209
- function escapeXml(str) {
1210
- return str.replace(AMP_RE, "&amp;").replace(LT_RE, "&lt;").replace(GT_RE, "&gt;").replace(QUOT_RE, "&quot;");
1211
- }
1212
1206
  function buildSkillContentWrapper(skill, body) {
1213
1207
  const parts = [];
1214
1208
  parts.push(`<skill_content name="${escapeXml(skill.name)}" spec_version="0.1">`);
@@ -1812,10 +1806,11 @@ async function executeTurn(ctx, turn) {
1812
1806
  sanitizedMessages = applyTailCompaction(sanitizedMessages, threshold, keep);
1813
1807
  }
1814
1808
  const effectiveThinkingBudget = applyThinkingDecay(ctx.thinkingBudget, ctx.thinkingDecay, turn);
1809
+ const formattedTools = ctx.rebuildFormattedTools ? ctx.rebuildFormattedTools() : ctx.formattedTools;
1815
1810
  const streamOptions = {
1816
1811
  model: ctx.model,
1817
1812
  system: ctx.system,
1818
- tools: ctx.formattedTools,
1813
+ tools: formattedTools,
1819
1814
  messages: sanitizedMessages,
1820
1815
  maxTokens: ctx.maxTokens ?? 16384,
1821
1816
  thinking: ctx.thinking,
@@ -2350,6 +2345,154 @@ function defaultBlockMessage(tool, max) {
2350
2345
  return `Tool '${tool}' has reached its per-run budget of ${max} calls; further invocations are refused.`;
2351
2346
  }
2352
2347
 
2348
+ // src/tools/tool-search.ts
2349
+ var DEFAULT_LIMIT2 = 20;
2350
+ function rankByQuery(catalog, query) {
2351
+ const q = query.trim().toLowerCase();
2352
+ if (!q)
2353
+ return [...catalog];
2354
+ const nameHits = [];
2355
+ const descHits = [];
2356
+ for (const entry of catalog) {
2357
+ if (entry.name.toLowerCase().includes(q))
2358
+ nameHits.push(entry);
2359
+ else if (entry.description.toLowerCase().includes(q))
2360
+ descHits.push(entry);
2361
+ }
2362
+ return [...nameHits, ...descHits];
2363
+ }
2364
+ function sanitiseSchemaForXml(schemaJson) {
2365
+ return schemaJson.replace(/</g, "\\u003c");
2366
+ }
2367
+ function formatMatch(entry) {
2368
+ const schema = sanitiseSchemaForXml(JSON.stringify(entry.inputSchema));
2369
+ const serverAttr = entry.server ? ` server="${escapeXml(entry.server)}"` : "";
2370
+ return [
2371
+ ` <tool name="${escapeXml(entry.name)}"${serverAttr}>`,
2372
+ ` <description>${escapeXml(entry.description)}</description>`,
2373
+ ` <input_schema>${schema}</input_schema>`,
2374
+ ` </tool>`
2375
+ ].join("\n");
2376
+ }
2377
+ function createToolSearchTool(options) {
2378
+ const defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT2;
2379
+ const byName = new Map(options.catalog.map((e) => [e.name, e]));
2380
+ const byServer = /* @__PURE__ */ new Map();
2381
+ for (const entry of options.catalog) {
2382
+ if (!entry.server)
2383
+ continue;
2384
+ const list = byServer.get(entry.server) ?? [];
2385
+ list.push(entry);
2386
+ byServer.set(entry.server, list);
2387
+ }
2388
+ const maxLimit = Math.max(options.catalog.length, 1);
2389
+ return {
2390
+ spec: {
2391
+ name: "tool_search",
2392
+ description: "Discover and load schemas for additional tools listed in <searchable_tools>. Tools listed there are advertised by name + description only \u2014 their input schemas are not loaded into context until you surface them through this tool. Pass `query` for a substring search, `names` to load specific tools, or `server` to load every tool from one MCP server. Returned tools become callable for the rest of this run.",
2393
+ inputSchema: {
2394
+ type: "object",
2395
+ properties: {
2396
+ query: {
2397
+ type: "string",
2398
+ description: "Substring to match against tool name + description (case-insensitive)."
2399
+ },
2400
+ names: {
2401
+ type: "array",
2402
+ items: { type: "string" },
2403
+ description: "Explicit tool names to load (bypasses ranking). Use the names shown in <searchable_tools>."
2404
+ },
2405
+ server: {
2406
+ type: "string",
2407
+ description: "MCP server name \u2014 load every tool from this server."
2408
+ },
2409
+ limit: {
2410
+ type: "integer",
2411
+ minimum: 1,
2412
+ description: `Cap on returned matches. Default: ${defaultLimit}.`
2413
+ }
2414
+ },
2415
+ additionalProperties: false
2416
+ }
2417
+ },
2418
+ async execute(input, ctx) {
2419
+ if (ctx.signal?.aborted)
2420
+ return '<tool_search_results matches="0" aborted="true">Run aborted.</tool_search_results>';
2421
+ const rawQuery = typeof input.query === "string" ? input.query.trim() : void 0;
2422
+ const query = rawQuery || void 0;
2423
+ const namesIn = Array.isArray(input.names) ? input.names.filter((n) => typeof n === "string" && n.length > 0) : void 0;
2424
+ const server = typeof input.server === "string" && input.server.length > 0 ? input.server : void 0;
2425
+ const limitIn = typeof input.limit === "number" && Number.isFinite(input.limit) && input.limit > 0 ? Math.floor(input.limit) : defaultLimit;
2426
+ const limit = Math.min(limitIn, maxLimit);
2427
+ if (options.catalog.length === 0)
2428
+ return '<tool_search_results matches="0">No lazy tools registered for this run.</tool_search_results>';
2429
+ const matches = [];
2430
+ const seen = /* @__PURE__ */ new Set();
2431
+ const misses = [];
2432
+ if (namesIn && namesIn.length > 0) {
2433
+ for (const n of namesIn) {
2434
+ if (seen.has(n))
2435
+ continue;
2436
+ const entry = byName.get(n);
2437
+ if (entry) {
2438
+ matches.push(entry);
2439
+ seen.add(n);
2440
+ } else {
2441
+ misses.push(n);
2442
+ }
2443
+ }
2444
+ }
2445
+ if (server) {
2446
+ const list = byServer.get(server) ?? [];
2447
+ for (const entry of list) {
2448
+ if (seen.has(entry.name))
2449
+ continue;
2450
+ matches.push(entry);
2451
+ seen.add(entry.name);
2452
+ }
2453
+ }
2454
+ if (query !== void 0) {
2455
+ for (const entry of rankByQuery(options.catalog, query)) {
2456
+ if (seen.has(entry.name))
2457
+ continue;
2458
+ matches.push(entry);
2459
+ seen.add(entry.name);
2460
+ }
2461
+ }
2462
+ if (!namesIn?.length && !server && query === void 0) {
2463
+ for (const entry of options.catalog) {
2464
+ matches.push(entry);
2465
+ seen.add(entry.name);
2466
+ }
2467
+ }
2468
+ const truncated = matches.length > limit;
2469
+ const shown = truncated ? matches.slice(0, limit) : matches;
2470
+ for (const entry of shown)
2471
+ options.unlocked.add(entry.canonicalName);
2472
+ const parts = [];
2473
+ const queryAttr = query ? ` query="${escapeXml(query)}"` : "";
2474
+ const serverAttr = server ? ` server="${escapeXml(server)}"` : "";
2475
+ parts.push(`<tool_search_results matches="${shown.length}" total="${matches.length}"${queryAttr}${serverAttr}>`);
2476
+ if (shown.length === 0) {
2477
+ parts.push(" No matches. Try a broader query, or omit all parameters to list everything.");
2478
+ } else {
2479
+ for (const entry of shown)
2480
+ parts.push(formatMatch(entry));
2481
+ parts.push("");
2482
+ parts.push(" These tools are now callable. Invoke them by name in subsequent turns.");
2483
+ if (truncated) {
2484
+ parts.push(` ${matches.length - shown.length} additional matches were truncated \u2014 refine the query or raise \`limit\`.`);
2485
+ }
2486
+ }
2487
+ if (misses.length > 0) {
2488
+ parts.push(` <misses>${misses.map(escapeXml).join(", ")}</misses>`);
2489
+ }
2490
+ parts.push("</tool_search_results>");
2491
+ return parts.join("\n");
2492
+ }
2493
+ };
2494
+ }
2495
+
2353
2496
  // src/agent.ts
2354
2497
  var HOOK_EVENT_NAMES = [
2355
2498
  "system:before",
@@ -2429,9 +2572,103 @@ function resolveBehavior(agentBehavior, runBehavior) {
2429
2572
  requireReadBeforeEdit: runBehavior?.requireReadBeforeEdit ?? agentBehavior?.requireReadBeforeEdit,
2430
2573
  toolBudgets: runBehavior?.toolBudgets ?? agentBehavior?.toolBudgets,
2431
2574
  readLineNumbers: runBehavior?.readLineNumbers ?? agentBehavior?.readLineNumbers,
2432
- elideStaleReads: runBehavior?.elideStaleReads ?? agentBehavior?.elideStaleReads
2575
+ elideStaleReads: runBehavior?.elideStaleReads ?? agentBehavior?.elideStaleReads,
2576
+ toolDisclosure: runBehavior?.toolDisclosure ?? agentBehavior?.toolDisclosure ?? "eager",
2577
+ toolSearch: runBehavior?.toolSearch ?? agentBehavior?.toolSearch
2433
2578
  };
2434
2579
  }
2580
+ function resolveServerForTool(toolName, servers) {
2581
+ if (!servers || servers.length === 0)
2582
+ return void 0;
2583
+ let best;
2584
+ let bestLen = -1;
2585
+ for (const server of servers) {
2586
+ const prefix = `mcp_${server.name}_`;
2587
+ if (toolName.startsWith(prefix) && server.name.length > bestLen) {
2588
+ best = server;
2589
+ bestLen = server.name.length;
2590
+ }
2591
+ }
2592
+ return best;
2593
+ }
2594
+ function partitionToolDisclosure(toolsBySpecName, mcpToolNames, servers, globalMode, toolAliases) {
2595
+ const eagerCanonicalNames = /* @__PURE__ */ new Set();
2596
+ const lazyCanonicalNames = /* @__PURE__ */ new Set();
2597
+ const lazyEntries = [];
2598
+ function wireFor(canonical) {
2599
+ const aliased = toolAliases?.[canonical];
2600
+ return typeof aliased === "string" && aliased.length > 0 ? aliased : canonical;
2601
+ }
2602
+ for (const [canonicalName, def] of Object.entries(toolsBySpecName)) {
2603
+ if (!mcpToolNames.has(canonicalName)) {
2604
+ eagerCanonicalNames.add(canonicalName);
2605
+ continue;
2606
+ }
2607
+ const server = resolveServerForTool(canonicalName, servers);
2608
+ const mode = server?.disclosure ?? globalMode;
2609
+ if (mode === "lazy") {
2610
+ lazyCanonicalNames.add(canonicalName);
2611
+ lazyEntries.push({
2612
+ name: wireFor(canonicalName),
2613
+ canonicalName,
2614
+ description: def.spec.description || "",
2615
+ inputSchema: def.spec.inputSchema ?? { type: "object", properties: {} },
2616
+ ...server ? { server: server.name } : {}
2617
+ });
2618
+ } else {
2619
+ eagerCanonicalNames.add(canonicalName);
2620
+ }
2621
+ }
2622
+ return { eagerCanonicalNames, lazyCanonicalNames, lazyEntries };
2623
+ }
2624
+ function buildSearchableCatalog(entries, options) {
2625
+ const byServer = /* @__PURE__ */ new Map();
2626
+ const ungrouped = [];
2627
+ for (const entry of entries) {
2628
+ if (!entry.server) {
2629
+ ungrouped.push(entry);
2630
+ continue;
2631
+ }
2632
+ const list = byServer.get(entry.server) ?? [];
2633
+ list.push(entry);
2634
+ byServer.set(entry.server, list);
2635
+ }
2636
+ const serverNames = [...byServer.keys()].sort();
2637
+ const parts = [];
2638
+ if (options.discoveryToolName) {
2639
+ parts.push(
2640
+ "The following tools are available but their input schemas are NOT loaded in your context.",
2641
+ `Call the \`${options.discoveryToolName}\` tool to load schemas before invoking them. Surfaced tools persist for the rest of the run.`,
2642
+ ""
2643
+ );
2644
+ }
2645
+ parts.push("<searchable_tools>");
2646
+ for (const server of serverNames) {
2647
+ parts.push(` <server name="${escapeXml(server)}">`);
2648
+ for (const entry of byServer.get(server))
2649
+ parts.push(` <tool name="${escapeXml(entry.name)}">${escapeXml(entry.description)}</tool>`);
2650
+ parts.push(" </server>");
2651
+ }
2652
+ for (const entry of ungrouped)
2653
+ parts.push(` <tool name="${escapeXml(entry.name)}">${escapeXml(entry.description)}</tool>`);
2654
+ parts.push("</searchable_tools>");
2655
+ return parts.join("\n");
2656
+ }
2657
+ function installLazyDisclosureGate(hooks, lazyCanonicalNames, unlocked, discoveryToolName) {
2658
+ if (lazyCanonicalNames.size === 0)
2659
+ return () => {
2660
+ };
2661
+ return hooks.hook("tool:gate", (ctx) => {
2662
+ if (ctx.block)
2663
+ return;
2664
+ if (!lazyCanonicalNames.has(ctx.name))
2665
+ return;
2666
+ if (unlocked.has(ctx.name))
2667
+ return;
2668
+ ctx.block = true;
2669
+ ctx.reason = discoveryToolName ? `Tool "${ctx.name}" is listed in <searchable_tools> but its schema has not been loaded. Call the \`${discoveryToolName}\` tool with names: ["${ctx.name}"] first, then re-issue the call.` : `Tool "${ctx.name}" is listed in <searchable_tools> but its schema has not been loaded.`;
2670
+ });
2671
+ }
2435
2672
  function createAgent({ provider, name: agentName, system: agentSystem, tools: agentTools, toolAliases, behavior: agentBehavior, execution, mcpServers, session, skills: agentSkills, mcpConnector, eager }) {
2436
2673
  const hooks = createHooks();
2437
2674
  const executionContext = execution ?? createProcessContext();
@@ -2557,7 +2794,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2557
2794
  const thinking = options.thinking ?? "off";
2558
2795
  const model = options.model ?? provider.meta.defaultModel;
2559
2796
  const resolvedBehavior = resolveBehavior(agentBehavior, options.behavior);
2560
- const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads } = resolvedBehavior;
2797
+ const { toolExecution, maxTurns, maxTokens, thinkingBudget, schema, cache, toolOutputBudget, compactStrategy, compactThreshold, compactKeepTurns, thinkingDecay, dedupTools, toolBudgets, elideStaleReads, toolDisclosure, toolSearch } = resolvedBehavior;
2561
2798
  let system = options.system || agentSystem || "You are a helpful assistant.";
2562
2799
  if (skillsCatalog) {
2563
2800
  system = `${system}
@@ -2565,6 +2802,7 @@ function createAgent({ provider, name: agentName, system: agentSystem, tools: ag
2565
2802
  ${skillsCatalog}`;
2566
2803
  }
2567
2804
  const runBaseTools = options.tools !== void 0 ? options.tools : mcpConnection ? { ...sourceTools, ...mcpConnection.tools } : sourceTools;
2805
+ const mcpToolNames = options.tools === void 0 && mcpConnection ? new Set(Object.keys(mcpConnection.tools)) : /* @__PURE__ */ new Set();
2568
2806
  const shouldInjectSkillTools = options.tools === void 0 && !!resolvedSkills && resolvedSkills.length > 0 && skillsConfig?.tool !== false;
2569
2807
  const mergedWithSkills = shouldInjectSkillTools ? {
2570
2808
  // Auto-injected first so agent + MCP tools can override by name.
@@ -2584,19 +2822,51 @@ ${skillsCatalog}`;
2584
2822
  }),
2585
2823
  ...runBaseTools
2586
2824
  } : runBaseTools;
2587
- const tools = {};
2825
+ const toolsPreSearch = {};
2588
2826
  for (const tool of Object.values(mergedWithSkills)) {
2589
- tools[tool.spec.name] = tool;
2827
+ toolsPreSearch[tool.spec.name] = tool;
2828
+ }
2829
+ const disclosure = partitionToolDisclosure(toolsPreSearch, mcpToolNames, mcpServers, toolDisclosure, toolAliases);
2830
+ const unlocked = new Set(disclosure.eagerCanonicalNames);
2831
+ const hostDefinedToolSearch = !!toolsPreSearch.tool_search;
2832
+ const shouldInjectToolSearch = disclosure.lazyEntries.length > 0 && toolSearch?.tool !== false && !hostDefinedToolSearch;
2833
+ let tools = toolsPreSearch;
2834
+ if (shouldInjectToolSearch) {
2835
+ const toolSearchTool = createToolSearchTool({
2836
+ catalog: disclosure.lazyEntries,
2837
+ unlocked,
2838
+ ...toolSearch?.limit !== void 0 ? { defaultLimit: toolSearch.limit } : {}
2839
+ });
2840
+ tools = { ...toolsPreSearch, [toolSearchTool.spec.name]: toolSearchTool };
2841
+ unlocked.add(toolSearchTool.spec.name);
2842
+ }
2843
+ const discoveryToolName = shouldInjectToolSearch ? "tool_search" : hostDefinedToolSearch ? toolAliases?.tool_search ?? "tool_search" : null;
2844
+ if (disclosure.lazyEntries.length > 0) {
2845
+ system = `${system}
2846
+
2847
+ ${buildSearchableCatalog(disclosure.lazyEntries, { discoveryToolName })}`;
2590
2848
  }
2591
2849
  const aliasMaps = buildAliasMaps(toolAliases, Object.keys(tools));
2592
- const toolSpecs = Object.values(tools).map(
2593
- (t) => ({
2594
- name: aliasMaps.aliasByCanonical.get(t.spec.name) ?? t.spec.name,
2595
- description: t.spec.description || "",
2596
- inputSchema: t.spec.inputSchema
2597
- })
2850
+ const uninstallLazyDisclosureGate = installLazyDisclosureGate(
2851
+ hooks,
2852
+ disclosure.lazyCanonicalNames,
2853
+ unlocked,
2854
+ discoveryToolName
2598
2855
  );
2599
- const formattedTools = toolSpecs.length > 0 ? provider.formatTools(toolSpecs) : [];
2856
+ function buildFormattedTools() {
2857
+ const specs = [];
2858
+ for (const t of Object.values(tools)) {
2859
+ if (!unlocked.has(t.spec.name))
2860
+ continue;
2861
+ specs.push({
2862
+ name: aliasMaps.aliasByCanonical.get(t.spec.name) ?? t.spec.name,
2863
+ description: t.spec.description || "",
2864
+ inputSchema: t.spec.inputSchema
2865
+ });
2866
+ }
2867
+ return specs.length > 0 ? provider.formatTools(specs) : [];
2868
+ }
2869
+ const formattedTools = buildFormattedTools();
2600
2870
  const turns = [];
2601
2871
  const isResume = session && session.turns.length > 0 && (session.runs.length > 0 || !options.prompt);
2602
2872
  if (isResume) {
@@ -2685,6 +2955,7 @@ ${skillsCatalog}`;
2685
2955
  agentBehavior: resolvedBehavior,
2686
2956
  tools,
2687
2957
  formattedTools,
2958
+ rebuildFormattedTools: disclosure.lazyEntries.length > 0 ? buildFormattedTools : void 0,
2688
2959
  aliasMaps,
2689
2960
  model,
2690
2961
  system,
@@ -2756,6 +3027,7 @@ ${skillsCatalog}`;
2756
3027
  uninstallAllowedToolsGate();
2757
3028
  uninstallDedupTools();
2758
3029
  uninstallToolBudgets();
3030
+ uninstallLazyDisclosureGate();
2759
3031
  unregisterSpawnHook();
2760
3032
  unregisterSessionSync?.();
2761
3033
  for (const unregister of perRunUnregisters)
@@ -3200,6 +3472,7 @@ export {
3200
3472
  createSkillsReadTool,
3201
3473
  createSkillsRunScriptTool,
3202
3474
  createSkillsUseTool,
3475
+ createToolSearchTool,
3203
3476
  createAgent,
3204
3477
  edit,
3205
3478
  glob,
@@ -6,7 +6,7 @@ import {
6
6
  readFile,
7
7
  shell,
8
8
  writeFile
9
- } from "./chunk-OAIKGCZX.js";
9
+ } from "./chunk-VTLEPYND.js";
10
10
 
11
11
  // src/presets/basic.ts
12
12
  var basicTools = { shell, readFile, writeFile, listFiles, edit, multiEdit };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { d as AgentHooks } from './agent-LEf7zjw6.js';
2
- export { ab as ActivationVia, ac as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ad as DeactivationReason, ae as FileMapAdapter, af as FileMapStoreOptions, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ag as OpenAICompatAuthHeader, ah as OpenAICompatHttpError, ai as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, aj as SkillActivationState, ak as SkillActivationStateOptions, J as SkillConfig, al as SkillDiagnostic, K as SkillResource, am as SkillSource, L as SkillsConfig, N as SpawnHookContext, Q as StreamCallbacks, T as StreamHookContext, U as StreamOptions, V as ThinkingLevel, W as ToolCall, X as ToolContext, Y as ToolDef, Z as ToolExecutionMode, _ as ToolHookContext, $ as ToolMap, a0 as ToolResult, a1 as ToolResultContent, a2 as ToolResultImageContent, a3 as ToolResultTextContent, a4 as ToolSpec, a5 as TurnFinishReason, a6 as TurnResult, a7 as TurnUsage, an as anthropic, ao as autoDetectAndConvert, ap as cerebras, aq as classifyOpenAICompatError, ar as connectMcpServers, as as createAgent, at as createFileMapStore, au as createMemoryStore, av as createRemoteStore, aw as createSession, ax as createSkillActivationState, ay as fromAnthropic, az as fromOpenAI, aA as loadSession, aB as mapOAIFinishReason, a8 as matchesContextExceeded, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aE as openai, aF as openaiCompat, aG as openrouter, aH as resultToString, aI as toAnthropic, aJ as toOpenAI, aK as toTypedError, a9 as toolOutputByteLength, aa as toolResultToText } from './agent-LEf7zjw6.js';
1
+ import { d as AgentHooks } from './agent-CMjWCUPr.js';
2
+ export { ab as ActivationVia, ac as ActiveSkill, A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, ad as DeactivationReason, ae as FileMapAdapter, af as FileMapStoreOptions, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, ag as OpenAICompatAuthHeader, ah as OpenAICompatHttpError, ai as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, aj as SkillActivationState, ak as SkillActivationStateOptions, J as SkillConfig, al as SkillDiagnostic, K as SkillResource, am as SkillSource, L as SkillsConfig, N as SpawnHookContext, Q as StreamCallbacks, T as StreamHookContext, U as StreamOptions, V as ThinkingLevel, W as ToolCall, X as ToolContext, Y as ToolDef, Z as ToolExecutionMode, _ as ToolHookContext, $ as ToolMap, a0 as ToolResult, a1 as ToolResultContent, a2 as ToolResultImageContent, a3 as ToolResultTextContent, a4 as ToolSpec, a5 as TurnFinishReason, a6 as TurnResult, a7 as TurnUsage, an as anthropic, ao as autoDetectAndConvert, ap as cerebras, aq as classifyOpenAICompatError, ar as connectMcpServers, as as createAgent, at as createFileMapStore, au as createMemoryStore, av as createRemoteStore, aw as createSession, ax as createSkillActivationState, ay as fromAnthropic, az as fromOpenAI, aA as loadSession, aB as mapOAIFinishReason, a8 as matchesContextExceeded, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aE as openai, aF as openaiCompat, aG as openrouter, aH as resultToString, aI as toAnthropic, aJ as toOpenAI, aK as toTypedError, a9 as toolOutputByteLength, aa as toolResultToText } from './agent-CMjWCUPr.js';
3
3
  export { createDockerContext, createProcessContext } from './contexts.js';
4
4
  export { S as SandboxProvider, c as createSandboxContext } from './sandbox-CLghrTLi.js';
5
5
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SpawnConfig } from './types-vA1a_ZX7.js';
6
6
  export { Preset, basic, basicTools, definePreset } from './presets.js';
7
7
  export { IMPLICITLY_ALLOWED_SKILL_TOOLS, SkillValidationIssue, SkillValidationResult, SourcedScanPath, buildCatalog, defineSkill, discoverSkills, installAllowedToolsGate, interpolateShellCommands, isToolAllowedByUnion, matchesAllowedTool, parseAllowedToolPattern, parseSkillFile, resolveSkills, validateResourcePath, validateSkillForWrite, validateSkillName, writeSkillToDisk, writeSkillsToDisk } from './skills.js';
8
- export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-CVxdV6Tq.js';
9
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, v as validateToolArgs } from './validation-C4Ucr2wT.js';
8
+ export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-6oJKUjk_.js';
9
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, v as validateToolArgs } from './validation-BWV_pCdO.js';
10
10
  import { Hookable } from 'hookable';
11
11
  import '@modelcontextprotocol/sdk/client/index.js';
12
12
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineSkill
3
- } from "./chunk-LIVB5W4B.js";
3
+ } from "./chunk-JCOB6IYO.js";
4
4
  import {
5
5
  anthropic,
6
6
  cerebras,
@@ -11,7 +11,7 @@ import {
11
11
  basicTools,
12
12
  basic_default,
13
13
  definePreset
14
- } from "./chunk-CZQGCVMH.js";
14
+ } from "./chunk-WE4C6AQE.js";
15
15
  import {
16
16
  createAgent,
17
17
  createInteractionTool,
@@ -24,7 +24,7 @@ import {
24
24
  grep,
25
25
  multiEdit,
26
26
  validateToolArgs
27
- } from "./chunk-OAIKGCZX.js";
27
+ } from "./chunk-VTLEPYND.js";
28
28
  import {
29
29
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
30
30
  buildCatalog,
@@ -42,7 +42,7 @@ import {
42
42
  validateSkillName,
43
43
  writeSkillToDisk,
44
44
  writeSkillsToDisk
45
- } from "./chunk-X3VOTPVM.js";
45
+ } from "./chunk-6STZTA4N.js";
46
46
  import {
47
47
  createDockerContext,
48
48
  createProcessContext,
package/dist/mcp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import '@modelcontextprotocol/sdk/client/index.js';
2
2
  import 'hookable';
3
- export { M as McpConnection, p as McpServerConfig, ar as connectMcpServers, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aH as resultToString } from './agent-LEf7zjw6.js';
3
+ export { M as McpConnection, p as McpServerConfig, ar as connectMcpServers, aC as normalizeMcpBlocks, aD as normalizeMcpServers, aH as resultToString } from './agent-CMjWCUPr.js';
4
4
  import './types-vA1a_ZX7.js';
package/dist/presets.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Y as ToolDef, e as AgentOptions } from './agent-LEf7zjw6.js';
1
+ import { Y as ToolDef, e as AgentOptions } from './agent-CMjWCUPr.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/presets.js CHANGED
@@ -2,9 +2,9 @@ import {
2
2
  basicTools,
3
3
  basic_default,
4
4
  definePreset
5
- } from "./chunk-CZQGCVMH.js";
6
- import "./chunk-OAIKGCZX.js";
7
- import "./chunk-X3VOTPVM.js";
5
+ } from "./chunk-WE4C6AQE.js";
6
+ import "./chunk-VTLEPYND.js";
7
+ import "./chunk-6STZTA4N.js";
8
8
  import "./chunk-UD25QF3H.js";
9
9
  import "./chunk-7GQ7P6DM.js";
10
10
  import "./chunk-JH6IAAFA.js";
@@ -1,4 +1,4 @@
1
- export { j as AnthropicParams, k as CerebrasParams, ag as OpenAICompatAuthHeader, ah as OpenAICompatHttpError, ai as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, w as Provider, x as ProviderCapabilities, Q as StreamCallbacks, U as StreamOptions, W as ToolCall, a0 as ToolResult, a4 as ToolSpec, a6 as TurnResult, an as anthropic, ap as cerebras, aq as classifyOpenAICompatError, aB as mapOAIFinishReason, aE as openai, aF as openaiCompat, aG as openrouter } from './agent-LEf7zjw6.js';
1
+ export { j as AnthropicParams, k as CerebrasParams, ag as OpenAICompatAuthHeader, ah as OpenAICompatHttpError, ai as OpenAICompatParams, r as OpenAIParams, s as OpenRouterParams, w as Provider, x as ProviderCapabilities, Q as StreamCallbacks, U as StreamOptions, W as ToolCall, a0 as ToolResult, a4 as ToolSpec, a6 as TurnResult, an as anthropic, ap as cerebras, aq as classifyOpenAICompatError, aB as mapOAIFinishReason, aE as openai, aF as openaiCompat, aG as openrouter } from './agent-CMjWCUPr.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
@@ -1,4 +1,4 @@
1
- import { H as SessionStore } from '../agent-LEf7zjw6.js';
1
+ import { H as SessionStore } from '../agent-CMjWCUPr.js';
2
2
  import 'hookable';
3
3
  import '../types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/session.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { o as CreateSessionOptions, ae as FileMapAdapter, af as FileMapStoreOptions, R as RemoteStoreOptions, S as Session, z as SessionContentBlock, B as SessionData, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, ao as autoDetectAndConvert, at as createFileMapStore, au as createMemoryStore, av as createRemoteStore, aw as createSession, ay as fromAnthropic, az as fromOpenAI, aA as loadSession, aI as toAnthropic, aJ as toOpenAI } from './agent-LEf7zjw6.js';
1
+ export { o as CreateSessionOptions, ae as FileMapAdapter, af as FileMapStoreOptions, R as RemoteStoreOptions, S as Session, z as SessionContentBlock, B as SessionData, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, ao as autoDetectAndConvert, at as createFileMapStore, au as createMemoryStore, av as createRemoteStore, aw as createSession, ay as fromAnthropic, az as fromOpenAI, aA as loadSession, aI as toAnthropic, aJ as toOpenAI } from './agent-CMjWCUPr.js';
2
2
  import 'hookable';
3
3
  import './types-vA1a_ZX7.js';
4
4
  import '@modelcontextprotocol/sdk/client/index.js';
@@ -1,4 +1,4 @@
1
- import { Y as ToolDef, J as SkillConfig, aj as SkillActivationState, d as AgentHooks } from './agent-LEf7zjw6.js';
1
+ import { Y as ToolDef, J as SkillConfig, aj as SkillActivationState, d as AgentHooks } from './agent-CMjWCUPr.js';
2
2
  import { Hookable } from 'hookable';
3
3
 
4
4
  /**
package/dist/skills.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as AgentHooks, aj as SkillActivationState, J as SkillConfig, am as SkillSource, al as SkillDiagnostic, L as SkillsConfig } from './agent-LEf7zjw6.js';
2
- export { ab as ActivationVia, ac as ActiveSkill, ad as DeactivationReason, ak as SkillActivationStateOptions, K as SkillResource, ax as createSkillActivationState } from './agent-LEf7zjw6.js';
1
+ import { d as AgentHooks, aj as SkillActivationState, J as SkillConfig, am as SkillSource, al as SkillDiagnostic, L as SkillsConfig } from './agent-CMjWCUPr.js';
2
+ export { ab as ActivationVia, ac as ActiveSkill, ad as DeactivationReason, ak as SkillActivationStateOptions, K as SkillResource, ax as createSkillActivationState } from './agent-CMjWCUPr.js';
3
3
  import { Hookable } from 'hookable';
4
4
  import { b as ExecutionContext, c as ExecutionHandle } from './types-vA1a_ZX7.js';
5
5
  import '@modelcontextprotocol/sdk/client/index.js';
package/dist/skills.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineSkill
3
- } from "./chunk-LIVB5W4B.js";
3
+ } from "./chunk-JCOB6IYO.js";
4
4
  import {
5
5
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
6
6
  buildCatalog,
@@ -21,7 +21,7 @@ import {
21
21
  validateSkillName,
22
22
  writeSkillToDisk,
23
23
  writeSkillsToDisk
24
- } from "./chunk-X3VOTPVM.js";
24
+ } from "./chunk-6STZTA4N.js";
25
25
  import "./chunk-LNN5UTS2.js";
26
26
  export {
27
27
  IMPLICITLY_ALLOWED_SKILL_TOOLS,
package/dist/tools.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-CVxdV6Tq.js';
2
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, v as validateToolArgs } from './validation-C4Ucr2wT.js';
3
- import { Y as ToolDef } from './agent-LEf7zjw6.js';
4
- export { X as ToolContext, $ as ToolMap } from './agent-LEf7zjw6.js';
1
+ export { S as SkillsReadToolOptions, a as SkillsRunScriptToolOptions, b as SkillsUseToolOptions, c as createSkillsReadTool, d as createSkillsRunScriptTool, e as createSkillsUseTool, f as edit, g as glob, h as grep, m as multiEdit } from './skills-use-6oJKUjk_.js';
2
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult, c as createInteractionTool, b as createSpawnTool, v as validateToolArgs } from './validation-BWV_pCdO.js';
3
+ import { Y as ToolDef } from './agent-CMjWCUPr.js';
4
+ export { X as ToolContext, $ as ToolMap } from './agent-CMjWCUPr.js';
5
5
  import 'hookable';
6
6
  import './presets.js';
7
7
  import './types-vA1a_ZX7.js';
@@ -13,6 +13,68 @@ declare const readFile: ToolDef;
13
13
 
14
14
  declare const shell: ToolDef;
15
15
 
16
+ /**
17
+ * `tool_search` — progressive tool disclosure.
18
+ *
19
+ * Counterpart to `skills_use` for the MCP tool surface: the catalog (name +
20
+ * description) lives in the system prompt; full `inputSchema` payloads load
21
+ * lazily through this tool. After a successful match, the matched tools'
22
+ * canonical names are added to the per-run "unlocked" set and become callable
23
+ * immediately (the loop rebuilds `formattedTools` before the next provider
24
+ * request, advertising the unlocked tools to the provider).
25
+ *
26
+ * Aliasing: the tool advertises and matches on the **wire** (alias-rewritten)
27
+ * name — the only name the model ever sees. Internally each lazy entry also
28
+ * carries its `canonicalName`, which is what gets added to the `unlocked` set
29
+ * (the loop's tool registry is keyed by canonical name).
30
+ *
31
+ * Result shape: structured pseudo-XML wrapping a JSON `inputSchema` payload.
32
+ * The schema is inlined verbatim (NOT XML-escaped) so the model can reuse it
33
+ * directly when constructing arguments — escaping the JSON would force the
34
+ * model to mentally un-escape `&quot;` etc. when reasoning about field types.
35
+ */
36
+
37
+ interface LazyToolEntry {
38
+ /**
39
+ * Wire name (after `toolAliases` rewrite). What the model sees in the
40
+ * catalog, what `tool_search` matches against, and what the provider's
41
+ * tool list will carry once the entry is unlocked.
42
+ */
43
+ name: string;
44
+ /**
45
+ * Canonical (registry-key) name used for unlock-set membership and for the
46
+ * loop's `ctx.tools[name]` dispatch lookup. Equal to `name` when no alias
47
+ * is configured for this tool.
48
+ */
49
+ canonicalName: string;
50
+ description: string;
51
+ inputSchema: Record<string, unknown>;
52
+ /** Source MCP server, when applicable. Used for `server`-bulk unlock. */
53
+ server?: string;
54
+ }
55
+ interface ToolSearchToolOptions {
56
+ /**
57
+ * Snapshot of every lazy tool the model can discover. Built once per run by
58
+ * the agent — the tool closes over this array and never mutates it.
59
+ */
60
+ catalog: readonly LazyToolEntry[];
61
+ /**
62
+ * Mutable per-run set of unlocked **canonical** tool names. The tool adds
63
+ * matches in place; the loop reads the set when rebuilding the wire-level
64
+ * tool list. Keyed by canonical (not wire) so dispatch lookups stay
65
+ * alias-stable.
66
+ */
67
+ unlocked: Set<string>;
68
+ /** Default cap on returned matches when the model omits `limit`. */
69
+ defaultLimit?: number;
70
+ }
71
+ /**
72
+ * Factory for `tool_search`. Auto-injected by the agent when
73
+ * `behavior.toolDisclosure === 'lazy'` and at least one MCP tool is in the
74
+ * registry. Opt out via `behavior.toolSearch.tool === false`.
75
+ */
76
+ declare function createToolSearchTool(options: ToolSearchToolOptions): ToolDef;
77
+
16
78
  /**
17
79
  * Write a file, with an idempotency signal when the content is unchanged.
18
80
  *
@@ -30,4 +92,4 @@ declare const shell: ToolDef;
30
92
  */
31
93
  declare const writeFile: ToolDef;
32
94
 
33
- export { ToolDef, listFiles, readFile, shell, writeFile };
95
+ export { type LazyToolEntry, ToolDef, type ToolSearchToolOptions, createToolSearchTool, listFiles, readFile, shell, writeFile };
package/dist/tools.js CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  createSkillsRunScriptTool,
5
5
  createSkillsUseTool,
6
6
  createSpawnTool,
7
+ createToolSearchTool,
7
8
  edit,
8
9
  glob,
9
10
  grep,
@@ -13,8 +14,8 @@ import {
13
14
  shell,
14
15
  validateToolArgs,
15
16
  writeFile
16
- } from "./chunk-OAIKGCZX.js";
17
- import "./chunk-X3VOTPVM.js";
17
+ } from "./chunk-VTLEPYND.js";
18
+ import "./chunk-6STZTA4N.js";
18
19
  import "./chunk-UD25QF3H.js";
19
20
  import "./chunk-7GQ7P6DM.js";
20
21
  import "./chunk-JH6IAAFA.js";
@@ -25,6 +26,7 @@ export {
25
26
  createSkillsRunScriptTool,
26
27
  createSkillsUseTool,
27
28
  createSpawnTool,
29
+ createToolSearchTool,
28
30
  edit,
29
31
  glob,
30
32
  grep,
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, d as AgentHooks, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, l as ChildRunStats, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, J as SkillConfig, K as SkillResource, L as SkillsConfig, N as SpawnHookContext, Q as StreamCallbacks, T as StreamHookContext, U as StreamOptions, V as ThinkingLevel, W as ToolCall, X as ToolContext, Y as ToolDef, Z as ToolExecutionMode, _ as ToolHookContext, $ as ToolMap, a0 as ToolResult, a1 as ToolResultContent, a2 as ToolResultImageContent, a3 as ToolResultTextContent, a4 as ToolSpec, a5 as TurnFinishReason, a6 as TurnResult, a7 as TurnUsage, a8 as matchesContextExceeded, a9 as toolOutputByteLength, aa as toolResultToText } from './agent-LEf7zjw6.js';
1
+ export { A as Agent, a as AgentAbortedError, b as AgentBehavior, c as AgentContextExceededError, d as AgentHooks, e as AgentOptions, f as AgentProviderError, g as AgentRunOptions, h as AgentStats, i as AgentToolNotAllowedError, j as AnthropicParams, C as CONTEXT_EXCEEDED_MESSAGE_PATTERNS, k as CerebrasParams, l as ChildRunStats, m as ClassifiedError, n as ClassifiedErrorKind, o as CreateSessionOptions, M as McpConnection, p as McpServerConfig, q as McpToolHookContext, O as OAuthRefreshHookContext, r as OpenAIParams, s as OpenRouterParams, P as PromptDocumentPart, t as PromptImagePart, u as PromptPart, v as PromptTextPart, w as Provider, x as ProviderCapabilities, R as RemoteStoreOptions, y as RunHookMap, S as Session, z as SessionContentBlock, B as SessionData, D as SessionEndStatus, E as SessionHookContext, F as SessionMessage, G as SessionRun, H as SessionStore, I as SessionTurn, J as SkillConfig, K as SkillResource, L as SkillsConfig, N as SpawnHookContext, Q as StreamCallbacks, T as StreamHookContext, U as StreamOptions, V as ThinkingLevel, W as ToolCall, X as ToolContext, Y as ToolDef, Z as ToolExecutionMode, _ as ToolHookContext, $ as ToolMap, a0 as ToolResult, a1 as ToolResultContent, a2 as ToolResultImageContent, a3 as ToolResultTextContent, a4 as ToolSpec, a5 as TurnFinishReason, a6 as TurnResult, a7 as TurnUsage, a8 as matchesContextExceeded, a9 as toolOutputByteLength, aa as toolResultToText } from './agent-CMjWCUPr.js';
2
2
  export { C as ContextCapabilities, a as ContextType, E as ExecResult, b as ExecutionContext, c as ExecutionHandle, S as SpawnConfig } from './types-vA1a_ZX7.js';
3
3
  export { S as SandboxProvider } from './sandbox-CLghrTLi.js';
4
4
  export { Preset } from './presets.js';
5
- export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult } from './validation-C4Ucr2wT.js';
5
+ export { C as ChildAgent, I as InteractionToolOptions, S as SpawnToolOptions, a as SpawnToolState, V as ValidationResult } from './validation-BWV_pCdO.js';
6
6
  import 'hookable';
7
7
  import '@modelcontextprotocol/sdk/client/index.js';
@@ -1,4 +1,4 @@
1
- import { X as ToolContext, Y as ToolDef, h as AgentStats, l as ChildRunStats } from './agent-LEf7zjw6.js';
1
+ import { X as ToolContext, Y as ToolDef, h as AgentStats, l as ChildRunStats } from './agent-CMjWCUPr.js';
2
2
  import { Preset } from './presets.js';
3
3
 
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zidane",
3
- "version": "3.3.6",
3
+ "version": "3.4.0",
4
4
  "description": "an agent that goes straight to the goal",
5
5
  "type": "module",
6
6
  "private": false,