/** * Live capability catalog — what *can* be granted, enumerated at runtime. * * Until now grants were checked against agent-type files alone, which covers `tool:` capabilities and * nothing else. That leaves two gaps: extension tools are invisible (so `ext:` grants cannot be * validated), and **skills were ungovernable** despite "skills and tools" being half the requirement. * * Sources, and why each is trusted: * - **tools** — the `tools` array of a live provider request. Authoritative: it is exactly what pi sent * the model, so it includes extension-registered tools and reflects any `--tools` allowlist already in * force. Nothing else can see the real surface. * - **skills** — `SKILL.md` directories and top-level `.md` files under pi's skill roots. * - **definitions** — spawnable `SKILL.md` agents (ADR-0016), as `agent:`. * * Provenance note: a provider payload gives tool NAMES, not owning packages, so extension tools cannot be * qualified as `ext:/` from that source alone. They are catalogued as `tool:` — which is * also how pi's `--tools` matches — and marked `kind: "extension"` for display. `ext:` ids remain * supported for hand-authored grants; `deriveOwnGrant` already matches them by bare name. */ import { type SkillDefinition } from "./definitions.ts"; import { type Capability } from "./resolve.ts"; import { type WorkspaceRegistryFile } from "./workspace.ts"; export type CapabilityKind = "builtin" | "extension" | "skill" | "agentType" | "workspace"; export interface CatalogEntry { capability: Capability; kind: CapabilityKind; /** Where it was found, for display and debugging. */ source?: string; } export interface Catalog { entries: CatalogEntry[]; /** Every capability id in the catalog. */ all: Capability[]; byKind(kind: CapabilityKind): Capability[]; has(capability: Capability): boolean; /** * Why the workspace registry contributed nothing, when one was named and could not be read. * * The catalog fails SOFT on a bad registry — a malformed one must not stop a session starting — and the * refusal used to be discarded by `() => []` at the call below. That is rule 8's silent safe-mode: one * malformed entry removed every workspace from `/grants`, from the catalog and from `init`, and produced * no message anywhere, so an operator saw an empty list and could not tell it apart from having * registered nothing. Failing soft is still right; failing soft and SILENTLY was not. `undefined` means * no registry was named, or it read cleanly. */ registryRefusal?: string; } /** Split observed tool names into pi built-ins and extension-provided tools. */ export declare function classifyToolNames(observed: string[]): CatalogEntry[]; /** Skill roots pi discovers, project first. */ export declare function skillDirs(cwd: string): string[]; /** Enumerate the same enabled Pi resource paths as definitions, preserving resolver precedence. */ export declare function loadSkills(cwd: string): Promise; /** * Spawnable definitions, as `agent:` capabilities. * * A definition is BOTH a skill (loadable into a session) and an agent (spawnable as a child) — ADR-0016 * collapsed those into one file — so the same `SKILL.md` legitimately appears twice in the catalog under * two capability ids. That is not duplication: `skill:review` means "may load these instructions" and * `agent:review` means "may spawn a child running them", and a grant can hold either without the other. */ export declare function definitionEntries(definitions: Map): CatalogEntry[]; /** * Registered workspaces, as `workspace:` capabilities (ADR-0035). * * For DISPLAY and SCAFFOLDING only — `/grants` listing what this session may route to, and `init` offering * the ids without choosing among them (ADR-0028). It is deliberately **not** what `unknownCapabilities` * checks against; see the comment there. * * The operator registry is the authority on which ids exist, exactly as it is at `resolveWorkspace`. This * enumerates it; it does not decide anything. */ export declare function workspaceEntries(registry: WorkspaceRegistryFile, source?: string): CatalogEntry[]; /** Assemble a catalog from parts. Pure, so it is testable without a filesystem. */ export declare function makeCatalog(entries: CatalogEntry[], registryRefusal?: string): Catalog; /** Build the live catalog. `observedTools` comes from a provider payload; null when not yet seen. */ export declare function buildCatalog(input: { cwd: string; observedTools: string[] | null; /** Operator workspace registry (`PI_DADDY_WORKSPACE_REGISTRY`). Absent or unreadable yields no entries. */ registryPath?: string; }): Promise; /** * Capabilities requested that the catalog does not contain. * * Reported separately from `denied` because the causes differ and so do the fixes: `denied` means the * delegator lacks authority, `unknown` means the capability does not exist here — usually a typo or a * stale grant referring to an uninstalled package. Silently treating unknown as denied hides that. */ export declare function unknownCapabilities(requested: Capability[], catalog: Catalog): Capability[]; /** * The capability an unknown one was most likely meant to be, or null when nothing is close enough. * * Two sources, in order. A known foreign name wins outright — `Glob` is not a typo for `find`, so no * distance metric would ever connect them, and that is exactly the case worth naming. Otherwise the * nearest catalog entry within a small edit distance, which catches `raed`/`serach` and stops well short * of guessing: the threshold scales with the name's length and never exceeds two. */ export declare function suggestForUnknown(unknown: Capability, catalog: Catalog): Capability | null; /** * Why an id carries two namespaces, said as the mistake that produced it. * * `ceilingForDefinition` prefixes a BARE entry with `tool:` and recognises an explicit namespace by its literal * lower-case prefix, so `allowed-tools: Read` and `allowed-tools: tool:read` are both right. The capitalised * spelling the same field invites is not: the frontmatter is copied from Claude Code, where the tool names are * `Read` and `Grep`, so an author who also reaches for the namespace writes `Tool:Read` — which misses the prefix * test, is lower-cased with the rest of the bare entry, and arrives as `tool:tool:read`. Every namespace has this * shape (`Workspace:prod` becomes `tool:workspace:prod`), which is why the prefix is read from the one shared list * rather than a second spelling of it. * * The messages that followed were loud and useless. A spawn refusal said "not present in this session's catalog * (typo, or an uninstalled package?)" about an id nobody typed, and `suggestForUnknown` cannot rescue it either: * `tool:read` is five edits from `read`, well past a threshold that never exceeds two. `pi-daddy init` refuses the * whole definition earlier still, because `isSafeCapability` rejects the extra colon, and named the mangled id too. * * It names the mistake and does not repair it, for `ceilingForDefinition`'s own reason: a capability this package * inferred rather than read is a grant nobody wrote. The author edits the file. * * **What the suggestion cannot recover.** The doubling happens on the bare-entry path, which lower-cases the whole * entry before this function ever sees it, so `Workspace:Prod` and `Workspace:prod` both arrive as * `tool:workspace:prod`. For `tool:` that loses nothing, since a pi tool name is lower-case anyway. For the other * namespaces the identifier's own capitalisation is already gone, so the suggestion says so rather than implying * that copying it verbatim must work. Nor is "or the bare name" offered outside `tool:`: a bare `prod` becomes * `tool:prod`, which is a second unknown capability rather than a fix. */ export declare function explainDoubledNamespace(unknown: Capability): string | null; /** * Skill name -> absolute path, for `planSpawn`'s `--skill` flags (R-32). * * Derived from the catalog's own `source` field rather than re-scanning, so what a child is handed * cannot drift from what was discovered and offered. A skill entry without a source is omitted, which * makes it *unresolvable* rather than silently absent — `planDelegation` refuses on that, because a * grant naming a skill the child never receives is a ledger line that lies. */ export declare function skillPathsFromCatalog(catalog: Catalog): Record; //# sourceMappingURL=catalog.d.ts.map