import { type ApprovalWaitResult } from './approval-waiters.js'; import type { OrgDaemon } from './daemon.js'; /** M5: `apr--<8 hex>` — one per approval request. */ export declare function newApprovalRequestId(): string; /** M5: default resolver when none is named. */ export declare const DEFAULT_RESOLVER = "human"; /** M5: a resolver name from the CLI/API — trimmed, 1..128 chars, no control * characters. Returns undefined for anything else (callers fall back to the * default or reject). */ export declare function normalizeResolver(raw: unknown): string | undefined; /** One queued tool-call approval request, per org, in OrgDaemon.approvals. */ export interface ApprovalEntry { roleId: string; action: string; /** Fingerprint of the tool call's actual arguments (e.g. the Bash command, * the WebFetch url) — see checkApproval. Distinguishes a * materially different call from one already approved/pending under the * same (roleId, action), so one human approval can't silently authorize * every future call to that tool. Optional only so pre-fix entries * loaded from an old approvals.json don't fail to parse. */ fingerprint?: string; question: string; ts: number; approved: boolean | null; /** M5: `apr--<8 hex>` — addresses exactly this request. */ requestId?: string; /** M5: the redacted argument summary `policy.decide` logged. */ input?: Record; /** M5: who resolved it (`human` by default). */ resolvedBy?: string; resolvedAt?: number; } export interface ApprovalResolveOpts { /** Who resolved it — stored as resolvedBy and put on the audit event. */ resolvedBy?: string; /** Resolve only this request; absent = every pending (role, action) entry. */ requestId?: string; } /** Discard every approval — pending or resolved — left over from a previous * run. Call on a fresh (non-resume) startOrg. * * approvals.json is keyed per-org, not per-run, and daemon.approvals is an * in-memory Map that starts empty in every fresh CLI process. A pending * approval queued by a role in a PREVIOUS run — never resolved before that * run ended — otherwise survives on disk forever: it looks "pending" to * anything reading the file directly (dashboard, status checks, even * `org approve`'s own live-delivery-then-fallback path), but the role that * requested it is gone and no live daemon will ever have a matching * in-memory record for it, so `org approve`'s live path always 404s with * "No pending approval found" and silently falls back to patching a ghost * entry nobody is listening for. A fresh start means every previous * approval is moot for this run. */ export declare function clearApprovalsForFreshStart(daemon: OrgDaemon, org: string): void; /** Check if an action requires human approval (beforeTool hook for guardrails). Returns * the approval decision: true = approved, false = denied, null = pending (requires human input). * * CRITICAL fix: the pending cache used to key ONLY on (role, action) — the tool's * bare name (e.g. "Bash"), never its actual arguments. Once a human approved ONE * call, `existing.approved !== null` short-circuited below and returned true for * EVERY future call to that tool by that role for the rest of the run, regardless * of what the new call's command/url/content actually was: approve role * "builder"'s `npm test`, and its very next Bash call — `rm -rf $HOME`, say — * was auto-approved with zero further human involvement. Same issue for repeated * WebFetch/WebSearch (exfiltration/SSRF) and org_complete. `input` (the tool's * actual call arguments, now threaded through from gatedCanUseTool/daemon.ts) is * fingerprinted (fingerprintAction, above) and folded into the cache key so a * materially different call always queues its OWN pending entry requiring fresh * human approval — only a literally identical repeat call may reuse a prior * decision, which is reasonable UX, not the bug. * * R5: serialized per-org via withApprovalLock() — concurrent checkApproval and * setApproval calls previously raced on this.approvals + approvals.json. */ export declare function checkApproval(daemon: OrgDaemon, org: string, role: string, rawAction: string, input?: Record): Promise; /** #553: when a request stays pending, whether (and how long) the call waits * for its resolution — asked with the normalized action, inside the lock. */ export type ApprovalWaitPlan = (action: string) => { timeoutMs: number; stillLive: () => boolean; } | undefined; /** #553: checkApproval plus the queued entry it matched or created. With * `planWait`, a still-pending request gets its waiter registered here, * inside the approvals lock, so setApproval cannot resolve it unseen. */ export declare function checkApprovalEntry(daemon: OrgDaemon, org: string, role: string, rawAction: string, input?: Record, planWait?: ApprovalWaitPlan): Promise<{ approved: boolean | null; entry?: ApprovalEntry; wait?: Promise; }>; /** #345: the --auto-approve entries no role of the org would ever gate — a * typo there would change nothing and leave the run waiting, so startOrg * refuses them. Returns the error message, or null when every entry is gated. */ export declare function unknownAutoApproveError(tools: string[], roles: Array<{ policy?: { approvalTools?: string[]; }; }>): string | null; /** #345: `org run --auto-approve a,b` — the bare action names to pre-approve * for the run (a `mcp__org__` prefix is dropped, as checkApproval does). */ export declare function parseAutoApproveFlag(raw: unknown): { tools: string[]; } | { error: string; }; /** #345: the line `org run` prints for the approval request a `question` * event carries (checkApproval emits one per queued request), naming the * commands that resolve it; null for any other event. Without it the * request only reached approvals.json and the dashboard, and a foreground * run waiting on it looked hung. */ export declare function approvalPendingNotice(org: string, e: { type: string; from?: string; data?: unknown; }): string | null; /** Approve or deny a pending action (called by dashboard or CLI). * R5: serialized per-org via withApprovalLock(). * * Now that checkApproval fingerprints each call, more than one distinct * pending entry can exist under the same (role, action) at once (e.g. two * different Bash commands both awaiting approval). The CLI/dashboard only * identify a request by (role, action), not by which specific command — so * resolve every currently-UNRESOLVED entry for that (role, action) rather * than picking one arbitrarily via .find(). This still means what it always * meant ("yes, let the queued Bash/WebFetch/... call(s) through"): a NEW, * still-different future call queues its own fresh pending entry and is * unaffected by this decision. */ export declare function setApproval(daemon: OrgDaemon, org: string, role: string, action: string, approved: boolean, opts?: ApprovalResolveOpts): Promise<{ ok: true; } | { ok: false; error: string; }>; //# sourceMappingURL=approvals.d.ts.map