import type { AgentTool, AgentToolContext, ToolApprovalDecision, ToolTier } from "@oh-my-pi/pi-agent-core"; import type { Settings } from "../config/settings.js"; import type { ExtensionApprovalAnswer, ExtensionApprovalPrompt, ExtensionUIContext } from "../extensibility/extensions/types.js"; import { type ApprovalGrantStore, type ApprovalGrants } from "./approval-cache.js"; export type { ToolApproval, ToolApprovalDecision, ToolTier } from "@oh-my-pi/pi-agent-core"; export type ApprovalPolicy = "allow" | "deny" | "prompt"; /** Every approval mode: the type below and `isApprovalMode` read from one list. */ declare const APPROVAL_MODES: readonly ["always-ask", "always-ask-cached", "write", "yolo"]; export type ApprovalMode = (typeof APPROVAL_MODES)[number]; /** The slice of `AgentToolContext` that approval resolution actually reads. */ export type ApprovalContextSource = { autoApprove?: boolean; settings?: Settings; }; export interface ResolvedExecuteTimeApproval { approvalMode: ApprovalMode; userPolicies: Record; } type ApprovalSubject = Pick & { /** * Previous public name of this tool, when a rename changed how it mints. * MCP tools minted before digits were kept carry their digit-stripped name * here so user `deny`/`prompt` policies written against it still apply * (`allow` is deliberately not inherited — see resolveApproval). */ readonly legacyName?: string; }; /** * Resolve approval mode and per-tool user policies from the execute-time * `AgentToolContext`. * * Missing context (or context with no settings and no `--auto-approve`) is * fail-closed: `always-ask` with an empty policy map — no user grant. When * settings are present, the configured `tools.approvalMode` is used (schema * default remains `yolo`). `--auto-approve` still forces `yolo`. * * Shared by `ExtensionToolWrapper.execute`, `refuseByWritePolicy`, * `mcpApprovalPreflight`, and eval prelude host calls so those sites cannot * drift. `ExtensionToolWrapper.execute` still inherits the runner's session * settings when the caller omits context, so a live session keeps its * configured (schema-default `yolo`) grant. */ export declare function resolveApprovalFromContext(context?: ApprovalContextSource | null): ResolvedExecuteTimeApproval; export interface ResolvedApproval { policy: ApprovalPolicy; tier: ToolTier; reason?: string; override: boolean; source?: "tool" | "user" | "mode" | "restriction"; /** * User-policy key that produced `source: "user"` (defaults to the tool name), or the keyed * sub-tool (an `xd://` device) a tool-owned or mode decision is about. */ policyKey?: string; } /** Ordering of capability tiers, least to most privileged. */ export declare const TIER_RANK: Readonly>; /** * Fold the per-target decisions of a multi-target write tool (`edit`, `ast_edit`): the first * `policy: "deny"` decision wins with its reason (a read-only URL target); otherwise the highest * tier, starting from "read". */ export declare function strictestApproval(decisions: Iterable): ToolApprovalDecision; /** * The folder a directory answer would grant: the one folder whose grant covers every target. * `undefined` when the targets span folders or include one no folder grant covers. */ export declare function approvalFolderCandidate(tool: ApprovalSubject, args: unknown): { key: string; label: string; } | undefined; /** * Record the grant an approved `always-ask-cached` prompt answered, keyed by the capability the * call resolved to: the folder for a directory answer, else every exact target. A tree target (a * recursive search) also records the folder grant it includes. */ export declare function recordApprovalGrant(tool: ApprovalSubject, args: unknown, grants: ApprovalGrants, capability: ToolTier, answer: { grant: "folder" | "target"; always: boolean; }): void; /** * Evaluate a tool's own approval declaration against `args` and return the * resulting capability tier, defaulting to `exec` when the tool omits an * approval. Unlike reading `tool.approval` directly, this runs function-valued * approvals — the write tool's `xd://` gate uses it to take a mounted device's * argument-dependent tier instead of falling back to `exec`. */ export declare function resolveToolTier(tool: ApprovalSubject, args: unknown, context?: AgentToolContext): ToolTier; /** `tools.restrictedFolders` as one call sees it. */ export interface ApprovalRestrictions { /** Folders where every action needs an explicit approval, whatever the mode, policy, or grant. */ restrictedFolders: readonly string[]; /** * Directory the call runs in: a tool that names no path is matched by it (or by its own `cwd` * argument, resolved against it), and relative configured folders resolve against it. */ cwd?: string; } /** The restrictions `settings` configure for calls running in `cwd`; `undefined` when none are. */ export declare function approvalRestrictions(settings: Settings | undefined, cwd: string | undefined): ApprovalRestrictions | undefined; /** * Resolve approval policy for a tool call. * * Resolution order: * 1. Tool `approval(args)` decision, defaulting to tier "exec" when omitted. * A decision may carry a `policyKey` — `tools.approval.` is then * the user override consulted instead of `tools.approval.`, with * the invoking tool's own policy as the fallback when the user set none for * the keyed sub-tool (e.g. an `xd://` device dispatch without a device * policy still honors `tools.approval.write`). * 2. User per-tool override, if set and valid. * 3. Active mode tier comparison. In `always-ask-cached`, a call whose * `approvalCacheTargets` are all granted in `grants` is allowed instead * (see {@link resolveCachedModeApproval}). * 4. A call touching a folder in `restrictions` turns any non-deny result into * a per-call prompt sourced `restriction` (see {@link restrictedFolderReason}). * * In yolo mode, override-based tool prompts are ignored; user `tools.approval` * settings remain authoritative. */ export declare function resolveApproval(tool: ApprovalSubject, args: unknown, mode: ApprovalMode, userConfig?: Record, grants?: ApprovalGrantStore, restrictions?: ApprovalRestrictions, context?: AgentToolContext): ResolvedApproval; /** * The prompt reason for a call that touches a restricted folder, or `undefined` when it touches * none. A restricted folder covers itself and everything below it; a recursive search rooted above * one reads into it, so it is covered too. Configured folders expand `~` and resolve relative to the * call's working directory; they are compared like grant keys, so a symlink or a differently cased * Windows path names the same folder and `/a/foo` never covers `/a/foobar`. */ export declare function restrictedFolderReason(tool: ApprovalSubject, args: unknown, restrictions: ApprovalRestrictions | undefined): string | undefined; /** * What an "approve for this session" answer may cover for a `resolved` prompt: the subject the * prompt names (the tool, or its `xd://` device) and a grant key that also pins the tier, so * approving a write never covers an exec call of the same tool. Undefined when the prompt must * stay per-call: only a prompt the approval mode raised is grantable, never a critical override * or an explicit `prompt` policy (the user's or the tool's own). */ export declare function sessionApprovalScope(resolved: ResolvedApproval, toolName: string): { subject: string; key: string; } | undefined; /** * Error for a resolved deny. Distinguishes tool-owned policy from user config. */ export declare function denyError(resolved: ResolvedApproval, toolName: string): Error; /** * Check if a tool call requires user approval. * * @throws Error if policy is 'deny' * @returns Object with required flag and optional reason for the prompt */ export declare function requiresApproval(tool: ApprovalSubject, args: unknown, mode: ApprovalMode, userConfig?: Record, context?: AgentToolContext): { required: boolean; reason?: string; }; export declare function truncateForPrompt(value: string, maxChars?: number): string; /** * Layout a decoded device or MCP argument payload: * - standard JSON layout with 2-space indentation * - multiline strings (manifests, scripts, policy documents) expanded into quoted blocks * - tabs sanitized to spaces */ export declare function formatApprovalArguments(args: unknown, options?: { showEmpty?: boolean; }): string[]; /** Format the approval prompt body shown to the user: the header, then the tool's own details. */ export declare function formatApprovalPrompt(tool: ApprovalSubject, args: unknown, reason?: string): string; /** * Ask the user to approve a call: the host's approval dialog when it has one (it gets the full * prompt and a compact one with the details summarized to a row, so the composer beneath it * keeps its room), else a select with the full prompt (RPC, ACP, extension hosts). `appendix` * lines follow the prompt on both. With a `sessionSubject`, both also offer to approve that * subject for the rest of the session; with `grants`, both offer the `always-ask-cached` grants * the call can record, and the prompt lists the folder a directory grant would cover. Anything * but an explicit approval is `deny`. */ export declare function requestApproval(ui: Pick, tool: ApprovalSubject, args: unknown, options?: { reason?: string; appendix?: readonly string[]; sessionSubject?: string; grants?: boolean; }): Promise; /** The approve-this-call-only label: "Approve", or "Approve Once" beside grants that remember more. */ export declare function approvalOnceLabel(prompt: Pick): string; /** * An approval prompt's choices as select-menu labels, for hosts without an approval dialog (RPC, * ACP, collab guests): each grant for this session, each grant across sessions, this call only, * the session answer when offered, and Deny. */ export declare function approvalChoiceLabels(prompt: Pick): string[]; /** The answer a label from {@link approvalChoiceLabels} stands for; anything else denies. */ export declare function approvalAnswerForChoice(prompt: Pick, choice: string | undefined): ExtensionApprovalAnswer;