import type { Capability, CapabilityOrigin, Logger } from "@skaile/workspaces/types"; import type { DefinedCapability, HandlerContext } from "./define-capability.js"; /** * Function shape for the platform-bound capability invoker. * * The runner uses this to dispatch `host.*` capability invocations (e.g. * `host.refresh_credential`, `host.audit`, `host.notify_user`) over the * transport and await the matching `CapabilityResultCommand`. Implementations * own the wire serialization and the per-call pending map. * * The result value is the capability's domain payload (already * deserialised; for `host.refresh_credential` this is a `CredentialMint`). * Capability-dispatch infrastructure failures (timeout, schema validation, * transport disconnect) are thrown as `Error` so callers can distinguish * them from domain failures encoded in the payload. * * @category Runtime * @since 3.0.0 * @docLink packages/runner/capabilities#remote-invoker */ export type RemoteCapabilityInvoker = (name: string, input: unknown, timeoutMs?: number) => Promise; /** * Minimal LLM tool descriptor consumed by bridge drivers. Drivers translate * this into the provider-specific tool spec (Claude tool definition, OpenAI * function spec, etc.) at registration time. * * Only the fields needed for tool registration are surfaced here; runtime * dispatch flows through {@link CapabilityRegistry.invoke}. * * @category Runtime * @since 2.0.0 * @docLink packages/runner/capabilities#registry-surface */ export type LLMTool = { /** Capability name; used as the LLM-visible tool name. */ name: string; /** Human/LLM-readable description. */ description: string; /** JSON Schema for the tool's input parameters. */ parameters: Record; }; /** * Filter shape accepted by {@link CapabilityRegistry.list}. All fields are * optional and match exactly when present. * * @category Runtime * @since 2.0.0 * @docLink packages/runner/capabilities#registry-surface */ export type CapabilityListFilter = { side?: "client" | "agent" | "app"; origin?: CapabilityOrigin["kind"]; scope?: "session" | "turn"; }; /** * Side-tag declaring who is registering a capability. The registry uses * this to enforce origin trust — a client cannot register an `agent`-side * origin, the agent cannot register `client` / `app` origins, and an * embedded app may register only its own `app` origin. * * The `app` source carries the registering connection's `appId` so the * registry can assert that an `app`-origin capability is declared for the * connection it actually arrived on (a compromised source cannot register * `app.delete_everything` under someone else's `appId`). Bare-string * `client` / `agent` sources are unchanged. * * @category Runtime * @since 2.0.0 * @docLink packages/runner/capabilities#registry-surface */ export type RegisterSource = "client" | "agent" | { kind: "app"; appId: string; }; /** * The `instance` segment of a capability's log slice. An app capability carries * its `appId`, yielding `capability:app:.` — so an operator can * attribute an invocation to the app that answered it, not just to its name. * * The `appId.name` join is unambiguous because an `appId` cannot contain a dot: * the platform validates it as `/^[a-z0-9-]+$/` before an app connection is ever * bound. Nothing here re-checks it; a dotted `appId` would only blur the slice. */ export declare function capabilityLogInstance(cap: Pick): string; /** * Per-session registry of capabilities. Construct one per * `startAgentServer` invocation; the runner threads it through serve mode's * command handlers and the bridge. * * @category Runtime * @since 2.0.0 * @docLink packages/runner/capabilities#registry-surface */ export declare class CapabilityRegistry { private readonly byName; private readonly logger; private remoteInvoker?; private readonly toolListeners; /** Observe inventory changes without caching a second tool registry. */ onToolsChanged(listener: () => void): () => void; private toolsChanged; constructor(logger?: Logger); /** * Install the platform-bound invoker used by {@link invokeRemote}. * * The runner wires this up after the transport is connected and the * per-session pending-call map is in place. Idempotent — repeated calls * replace the prior invoker. * * @category Runtime * @since 3.0.0 */ setRemoteInvoker(invoker: RemoteCapabilityInvoker | undefined): void; /** * Invoke a `host.*` (or other peer-side) capability on the platform. * * Composes a `capability_invoked` event under the hood and resolves with * the matching `capability_result` payload. Infrastructure failures * (timeout, schema mismatch, transport disconnect) reject with `Error`; * domain failures (e.g. `{ ok: false, code }` for credential mints) ride * in the resolved payload. * * @category Runtime * @since 3.0.0 */ invokeRemote(name: string, input: unknown): Promise; /** * Register a capability. Validates that the registering `source` is * permitted to declare the capability's `origin.kind`; mismatches are * dropped with a warning so untrusted callers cannot impersonate other * registration sources. * * Allowed combinations: * - source `'agent'`: origins `framework | agent | flow | skill | mcp | connector | mount` * — never `client`, and never `app` (an in-process registrant must not be * able to impersonate an embedded app) * - source `'client'`: origins `client` or `app`. The platform gateway is the * only client peer; it forwards an embedded app's capabilities after binding * each to the connection it arrived on. The runner cannot verify an `appId`. * - source `{ kind: 'app', appId }`: origin `app` only, and the origin's * `appId` must match the source `appId`. For a direct app→runner connection; * unused today. * * @param cap - capability to register (typically built via {@link defineCapability}) * @param source - which side is doing the registration */ register(cap: DefinedCapability, source: RegisterSource): void; /** * Remove a capability by name. No-op when the name is unknown. * * @param name - capability name */ deregister(name: string): void; /** * List capabilities, optionally filtering by `side`, `origin` kind, or * `scope`. Returns the wire-format {@link Capability} shape (no internal * Zod schemas or handlers). */ list(filter?: CapabilityListFilter): Capability[]; /** * Resolve a capability by name. Returns the full {@link DefinedCapability} * (including the original Zod schemas + handler) for use by the bridge. * * @returns the capability or `null` when the name is not registered */ resolve(name: string): DefinedCapability | null; /** * Build LLM tool descriptors for every registered capability whose * audience includes `'llm'` (the default when {@link Capability.audience} * is omitted). Bridge drivers consume this output directly when * assembling the tool list passed to the underlying LLM SDK. * * Capabilities scoped to `'runtime'` only (e.g. `host.refresh_credential`, * `runner.add_mount`) are filtered out so they never reach the model. */ composeLLMTools(): LLMTool[]; /** * Compose a deterministic prompt section from every registered * capability whose audience includes `'llm'`. Ordering matches * `ORIGIN_PROMPT_ORDER` (framework first, then client, then agent-side * surfaces) so the agent sees a stable layout. Capabilities without a * `promptFragment` are skipped. */ composePromptSection(): string; /** * Serialize the registry to wire-format capabilities for hibernation. The * runtime fields (`inputZod`, `outputZod`, `handler`) are dropped; only * the wire shape is kept. */ serialize(): Capability[]; /** * Replay a wire snapshot taken via {@link serialize}. Wire-only capabilities * (no handler) are stored so {@link list} / {@link composeLLMTools} return * a complete picture; agent-side surfaces are expected to re-register * fully (with handlers) from their original sources on wake. * * Per the v2 design, only `client`-side capabilities are persisted across * hibernation by this method — agent-side ones come back via the * skill / flow / connector / mount adapters. Snapshot entries from other * origins are skipped on hydrate. * * Embedded-app capabilities carry an `app` origin, so the `client`-origin * check already excludes them; the reserved-prefix skip below is a backstop * against a capability that reaches the registry with a relabelled origin. */ hydrate(snapshot: Capability[]): void; /** * Invoke a registered capability. Validates the input against the Zod * schema, builds the per-handler logger, and runs the handler. Logs * `info "invoking"` and `info "ok"` (or `error "failed"`) under the * `capability::` slice for every call. * * @throws when the capability is unknown or the input fails validation */ invoke(name: string, input: unknown, baseCtx: Omit): Promise; /** True when `source` is allowed to register the given `origin`. */ private isOriginAllowed; /** Strip the runtime-only fields from a {@link DefinedCapability}. */ private toWire; } /** * Compute a deterministic signature over a registry's wire-format * capabilities. Used by the session-resume cascade (tier-1 native SDK * resume) to detect toolset drift between hibernate and wake. * * The signature hashes a canonicalized projection of each capability: * `name + origin.kind + sha256(inputSchema)`. Capabilities are sorted * by `name` so registration order does not affect the result. * * Excluded from the hash: * - `displayName` and `userInvokable` — UI metadata only; do not affect * what the LLM can call. * - `description` and `outputSchema` — text/shape changes that don't * alter the call surface. * * Spec: `_devlog/specs/2026-05-05-session-resume-restart-design.md` * § "Capability signature". * * @param caps - Wire-format capabilities from {@link CapabilityRegistry.list}. * @returns A hex SHA-256 string that uniquely identifies the toolset. * @category Resume * @since 3.2.0 * @docLink packages/runner/capabilities#compute-capability-signature */ export declare function computeCapabilitySignature(caps: readonly Capability[]): string; //# sourceMappingURL=capability-registry.d.ts.map