/**
 * buildToolRegistry — pure function that composes the agent's
 * augmented tool registry from three sources:
 *
 *   1. **Static registry** — tools registered via `.tool()`. Always
 *      visible to the LLM; always executable.
 *   2. **`read_skill`** — auto-attached when ≥1 Skill is registered.
 *      Activation tool for LLM-guided Skills.
 *   3. **Skill-supplied tools** (`Skill.inject.tools[]`) — visible
 *      only when the Skill is active (filtered by tools-slot subflow);
 *      MUST always be in the executor registry so when the LLM calls
 *      one, the tool-calls handler can dispatch.
 *
 * Tool-name uniqueness is enforced across all three sources at build
 * time. The LLM only sees `tool.schema.name` (no ids), so names ARE
 * the runtime dispatch key — collisions break the LLM's ability to
 * call the right tool. Throw early instead of subtly shadowing.
 *
 * **Block C runtime — `autoActivate: 'currentSkill'` semantics:**
 * When a skill's `defineSkill({ autoActivate: 'currentSkill' })` is
 * set, its tools are EXCLUDED from the static registry. They flow
 * into the LLM's tool list ONLY through `dynamicSchemas` (the
 * buildToolsSlot path that reads activeInjections), which means
 * they're visible ONLY on iterations after the skill is activated by
 * `read_skill('id')`. Without this, the LLM sees every skill's tools
 * on every iteration and the per-skill-narrowing autoActivate
 * promised in `defineSkill` doesn't actually narrow anything. Skills
 * WITHOUT autoActivate keep the v2.4 behavior (tools always visible)
 * for back-compat.
 *
 * **autoActivate dispatch invariant:** autoActivate skill tools live
 * OUTSIDE the LLM-visible registry (so they don't pollute the
 * per-iteration tool list before the skill activates), but they MUST
 * still be findable by the dispatch handler — the LLM calls them by
 * name once the skill is active, and dispatch looks up by name. We
 * add them to the dispatch map (`registryByName`) so `lookupTool`
 * resolves correctly.
 *
 * ── THE CAPABILITY LAW, EPOCH-SCOPED ──────────────────────────────────
 *
 * An EPOCH is one composed request plus the dispatch of the tool calls
 * that request comes back with. For one epoch E:
 *
 *   every offered capability resolves to a dispatchable implementation
 *   with stable identity. Attention may alter the offer. Omission from
 *   the offer must NOT be presented as proof of permanent capability loss.
 *
 * Clause one is what the two maps returned from here are FOR, and it is
 * scoped to THE TOOLS THIS FILE ROUTES. `augmentedRegistry` (plus the
 * per-iteration narrowing the tools slot applies downstream) is this file's
 * CONTRIBUTION to the offer; `registryByName` is DISPATCH. It is not the
 * whole offer: the wire list is assembled one layer out, as
 * `[static, provider, skill, step]` merged first-occurrence-wins
 * (`buildToolsSlot`), and PROVIDER schemas are a source these maps never
 * contain. Within the scoped set, a name in the offer that is missing from
 * dispatch is an `Unknown tool` the model was invited to call — which is why
 * the autoActivate invariant above exists, and why the name-uniqueness throws
 * below are throws: an identity that is not stable is one the model cannot
 * address. Where a provider schema reaches the wire ahead of a skill tool of
 * the same name, the identity half WAS the SHADOW SEAM illustrated below —
 * closed in 9.92.0 by making dispatch follow the offer.
 *
 * `with stable identity` is kept rather than dropped, and the reason is the
 * event. A law reading only "every offered name resolves to something" would
 * be true everywhere and would leave `agentfootprint.tools.shadowed` reporting
 * a deviation from nothing — the framework spends an event on that seam
 * precisely because reading one contract and calling another implementation is
 * a violation of an expectation worth naming. Since 9.92.0 the clause HOLDS
 * for every name on the wire (dispatch follows the offer); the event now names
 * the collision that would have broken it, and its companion
 * `agentfootprint.tools.claim_swallowed` names the party the collision silenced.
 *
 * Clause two is the whole point of the narrowing dials — `autoActivate`,
 * `skillGraph({ scopeTools })`, `.toolsFromActiveSkill()`, a parked map,
 * an open step tenure. Every one of them subtracts from the OFFER.
 *
 * Clause three is a rule about SENTENCES, and nothing in this file can
 * enforce it; the surfaces that write them are inventoried by
 * `test/modelFacingSurfaces.test.ts` and judged by
 * `test/helpers/modelFacingClaims.ts`. It is stated HERE because this is
 * where a reader learns what an offer is, and therefore where the wrong
 * inference is cheapest to draw: a tool absent from this iteration's list
 * is a tool the attention policy did not put there, never a capability
 * that has been taken away.
 *
 * ── WHAT THE LAW DELIBERATELY DOES NOT SAY ────────────────────────────
 *
 * Three drafts of it read "position governs the offer, never dispatch".
 * That generalisation is FALSE across the framework, and it is false in a
 * direction that matters: it would license telling a model that anything
 * it once saw is still callable. It holds for the tools THIS file routes —
 * a scoped skill tool leaves `augmentedRegistry` and stays in
 * `registryByName`, which is exactly the invariant above. It does not hold
 * where the implementation never lived in these maps, or where the SCHEMA
 * the model read never did.
 *
 * ── WHERE IT DOES NOT HOLD IS NOT WRITTEN DOWN HERE ───────────────────
 *
 * It used to be: a list, in this comment, that called itself complete. It was
 * wrong three times in three rounds — round 1 missed provider tools vanishing
 * cross-epoch, round 2 the same-epoch provider/skill shadow, round 3 an
 * INACTIVE skill shadowing silently and a provider able to claim `skip_step`.
 * Each round stated the list more precisely and each round an independent
 * check found one more. A hand-maintained enumeration that claims completeness
 * is the exact defect this library exists to fix, one level up, so this one is
 * no longer maintained by hand:
 *
 *   THE ENUMERATION IS `test/core/agent/toolDivergenceWalk.test.ts`.
 *
 * It walks the configuration space — every source that can put a name on the
 * wire or answer to one, crossed with the narrowings that change resolution —
 * drives a REAL run per configuration, and records per epoch what name was
 * offered, whose contract was on the wire, whose implementation answered, and
 * whether `agentfootprint.tools.shadowed` fired and what it claimed. Its
 * committed baseline is the list this comment used to be, kept by observation
 * instead of by memory: a divergence missing from it fails, and one that stops
 * appearing fails too. Read it for the SET, and for the reason each member is
 * tolerated.
 *
 * What follows is ILLUSTRATION — the seams a reader of THIS file meets first,
 * so the two maps below make sense. It is not a boundary, and nothing here
 * should be read as "and no others":
 *
 *   • PROVIDER-DELIVERED tools (`ToolProvider`). Dispatch resolves those
 *     from `providerToolCache.current`, which the Tools slot OVERWRITES
 *     with this iteration's `provider.list(ctx)` (buildToolsSlot.ts). So
 *     `skillScopedTools(id, …)`, which answers `[]` whenever
 *     `ctx.activeSkillId` is not its skill — and that field reports only a
 *     `read_skill` activation — drops the tool out of the offer and out of
 *     dispatch on the SAME epoch.
 *     Same-epoch offer implies same-epoch dispatch; the cross-epoch half is
 *     simply not true there.
 *   • `read_skill` RECOVERY is cursor-gated, so it is not the escape hatch
 *     that would make the cross-epoch claim harmless anyway.
 *     `makeReachableSkills` filters the cursor out of its own successor set,
 *     and under `.tree()` the reachable set is `() => []` outright
 *     (skillGraph.ts) — a tree routes by predicate and has no cursor to move.
 *   • The SHADOW SEAM — the SAME-EPOCH divergence this file's two maps used
 *     to produce, CLOSED in 9.92.0 and kept here because a reader meeting
 *     the maps has to know why dispatch no longer reads them first. A
 *     `ToolProvider` and a skill can declare the same tool name. The wire
 *     merge puts provider schemas ahead of skill injections
 *     first-occurrence-wins, so the model reads the PROVIDER's contract; until
 *     9.92.0 `lookupTool` checked `registryByName` first, where every skill
 *     tool lives and no provider tool does, so the SKILL's `execute` ran —
 *     the contract offered and the implementation that answered were
 *     different tools, inside ONE epoch, and an INACTIVE skill could answer
 *     a call it was never offered. Now DISPATCH FOLLOWS THE OFFER: the tools
 *     slot records which party put each name on the wire (`ServedToolParties`,
 *     buildToolsSlot.ts) and `lookupTool` resolves a name on the wire to THAT
 *     party's implementation; `registryByName` is the fallback for a name
 *     that is not on this epoch's wire at all (the held-out, parked and
 *     restored-transcript cases the capability law keeps dispatchable) —
 *     reached only for the party the model LAST read the name under, or the
 *     name's only holder when it was never served, and put on the record by
 *     `agentfootprint.tools.answered_off_wire`; a name whose last-served
 *     party can no longer answer is refused (`toolCalls.ts` ·
 *     `notServedResult`), never handed to a party the model was not shown
 *     under that name. The
 *     collision itself is still REPORTED rather than refused, because nothing
 *     here can see it coming: the provider's list is resolved per iteration
 *     (`list(ctx)`), so there is no build-time moment at which the pair is
 *     knowable. The pair this file CAN see — a static `.tool()` against a
 *     skill tool — is refused upstream (`validators.ts` ·
 *     `validateToolNameUniqueness`), which is the better answer whenever the
 *     answer is available that early. `agentfootprint.tools.shadowed` names
 *     the wire's party (now the same party in both of its halves) every
 *     iteration two contracts compete, and `agentfootprint.tools.claim_swallowed`
 *     names every party whose claim is dead that iteration — the loser of a
 *     competition, and the claimant that never even reached the merge (a
 *     provider tool whose name a registry holder owns, a skill tool named
 *     after a framework auto-attach). `toolClaimants` below is what makes the
 *     second event possible: the list of who claimed a name, kept instead of
 *     collapsed to a winner. Pinned by `test/toolShadowing.test.ts`,
 *     `test/core/tools/offer-and-answer.test.ts` and epoch-laws 1(g).
 *
 * MCP does not add a seam of its OWN, and it is worth saying so because it
 * looks like it does: `mcpServe` builds `listing` and `byName` ONCE at
 * construction from the same array, and every server instance installs
 * those same two captures, so the offer and the dispatch map there cannot
 * diverge at all. What it does add is a mount: an MCP source reaches an agent
 * as `staticTools(await client.tools())` — a `ToolProvider` — so it inherits
 * every provider seam above unchanged. The walk drives both and gets the same
 * rows for each, which is the only way that claim is worth making.
 *
 * Pinned by `test/core/agent/epoch-laws.test.ts` (the law) and
 * `test/core/agent/toolDivergenceWalk.test.ts` (the set).
 */
import type { Injection } from '../../injection-engine/types.js';
import type { LLMToolSchema } from '../../provider-contract/types.js';
import type { Tool, ToolRegistryEntry } from '../../tools/tools.js';
import type { ToolNameChannel } from '../../events/payloads.js';
/**
 * One party's claim to a tool name, as declared at build (9.92.0).
 *
 * `channel` is the vocabulary `tools.shadowed` / `tools.claim_swallowed` name
 * parties in; `id` is the skill id for a skill's claim (a static `.tool()` and
 * a framework auto-attach carry none). `tool` is the implementation that
 * answers when THIS claim is the one on the wire.
 */
export interface ToolClaim {
    readonly channel: Exclude<ToolNameChannel, 'provider'>;
    readonly id?: string;
    readonly tool: Tool;
}
export interface ToolRegistryArtifacts {
    /** All tools the LLM sees in the static portion of its tool list
     *  (registry + read_skill + non-autoActivate skill tools). */
    readonly augmentedRegistry: readonly ToolRegistryEntry[];
    /** Dispatch map by name — used by the tool-calls handler at run
     *  time to resolve a tool the LLM called. INCLUDES autoActivate
     *  skill tools (which aren't in `augmentedRegistry`) so dispatch
     *  works once the skill is active. */
    readonly registryByName: ReadonlyMap<string, Tool>;
    /** Static tool schemas for the LLM call. Mirrors `augmentedRegistry`
     *  shape; passed to `buildToolsSlot` + the seed stage as the
     *  per-iteration default before the dynamic tools slot has run. */
    readonly toolSchemas: readonly LLMToolSchema[];
    /**
     * WHICH SKILLS DECLARE EACH TOOL NAME (9.86.0) — tool name → the ids of the
     * skills whose `inject.tools` carry it, in declaration order.
     *
     * This walk already happened here; it was thrown away, and the one consumer
     * that needed it — the unknown-tool roster, which must not name a tool
     * belonging to a skill the caller's policy hides — had no way to ask. A
     * consumer re-deriving it from `Agent.injections` would be a second walk of
     * the same arrays, which is the shape this release exists to remove.
     *
     * Shared references are kept whole: a Tool the same reference of which two
     * skills carry is listed under BOTH ids, so a reader can apply the
     * sole-owner rule (hide the name only when every declaring skill is hidden)
     * rather than guessing at ownership.
     *
     * EMPTY when no registered skill declares a tool. Static `.tool()`
     * registrations, `read_skill`, `present` and `skip_step` are never in it:
     * they are the framework's or the app's, not a skill's.
     */
    readonly toolDeclaringSkills: ReadonlyMap<string, readonly string[]>;
    /**
     * EVERY BUILD-TIME CLAIMANT PER TOOL NAME (9.92.0) — a list, not a winner.
     *
     * `registryByName` holds one implementation per name and still does: it is
     * the map dispatch falls back to for a name that is NOT on this epoch's
     * wire (a held-out step tool, a parked map's tool, a scoped tool named from
     * a restored transcript — the capability law's "a narrowing takes a name
     * off the wire, never out of dispatch", qualified since the 9.92.0 review
     * by "to the party the model last read it under"). What it cannot say is who ELSE
     * claimed the name, and that is what the tools slot needs to report a
     * dead claim (`tools.claim_swallowed`) and to name the wire's party
     * truthfully (`tools.shadowed`). Provider claims are not here — a
     * `ToolProvider` resolves per iteration and is a runtime fact.
     *
     * Order per name: the `augmentedRegistry` holder first (a static `.tool()`,
     * the framework's `read_skill`/`present`, an always-visible skill's tool),
     * then every other skill that declares the name (a shared reference is
     * listed under each declaring skill), then the framework's `skip_step` when
     * any skill declares steps. Build time decides nothing here: the wire
     * decides, per epoch, in `buildToolsSlot`.
     */
    readonly toolClaimants: ReadonlyMap<string, readonly ToolClaim[]>;
}
/** The build facts that gate auto-attached tools beyond skills (9.22.0). */
export interface BuildToolRegistryOptions {
    /**
     * Is an artifact store attached to this agent? Gates the `present`
     * auto-attach (the `read_skill` seam): true reserves the name and adds the
     * tool; false — the default — changes not one byte, so a storeless agent
     * may keep its own `present`.
     */
    readonly hasArtifactStore?: boolean;
    /**
     * THE FINDINGS LEDGER IS ARMED (9.101.0, `.findings()`) — present only
     * then, only ever `true`. Arms `assertReservedArgument`: a REGISTRY schema
     * that already declares the reserved `_findings` argument is refused at
     * build, naming the tool. Absent — the default — and not one line runs.
     */
    readonly findings?: true;
    /**
     * DECLARED SOURCES ARE ARMED (honesty layer 2 — `.inputsLayer({
     * argumentSources: true })` or `.findings({ argumentSources: true })`) —
     * present only then, only ever `true`. Arms `assertReservedArgument` over
     * every RULED tool of the dispatch map (`tools/declare.ts` ·
     * `carriesRules`): the tools the wire plants `_findings.from` on, a skill's
     * scoped tools included, whichever door armed it — without `.findings()`,
     * this is the only thing that arms the refusal. An UNRULED tool is never
     * decorated by this arm, so it keeps its own `_findings`. Absent — and not
     * one line runs.
     */
    readonly argumentSources?: true;
}
/**
 * Compose the augmented tool registry from the static `.tool()`
 * registry + the agent's injections (skills only). Throws on tool-
 * name collisions across sources.
 */
export declare function buildToolRegistry(registry: readonly ToolRegistryEntry[], injections: readonly Injection[], options?: BuildToolRegistryOptions): ToolRegistryArtifacts;
