import type { EffectiveExtensionRoots, SourceMeta } from "../capability/types.js"; import type { Settings } from "../config/settings.js"; import { type SkillsSettings } from "./settings.js"; import { allowsSkillTokens, SKILL_TOKEN_RE } from "@oh-my-pi/pi-tui/prompt/skill-tokens"; import type { SkillPromptDetails } from "../session/messages.js"; export { allowsSkillTokens, SKILL_TOKEN_RE }; export interface Skill { name: string; description: string; filePath: string; baseDir: string; source: string; /** * When `true`, the skill is loaded and reachable via `skill://` and * (when enabled) `/skill:`, but is excluded from the rendered system * prompt's `` listing. */ hide?: boolean; /** * Filesystem-resolved plugin root for Agent Plugin skills (spec ยง4.1): * every `skill://` resource access must realpath-resolve within it. */ containRoot?: string; /** Source metadata for display */ _source?: SourceMeta; } export interface SkillWarning { skillPath: string; message: string; } export interface LoadSkillsResult { skills: Skill[]; warnings: SkillWarning[]; } /** * Process-global snapshot of skills the active session loaded. * Read by internal URL protocol handlers (skill://). */ export declare function getActiveSkills(): readonly Skill[]; /** Replace the active skill snapshot. Called once per top-level session. */ export declare function setActiveSkills(value: readonly Skill[]): void; /** Reset the active skill snapshot. Test-only. */ export declare function resetActiveSkillsForTests(): void; /** * Whether `name` is already claimed by an active authored (non-managed) skill. * * Managed (auto-learn) skills resolve dead-last in discovery, so an authored * skill of the same name always wins (see `loadSkills`) and a managed skill * written under an authored name is silently dropped โ€” it never surfaces. * `manage_skill` create consults this to refuse the write up front instead of * reporting a false "Created" for a skill that can never appear. */ export declare function isNameClaimedByAuthoredSkill(name: string): boolean; export interface LoadSkillsFromDirOptions { /** Directory to scan for skills */ dir: string; /** Source identifier for these skills */ source: string; } export declare function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Promise; export interface LoadSkillsOptions extends SkillsSettings { /** Working directory for project-local skills. Default: getProjectDir() */ cwd?: string; /** Disabled extension ids (`disabledExtensions`); `skill:` entries hide those skills. */ disabledExtensions?: string[]; /** * Session-local extension roots. Post-startup reloads pass their live * session value so explicit roots, discovery mode, and configured * extensions all survive outside the construction-time invocation scope. */ extensionRoots?: EffectiveExtensionRoots; } /** * Load skills from all configured locations. * Returns skills and any validation warnings. */ export declare function loadSkills(options?: LoadSkillsOptions): Promise; /** What {@link rediscoverSkills} needs from whoever holds a skill list. */ export interface SkillRediscoveryHost { /** The holder's current skills. */ skills: readonly Skill[]; /** Rediscovers the holder's own skills in place and returns them; absent when the list is fixed. */ refreshSkills?: () => Promise; /** Stores a re-pointed fixed list, so later reads and subagents spawned from the holder use it. */ replaceSkills?: (skills: readonly Skill[]) => Promise; /** Runs `rediscover`, or joins the holder's pass already in flight, so concurrent misses share one. */ shareRediscovery?: (rediscover: () => Promise) => Promise; /** * Rediscovers the holder's own skills for a `skill://` name its list lacks (a skill created or * installed since), rate-limited by the holder; undefined when it skips the pass. Absent for a * fixed list, which gains no new skills. */ rediscoverForUnknownName?: () => Promise | undefined; /** Settings for a one-off discovery; without them a fixed list cannot be re-pointed. */ settings?: Settings; cwd?: string; extensionRoots?: EffectiveExtensionRoots; } /** * A session that can describe itself as a {@link SkillRediscoveryHost}, asked when one of its skills * is read. A caller that cannot describe one (an embedder's partial session) reads the path it holds. */ export interface SkillRediscoverySource { skillRediscoveryHost?(): SkillRediscoveryHost; } /** * Skills to use again after one of them went stale: its instruction file is gone (`omp plugin * upgrade` installed the new version in a new cache directory and deleted the old one), or the * registries now install its plugin elsewhere ({@link isSkillSuperseded}). The plugin-root cache * and the discovery file cache still name the old directory, so both are dropped first. A holder * that rediscovers its own skills refreshes in place, so its list, its prompt and the process * snapshot follow. A fixed list (a subagent given its parent's) keeps every entry whose file still * exists; an entry whose file is gone is re-pointed, under the name it already has, at a fresh * discovery's same skill of the same plugin ({@link findStaleSkillStandIn}), or dropped when there * is none. The holder stores the result, so the next lookup needs no new pass. */ export declare function rediscoverSkills(host: SkillRediscoveryHost): Promise; /** * The skill in `skills`, found by a rediscovery, that stands in for `stale`: one from the same * registered plugin that is the same skill there, by name or else by its file's path inside the * plugin (a collision can list a plugin's skill under another name: the upgrade that rewrites the * registry moves the plugin after one with a same-named skill, which then keeps the bare name). * A skill outside any plugin stands in only for a same-named one outside any plugin. A same-named * skill from elsewhere has other instructions: another plugin's colliding skill takes the bare name * when an upgrade drops it, and a worktree subagent's discovery, which runs in its own cwd, finds * that project's skills. */ export declare function findStaleSkillStandIn(stale: Pick, skills: readonly Skill[]): Skill | undefined; /** * Whether `holder` must rediscover before it reads `skill` although the skill's file is still there: * the plugin registries now install the skill's plugin elsewhere (an upgrade that kept the old * version's directory, a project-scope install shadowing a user-scope one). Only a holder that * rediscovers its own skills asks: its list follows the registries for its cwd, while a fixed list * keeps what its creator gave it for as long as those files exist. */ export declare function isSkillSuperseded(skill: Pick, holder: SkillRediscoveryHost): Promise; export interface BuiltSkillPromptMessage { message: string; details: SkillPromptDetails; } export declare function getSkillSlashCommandName(skill: Pick): string; /** * Parsed `/skill:` invocation: either at the start of the draft (the * traditional slash-command position) or as a `/skill:` token embedded * mid-prompt. For the mid-prompt form the surrounding prose is threaded * through as `args` so the skill sees the full user request. */ export interface ParsedSkillInvocation { /** Bare skill name without the leading `skill:` prefix. */ name: string; /** User-supplied arguments (everything outside the `/skill:` token). */ args: string; /** The draft as submitted (trimmed), token in place โ€” drives the transcript layout. */ prompt: string; } /** * Detect a `/skill:` invocation in a user draft. * * Returns `undefined` when the text contains no skill token. Otherwise: * - Leading form (`/skill:foo bar baz`): name=`foo`, args=`bar baz`. * - Mid-prompt form (`fix the bug /skill:foo focus on auth`): name=`foo`, * args=`fix the bug focus on auth` โ€” the surrounding prose collapsed * into a single args string. * * Mid-prompt detection is gated by {@link allowsSkillTokens}. */ export declare function parseSkillInvocation(text: string): ParsedSkillInvocation | undefined; export type SkillInvocationKind = "user" | "autoload"; /** What the user typed around a skill token: `args` feed the template, `prompt` only the transcript. */ export type SkillPromptInput = Pick & Partial>; /** Read a skill's instruction body before rendering user-provided arguments. */ export declare function loadSkillPromptBody(skill: Pick): Promise; /** A skill's instruction body and the skill it was read from. */ export interface LoadedSkillPrompt { skill: Pick; body: string; } /** * Reads a skill's instruction body. With `source`, a stale skill (its instruction file is gone, or * for a session that rediscovers its own skills, its plugin install was replaced; see * {@link rediscoverSkills}) is replaced by the same skill of the same plugin from a rediscovery * ({@link findStaleSkillStandIn}); the returned `skill` names the file actually read, so render * with it. */ export declare function loadSkillPrompt(skill: Pick, source?: SkillRediscoverySource): Promise; /** Render a loaded skill with user arguments substituted by its static template. */ export declare function renderSkillPromptMessage(skill: Pick, input: SkillPromptInput, body: string, invocation?: SkillInvocationKind, options?: { expandUserArgs?: (args: string) => string; }): BuiltSkillPromptMessage; /** * Load and render a skill prompt without authorizing session-specific model mentions. With * `source`, a stale skill is read from its rediscovered replacement ({@link loadSkillPrompt}); * `details.path` names the file actually read. */ export declare function buildSkillPromptMessage(skill: Pick, input: SkillPromptInput, invocation?: SkillInvocationKind, source?: SkillRediscoverySource): Promise;