import type { Agent, AgentMessage, AgentTool } from "@oh-my-pi/pi-agent-core"; import type { Model } from "@oh-my-pi/pi-ai"; import type { EffectiveExtensionRoots } from "../capability/types.js"; import type { ModelRegistry } from "../config/model-registry.js"; import type { Settings } from "../config/settings.js"; import type { CustomTool } from "../extensibility/custom-tools/types.js"; import type { ExtensionRunner, ToolInfo } from "../extensibility/extensions/index.js"; import { type EvalPreludeDefinition } from "../eval/preludes.js"; import { type Skill, type SkillWarning } from "../extensibility/skills.js"; import { type LocalProtocolOptions } from "../internal-urls/index.js"; import type { MemoryBackendStartOptions, MemoryPromptPreparation } from "../memory-backend/types.js"; import type { AgentDefinition } from "../task/types.js"; import { type XdevState } from "../tools/xdev.js"; import type { EditModeSetting } from "../edit/settings.js"; import type { ClientBridge } from "./client-bridge.js"; import type { CustomMessage } from "./messages.js"; import type { SessionEntry } from "./session-entries.js"; import type { SessionManager } from "./session-manager.js"; import { type SkillsSettings } from "../extensibility/settings.js"; /** Capabilities borrowed from the owning AgentSession. */ export interface SessionToolsHost { agent: Agent; sessionManager: SessionManager; settings: Settings; /** Session-local extension roots (explicit + mode + configured) for post-startup reloads. */ effectiveExtensionRoots(): EffectiveExtensionRoots; modelRegistry: ModelRegistry; extensionRunner(): ExtensionRunner | undefined; clientBridge(): ClientBridge | undefined; agentKind(): "main" | "sub"; isDisposed(): boolean; isStreaming(): boolean; /** * Aborts once per user steering-message arrival; the ACP permission gate * races it against its elicit so a steer supersedes an open dialog. */ steeringArrivalSignal(): AbortSignal; /** * Monotonic count of steering arrivals fired so far. The permission gate * snapshots it when it parks, so its read of * {@link steeringArrivalAdmitted} covers exactly the arrivals inside its * own wait window — never a group that fired before the dialog opened. */ steeringArrivalSeq(): number; /** * The user-message groups admitted by the steering arrivals after `since` * (a snapshot from {@link steeringArrivalSeq}), flattened in admission * order — first post-park arrival first — or `undefined` when none. The * permission gate promotes exactly these messages: an agent-authored * steer enqueued in the same window never fired the signal and must not * ride the promotion ahead of the user's superseding message, and an * earlier pre-dialog steer must not be re-promoted by a later dialog. */ steeringArrivalAdmitted(since?: number): readonly AgentMessage[] | undefined; /** * Release a park snapshot taken by {@link steeringArrivalSeq} once its * dialog ends without superseding (the read in * {@link steeringArrivalAdmitted} releases it on the supersede path). * Without this, sessions that steer often but rarely supersede a dialog * would pin every arrival group since that stale snapshot forever. */ steeringArrivalRelease(seq: number): void; queuedMessageCount(): number; planModeEnabled(): boolean; model(): Model | undefined; memoryBackendSession(): MemoryBackendStartOptions["session"]; clearInheritedProviderPromptCacheKey(): void; clearMemoryPromotionSnapshot(): void; captureMemoryPromotionSnapshot(prompt: string[]): void; emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void; notifyCommandMetadataChanged(): void; localProtocolOptions(): LocalProtocolOptions; /** Live enabled eval preludes; candidates for the next base rebuild's advertised snapshot. */ evalPreludes(): readonly EvalPreludeDefinition[]; /** Live user-tagged model agents; candidates for the next base rebuild's advertised snapshot. */ sessionAgents(): readonly AgentDefinition[]; /** Runs a wait for the user's answer, such as an ACP client's permission request. */ waitForUser(dialog: () => Promise): Promise; } /** Registry delta applied by the SDK's settings-gated tool reconcile. */ export interface SettingsGatedToolDelta { /** Newly registered names: `builtIn` marks built-in factory provenance, `activate` joins the enabled set. */ readonly added: readonly { readonly name: string; readonly builtIn: boolean; readonly activate: boolean; }[]; /** Names whose registry entries were removed. */ readonly removed: readonly string[]; /** `xd://` state after following `tools.xdev`; undefined while mounting is off. */ readonly xdev: XdevState | undefined; } /** * Renders the base prompt for an active tool set. `xdev` is the `xd://` state * to render: the live state, or a frozen prefix's committed roster when a * rebuild re-renders it. Supplied by `sdk.ts`; called on every prompt rebuild. */ export type SystemPromptRebuilder = (toolNames: string[], tools: Map, xdev: XdevState | undefined) => Promise<{ systemPrompt: string[]; xdevCatalogNames?: readonly string[]; }>; interface SessionToolsOptions { autoApprove?: boolean; toolRegistry?: Map; createVibeTools?: () => AgentTool[]; /** Creates the private `think` scratchpad tool for runtime setting changes. */ createThinkTool?: () => Promise; builtInToolNames?: Iterable; presentationPinnedToolNames?: ReadonlySet; /** MCP tool names whose current registry entries came from the manager snapshot. */ mcpManagerToolNames?: Iterable; ensureWriteRegistered?: () => Promise; isDeviceOnlyWrite?: () => boolean; setDeviceOnlyWrite?: (enabled: boolean) => void; setPendingFullWriteDescription?: (enabled: boolean) => void; /** Registers the hidden `goal` tool when goal mode is enabled at runtime. */ ensureGoalRegistered?: () => Promise; /** * Re-resolves settings-gated tools against live settings, mutating the shared * registry; `isBuiltIn` reports this session's built-in provenance. */ reconcileSettingsGatedTools?: (isBuiltIn: (name: string) => boolean) => Promise; rebuildSystemPrompt?: SystemPromptRebuilder; getMcpServerInstructions?: () => Map | undefined; xdev?: XdevState; setActiveToolNames?: (names: Iterable) => void; baseSystemPrompt: string[]; skills?: Skill[]; skillWarnings?: SkillWarning[]; skillsSettings?: SkillsSettings; skillsReloadable?: boolean; } interface SystemPromptPreparation { systemPrompt: string[]; /** Memory backend note to deliver with the turn ({@link MemoryPromptPreparation.notice}). */ memoryNotice?: MemoryPromptPreparation["notice"]; /** Publish staged state at validated delivery; false declines the prepared turn without mutation. */ commit?(): boolean; } export interface MountedMCPToolRouteSource { readonly name: string; readonly mcpServerName?: unknown; readonly mcpToolName?: unknown; } export interface MountedMCPToolRoute { readonly mcpServerName: string; readonly mcpToolName: string; readonly name: string; } export interface MCPXdevGuidanceMapping extends MountedMCPToolRoute { readonly label: string; readonly path: string; } export interface MCPXdevGuidanceProjection { readonly mappings: readonly MCPXdevGuidanceMapping[]; readonly hasOmittedMappings: boolean; } /** Yield exact mounted MCP ownership and route metadata. */ export declare function collectMountedMCPToolRoutes(tools: Iterable): Generator; /** * Project exact live MCP routes into the bounded, Markdown-safe mapping data * rendered by the static MCP guidance prompt. */ export declare function projectMountedMCPXdevGuidance(routes: Iterable): MCPXdevGuidanceProjection; /** The digest a branch entry records, `undefined` for a malformed record, or `null` when it is not a record. */ export declare function promptDigestOfEntry(entry: SessionEntry): string | undefined | null; /** * Structured payload persisted on each {@link XDEV_MOUNT_NOTICE_MESSAGE_TYPE} * custom message. Lets a resumed session reconstruct which dynamic devices the * model has already been told about, so reconnecting hosts do not re-announce * (and re-splice a redundant developer message that busts the provider * prompt-cache prefix). */ interface ToolRosterNoticeDetails { added: string[]; removed: string[]; } interface XdevMountNoticeDetails { added: string[]; removed: string[]; } /** Prelude names added/removed by one hidden {@link EVAL_PRELUDE_NOTICE_MESSAGE_TYPE} message. */ interface EvalPreludeNoticeDetails { added: string[]; removed: string[]; } /** Skills one hidden {@link SKILL_NOTICE_MESSAGE_TYPE} message announced or withdrew. */ interface SkillNoticeDetails { announced: Array<{ name: string; description: string; }>; removed: string[]; } /** Agent pseudonyms added/removed by one hidden {@link SESSION_AGENT_NOTICE_TYPE} message. */ interface SessionAgentNoticeDetails { added: string[]; removed: string[]; } interface PendingNoticePreview { notice: CustomMessage | undefined; /** * Exact rendered content included in the context estimate. Re-projecting and * comparing this key after maintenance catches every semantically relevant * change — mount membership, base-catalog suppression, summaries, and schemas * — without false-invalidating a byte-identical notice after a hidden rebuild. */ contentKey: string; } /** Owns tool registration, presentation, prompt rebuilding, skills, and permissions. */ export declare class SessionTools { #private; constructor(host: SessionToolsHost, options: SessionToolsOptions); /** Mutable registry shared with controller hosts that inspect available tools. */ get registry(): Map; /** Current stable base system prompt. */ get baseSystemPrompt(): string[]; /** Replaces the controller-owned base prompt without applying it to the agent. */ setBaseSystemPrompt(prompt: string[]): void; /** * Registers the per-turn `before_agent_start` system-prompt override and * applies it to the agent. Base rebuilds during the turn preserve it until * {@link clearTurnSystemPromptOverride}. */ setTurnSystemPromptOverride(prompt: string[], base: string[]): void; /** * The base prompt a system prompt the agent sent was built from: the hook's input when it is a * per-turn override, else the prompt itself, since every other prompt applied to the agent is a base. */ baseOfSystemPrompt(prompt: string[]): string[]; /** Drops the active per-turn override; later rebuilds fall back to the base prompt. */ clearTurnSystemPromptOverride(): void; /** The live skill registry: what `skill://` and `/skill:` resolve. */ get skills(): Skill[]; /** Skills the system prompt renders; behind {@link skills} after {@link reloadSkills}. */ get promptSkills(): Skill[]; /** Whether {@link refreshSkills} rediscovers the list; false when it was fixed at creation (a subagent's). */ get skillsReloadable(): boolean; /** Stores a re-pointed fixed skill list (see `rediscoverSkills`) and refreshes prompt metadata. */ replaceSkills(skills: readonly Skill[]): Promise; /** Runs `rediscover`, or joins the rediscovery already in flight, so concurrent misses share one pass. */ shareSkillRediscovery(rediscover: () => Promise): Promise; /** * Skills rediscovered for a `skill://` name the list lacks (one created or installed since the * list was taken, or a typo): joins a pass in flight, else starts one unless another such miss * started one within {@link UNKNOWN_SKILL_REDISCOVERY_INTERVAL_MS}. Undefined when skipped. */ rediscoverSkillsForUnknownName(): Promise | undefined; /** Diagnostics produced while loading the current skills. */ get skillWarnings(): SkillWarning[]; /** Settings snapshot used for the current skill discovery. */ get skillsSettings(): SkillsSettings | undefined; /** * Skill-URI hint visibility (see {@link #promptSurface}). Tools read this * instead of the live `skillful` setting so the provider tool prefix stays * byte-stable between system-prompt rebuilds. * * Inside a rebuild frame ({@link #promptSurfaceScope}) this returns the * candidate so the rendered prompt and computed signature see the new * state; outside, the committed snapshot. */ get skillHintVisible(): boolean; /** * Eval preludes the system prompt and eval description advertise (see * {@link #promptSurface}); the candidate inside a rebuild frame. */ get advertisedEvalPreludes(): readonly EvalPreludeDefinition[]; /** * User-tagged model agents the task description advertises (see * {@link #promptSurface}); the candidate inside a rebuild frame. */ get advertisedSessionAgents(): readonly AgentDefinition[]; /** * Tool names the eval description advertises (see {@link #promptSurface}); * the candidate inside a rebuild frame. Tools enabled since the last rebuild * stay callable from cells and ride the mount notice until a rebuild absorbs them. */ get advertisedEvalToolNames(): readonly string[]; /** Drops cached per-session ACP `allow_always`/`reject_always` decisions. */ clearAcpPermissionDecisions(): void; /** Drops cached ACP decisions and re-wraps active tools after the client changes. */ refreshAcpPermissionGates(): void; /** Names of tools currently exposed at the top level. */ getActiveToolNames(): string[]; /** Enabled top-level and `xd://` tool names. */ getEnabledToolNames(): string[]; /** Names currently presented as `xd://` devices. */ getMountedXdevToolNames(): string[]; /** Whether the edit tool is registered. */ get hasEditTool(): boolean; /** * Looks up a registered tool by its canonical name or `xd://` alias. * * An unmatched `mcp__` name is retried under its canonical registry keys: the * identity prompt primes the Claude Code spelling `mcp____` * while `createMCPToolName` mints a single separator, so the doubled form is * a dead end for a tool the session does expose. That retry goes through the * shared {@link resolveMCPToolAlias}, so an alias two registered tools both * answer resolves to nothing here exactly as it does at dispatch — this * method is public and also picks transcript renderers, so a first-match * shortcut could render or return the wrong tool. */ getToolByName(name: string): AgentTool | undefined; /** * Looks up an enabled tool through the same ACP permission gate as direct calls. * * Accepts the spellings direct dispatch accepts: an `xd://` prefix and the * Claude Code `mcp____` alias. The alias resolves in one * {@link resolveMCPToolAlias} pass over every enabled tool (top-level plus * mounted devices), so an alias two enabled tools answer resolves to nothing. */ getToolForEvalBridge(name: string): AgentTool | undefined; /** Canonical and unambiguous wire names accepted by the eval bridge. */ getEvalBridgeToolNames(): string[]; /** * Whether a registry entry came from a built-in factory. * * Resolves `customWireName` aliases too: a built-in tool may present on the * wire under a different name (e.g. `edit` exposes itself as `apply_patch` in * apply_patch mode), and tool cards render the call under that wire name. An * extension registering the literal alias name shadows it — the agent loop * routes exact-name matches ahead of wire aliases — so a registered non-built-in * tool with that name wins and the alias no longer counts as built-in. */ hasBuiltInTool(name: string): boolean; /** Updates source provenance when a live registry entry is replaced or restored. */ setToolBuiltIn(name: string, builtIn: boolean): void; /** Whether the live registry entry is owned by the RPC host. */ hasRpcHostTool(name: string): boolean; /** Whether the current MCP entry came from the manager snapshot. */ hasMCPManagerTool(name: string): boolean; /** Restores manager ownership after a lifecycle registration rollback. */ setMCPManagerTool(name: string, managerOwned: boolean): void; /** Current extension-owned MCP entry retained across manager refreshes. */ getExtensionMCPTool(name: string): AgentTool | undefined; /** Updates extension ownership when a lifecycle registration commits or rolls back. */ setExtensionMCPTool(name: string, tool: AgentTool | undefined): void; /** Serializes every registry and presentation mutation for this session. */ runToolRegistryMutation(mutation: () => Promise, signal?: AbortSignal): Promise; /** Names of every registered tool. */ getAllToolNames(): string[]; /** * Full metadata for every registered tool, including source provenance. * * Backs the `getAllTools()` ExtensionAPI method. Returns {@link ToolInfo} * objects (not bare names) so extensions authored against upstream * `@earendil-works/pi-coding-agent` — which promises `ToolInfo[]` — can read * `sourceInfo.source` unchanged. */ getAllToolInfos(): ToolInfo[]; /** Installs and activates the ephemeral vibe tool set. */ activateVibeTools(baseToolNames: string[]): Promise; /** Uninstalls vibe tools and activates the replacement set. */ deactivateVibeTools(nextToolNames: string[]): Promise; /** Removes vibe tools without restoring a source-session snapshot. */ removeVibeToolsPreservingActive(): Promise; /** Resolves the edit mode for the active model and settings; `"none"` when the edit tool is disabled. */ resolveActiveEditMode(): EditModeSetting; /** * Rebuilds model-dependent tool prompts after a model change. * * `retryFallback` marks a hop down the retry fallback chain. It keeps the Model line and the * Delegation wording the prompt was rendered with: re-rendering them changed the prompt's bytes, * and the provider then wrote the whole cached conversation again on every hop. It still rebuilds * when the hop changes what the request must carry for the new model: the edit-mode policy * (#11983), or the inline-descriptor decision, which also turns provider-side description pruning * on or off (#14200). The kept key makes a later switch back to the rendered model a no-op, and a * deliberate switch (`/model`, `setModel`) compares against the model the prompt describes. */ syncAfterModelChange(previousEditMode: EditModeSetting, options?: { retryFallback?: boolean; }): Promise; /** Enabled MCP tools in their current presentation partition. */ getSelectedMCPToolNames(): string[]; /** Applies an enabled tool set and reconciles its `xd://` partition. */ applyActiveToolsByName(toolNames: string[], forcePromptRefresh?: boolean, signal?: AbortSignal): Promise; /** Digest of the base prompt the current branch's newest reply with provider output was produced under. */ recordedPromptDigest(): string | undefined; /** Digest of the agent's current base prompt, comparable with the digests recorded on the branch. */ currentPromptDigest(): string; /** * Called with the prompt a primary model call captures, before the request is * sent. From then on the transcript's signed thinking may be bound to that * prompt, whatever later history edits (`/tree`, fork, recovery) leave as the * newest reply, so the restored transcript is dropped for good. The capture * is not recorded yet: the call may still stop before it is sent; see * {@link bindReplyToCapturedPrompt}. Side requests (`runEphemeralTurn`) do * not count. */ recordPrimaryModelCall(prompt: string[]): void; /** * Called synchronously when a primary reply ends. A reply carrying provider * output was produced under the last captured prompt; one without (a call * stopped before it was sent, an abort before the first event) binds nothing, * so the replies before it keep their prompt. */ bindReplyToCapturedPrompt(reply: AgentMessage): void; /** * Called just before a primary reply is appended to the branch: records the * digest of the prompt it was produced under when it differs from the * branch's, ahead of the reply so every path to the reply carries it. */ recordReplyPrompt(reply: AgentMessage): void; /** Records `digest` on the current branch unless it is already the branch's newest record. */ recordPromptDigest(digest: string | undefined): void; /** Drops retained transcript references when the session is disposed. */ releaseRestoredTranscript(): void; /** * Forget the announced-mount baseline for a replaced transcript. Called when * session history is swapped wholesale (`/new`, `switchSession`, `branch`): the * previous transcript's persisted notices no longer apply, so the next notice * re-seeds from the new history and a device reconnecting into it announces * again. * * The pending delta is deliberately preserved: it holds mounts that are still * live but not yet delivered to the model, and `branch()` does not rebuild the * base system prompt, so dropping it would leave the branched transcript * unaware of a still-mounted device that no later refresh would re-queue. */ resetAnnouncedMounts(): void; /** * Consumes the hidden provider-visible roster notice for the current pending * delta. This carries no meaningful payload (a short tool-name list), so it is * never previewed or deferred for context budgeting: it is re-derived from the * live delta after pre-prompt maintenance, keeping the model's stated * availability in lockstep with the wire tool list. * * When a rebuilt base is delivered, the notice includes only changes made after * that snapshot. A per-turn `before_agent_start` override that hides the rebuilt * base instead receives the complete delta from the provider's last known * roster. */ takePendingToolRosterNotice(options: { baseDelivered: boolean; }): CustomMessage | undefined; /** * Builds the hidden notice reconciling the eval preludes the model knows with * the live set. Delivered with the next user prompt instead of rebuilding the * system prompt, so a mid-session toggle (`/computer on`) keeps the provider * cache prefix intact. * * Known preludes are the committed base snapshot, then every prelude notice * still in context, applied in order. Deriving this from the transcript * rather than tracked state stays truthful across base rebuilds (which absorb * the live set), compaction (which drops old notices), and resume. */ takeEvalPreludeNotice(): CustomMessage | undefined; /** * Builds the hidden notice reconciling the user-tagged model agents the task * description advertises with the live set. Delivered with the next user * prompt instead of rewriting the task description, so tagging a model * mid-session keeps the provider cache prefix intact. * * Known agents are the committed base snapshot, then every agent notice still * in context, applied in order — mirroring {@link takeEvalPreludeNotice}. */ takeSessionAgentNotice(): CustomMessage | undefined; /** * Builds the hidden notice of skills the live registry has gained, changed or lost since the system * prompt and the notices still in context listed them. A changed description is announced again * because the model routes on it. Gated like the prompt's skill list: `skillful` on and an enabled * tool, top-level or `xd://`-mounted, that reads `skill://`. */ takeSkillNotice(): CustomMessage | undefined; /** Previews the hidden `xd://` mount notice and its rendered-content fingerprint. */ peekPendingXdevMountNotice(options: { baseCatalogDelivered: boolean; }): PendingNoticePreview | undefined; /** * Consumes the mount notice only when its rendered content still matches the * preview included in the context estimate. */ takePendingXdevMountNotice(options: { baseCatalogDelivered: boolean; expectedContentKey: string; }): CustomMessage | undefined; /** Rediscovers reloadable skills and rebuilds the system prompt's skill list. */ refreshSkills(): Promise; /** * Rediscovers skills for the registry only, after a tool wrote or deleted one. The system prompt keeps * its list so the provider cache prefix survives; {@link takeSkillNotice} tells the model instead. */ reloadSkills(): Promise; /** Selects enabled tools, ignoring names absent from the registry. */ setActiveToolsByName(toolNames: string[]): Promise; /** * Restore an enabled tool set with its exact top-level versus `xd://` partition. * * Both inputs are required because {@link setActiveToolsByName} only receives the * enabled name list and classifies mounts from the current presentation set. * Rollback/restore callers must pass the snapshotted mounted subset so names that * were top-level stay pinned (`#runtimeSelectedToolNames`) and names that were under * `xd://` remain mount-eligible, even when the live mount set has drifted. * * Names outside `mountedToolNames` are pinned top-level for this application; * names in the mounted subset remain eligible for xdev mounting. Set * `forcePromptRefresh` when an enabled tool's schema or prompt-visible metadata * changed without changing its name or presentation. * * Delegates the actual apply through {@link applyActiveToolsByName} and restores * the prior runtime selection if that apply throws. */ setActiveToolPresentation(toolNames: string[], mountedToolNames: string[], forcePromptRefresh?: boolean, signal?: AbortSignal): Promise; /** Restores a non-MCP presentation snapshot plus the MCP selection and non-built-in tools registered after it. */ restoreNonMCPToolPresentation(nonMCPToolNames: string[], nonMCPMountedToolNames: string[], registeredToolNames: readonly string[]): Promise; /** Replaces memory-backend tools while preserving unrelated selections. */ replaceMemoryTools(tools: AgentTool[]): Promise; /** * Reconciles the private `think` scratchpad with the `externalThinking` * setting and the active model. Enabling constructs the tool once; * disabling removes it from the active set but keeps its registry entry. * * @returns false when enabling was requested but this session cannot build the tool. */ reconcileThinkTool(): Promise; /** * Re-resolves settings-gated tools (built-in factories, image/speech generation, * `xd://` mounting) against live settings: registers newly allowed tools, * unregisters disallowed ones, and reapplies the enabled set with one forced * prompt rebuild — unconditionally unless `refreshPrompt` is false, then only * when the tool set changed. Unrelated selections and MCP/extension tools are * preserved. */ reconcileBuiltinTools({ refreshPrompt }?: { refreshPrompt?: boolean; }): Promise; /** * Rebuilds the stable base prompt for the current tools and model. * `commitIf` lets asynchronous producers discard a stale rebuild atomically * after its inputs have been superseded. */ refreshBaseSystemPrompt(commitIf?: () => boolean): Promise; /** Stages memory prompt injection; the owning turn commits it together with extension policy. */ buildSystemPromptForAgentStart(promptText: string, isCurrent: () => boolean, signal?: AbortSignal): Promise; /** * Replace MCP tools in the registry and enable them immediately. Refreshes * are serialized so an older asynchronous prompt rebuild cannot commit * after a newer catalog snapshot. Every connected MCP tool becomes available * (mounted under `xd://` when that transport is active, else top-level); * while vibe mode is active, activation waits for vibe exit. */ refreshMCPTools(mcpTools: CustomTool[]): Promise; /** Replaces RPC host-owned tools and refreshes the active set before the next model call. */ refreshRpcHostTools(rpcTools: AgentTool[]): Promise; } export {};