import { type MessagePart, type Relay, type RelayWebhookEvent } from "@relaymessenger/sdk"; import type { PiApprovals } from "@relaymessenger/pi"; /** * Relays a coding agent's own permission prompts to the people who own the * Relay agent, as a card in each owner's chat with the agent. * * The harness decides what needs a person's yes; this process only carries * the question and the answer. That is how every integration of a messenger * with a coding agent does it: Claude Code's channels relay the harness's * permission prompt and let the harness apply the verdict (Claude Code * channels reference, "Relay permission prompts"; saved at * _sources/approvals-inkbox-20260926/claude-code-channels-reference.md), and * Inkbox's Claude Code and Codex plugins forward each `can_use_tool` and * `requestApproval` to the human (inkbox-claude-code-plugin-sessions.py.txt * `_can_use_tool`; inkbox-codex-plugin-codex_client.py.txt * `_handle_server_request`). * * Only an owner answers. The official Telegram plugin sends the prompt only to * allowlisted DMs (claude-plugins-official-telegram-server.ts.txt); the * iMessage plugin sends it to the owner's own chat because "that authority is * the owner's alone" (claude-plugins-official-imessage-server.ts.txt). Relay * names the owners itself: `GET /v1/me` returns `owner_people` for the calling * Agent Token (Relay-Server server/src/me.ts, `app.get("/me")`). * * The card is a `rich_card` whose reply suggestions are the harness's * choices; a tap comes back as the owner's own message, a `suggestion_response` * carrying the choice's id and replying to the card. A Pi select with more * choices than a card holds is a single-choice `selection` instead, answered * by a `selection_response`. A tap from anyone else is not registered, and * the card stays open for an owner to answer. */ /** * How a person may answer, in the words every harness maps to. `choice` is an * option of a question that is neither an allow nor a deny (a Pi extension's * `select`). */ export type ApprovalDecision = "allow_once" | "allow_session" | "deny" | "choice"; /** One choice the harness offers, with the label the card shows. */ export interface ApprovalChoice { /** The harness's own id for the choice, handed back to it unchanged. */ id: string; label: string; decision: ApprovalDecision; } /** One permission prompt from a harness. */ export interface ApprovalRequest { /** Who is asking, e.g. "Claude Code". */ harness: string; /** The tool it wants to use, e.g. "Bash". */ tool: string; /** The card's title, one sentence; ` asks to use .` when absent. */ title?: string; /** The command or file the harness wants to use. */ summary: string; /** * A plain line under the command: Bash's own description of it, which * Claude Code's terminal prompt shows under the command too. */ note?: string; /** The full input as a person reads it. */ detail: string; /** * Whether `detail` holds anything `summary` and `note` do not: an input * field beyond the summarized value and the note. The card then shows * `detail` in place of them. */ extra: boolean; /** The harness's own choices, allow first. */ choices: readonly ApprovalChoice[]; /** * How long to wait for an answer. The harness's own timeout when it sets * one (Codex's `autoResolutionMs`); otherwise `APPROVAL_TIMEOUT_MS`. */ timeoutMs?: number; /** Stops the wait, for example when the turn is cancelled. */ signal?: AbortSignal; } /** How a prompt ended. With no `choice`, the harness is told no. */ export interface ApprovalOutcome { choice?: ApprovalChoice; /** The owner who answered. */ by?: string; reason: "answered" | "timeout" | "no_owner" | "unsent" | "aborted"; } /** * Inkbox waits 600 seconds for a person's answer and then treats the request * as not approved (`permission_timeout_s: float = 600.0`, * inkbox-claude-code-plugin-config.py.txt:108 and * inkbox-codex-plugin-config.py.txt:132). */ export declare const APPROVAL_TIMEOUT_MS = 600000; /** The one line the terminal shows when nobody can be asked. */ export declare const noOwnerLine: (request: Pick) => string; /** What the reply to the card says once a prompt has ended. */ export declare const outcomeLine: (outcome: ApprovalOutcome) => string; /** * The contract's limits (Relay-Server contracts/developer/openapi.yaml), the * card's from the SDK; a `SelectionPart` subtitle is 512 characters and an * id-bearing `SelectionOption` has a 24-character label and a 200-character id. */ export declare const CARD_LIMITS: { readonly title: 200; readonly description: 2000; readonly suggestions: 4; readonly label: 25; readonly replyId: 256; readonly selectionTitle: 60; readonly selectionSubtitle: 512; readonly selectionOptions: 25; readonly selectionLabel: 24; readonly selectionId: 200; }; /** The card's title: the harness's sentence, or ` asks to use .` */ export declare const cardTitle: (request: ApprovalRequest) => string; /** * The card's description, plain text as the app draws it (Relay-iOS * RelayRichCardRow.swift, `Text(description)`): the command and its note, or, * when the input holds more than them, the whole input as a person reads it. * Cut at the contract's 2,000 characters with a closing "…". */ export declare const cardDescription: (request: ApprovalRequest) => string | undefined; /** * The prefix of every reply id on an approval card. Relay stores a reply * suggestion's `id` as sent and returns it in the person's * `suggestion_response` (or `selection_response`), so a tap on any approval * card, even one a bridge process no longer holds (sent before a restart), * is known by its id alone and never becomes a person's turn. */ export declare const APPROVAL_REPLY_PREFIX = "relay-approval:"; /** Whether a reply id is one an approval card gave out. */ export declare const isApprovalReplyId: (id: string) => boolean; /** * The message part that asks: a `rich_card` with one reply suggestion per * choice, or, past the card's 4 suggestions, a single-choice `selection` of up * to 25 options. */ export declare const approvalPart: (request: ApprovalRequest) => MessagePart; /** The choice a reply id names, as `approvalPart` gave it out. */ export declare const choiceFor: (request: ApprovalRequest, id: string) => ApprovalChoice | undefined; /** * A tap on a card, as `message.received` carries it: the card message it * replies to, and the id of the reply the person chose. A card reply is plain * text then `suggestion_response`; a picker answer, plain text then * `selection_response` (Relay-Server contracts/developer/openapi.yaml, * `SuggestionResponsePart` and `SelectionResponsePart`). */ export declare const readAnswer: (event: RelayWebhookEvent) => { messageId: string; id: string; } | undefined; /** The Relay calls this needs: who owns the agent, and the chat with each owner. */ export type ApprovalClient = Pick; export interface OwnerApprovalsOptions { client: ApprovalClient; say(line: string): void; } /** * One per bridge process. `ask` sends the card and waits; `take` reads every * event first and keeps the answers to its own cards, so they never start a turn. */ export declare class OwnerApprovals { #private; constructor(options: OwnerApprovalsOptions); ask(request: ApprovalRequest): Promise; /** * Whether this event is an answer to one of this process's approval cards. * Such an event is handled here and must not start a turn. */ take(event: RelayWebhookEvent): Promise; } /** What a harness is told when a prompt ends without an allow (Inkbox's words, sessions.py.txt:1249). */ export declare const denialMessage: (outcome: ApprovalOutcome) => string; /** One line of a tool input, for the card's summary. */ export declare const inputSummary: (input: Record) => string; /** Whether an input says anything in a field other than `shown`, the ones the card draws. */ export declare const beyond: (input: Record, shown: readonly string[]) => boolean; /** * A tool input as its card shows it: the summarized value, Bash's own * description as the note, the readable detail, and whether the input holds * more. An input with no field to summarize is summarized whole. */ export declare const inputCard: (input: Record) => Pick; /** * The whole tool input as a person reads it, for the card's description: an Edit is its * file and a diff (a MultiEdit, a diff per edit), a Write its file and the * content, anything else one `name: value` line per field. */ export declare const inputDetail: (input: unknown) => string; /** * Pi's dialogs, answered by the owners. A confirm is Yes or No, an allow and * a deny; a select offers the extension's own options, each as it wrote it. * The card's first line is the extension's own title. */ export declare const piApprovals: (approvals: Pick) => PiApprovals; //# sourceMappingURL=approvals.d.ts.map