import { type DeferredFeedback } from "./feedback-deferred.js"; import { type GitHubRepo } from "./registry-catalog.js"; /** * What feedback(submit) does when the elicitation gate cannot reach a human * (#991). * * The gate is the only approval channel the server has, and some clients * answer it themselves: the VS Code Claude Code extension auto-declines in * 15 to 75ms, before anything is rendered. The "too fast to be human" * detection catches that correctly, but until now the report died there. A * team ran into it eight times in a week and had to retype every report by * hand somewhere else. * * The fallback needs nothing from the client, which is the whole point: * * 1. The scrubbed payload is written to disk, in the same pending-feedback * store `npx ue-mcp feedback list/approve/discard` already reads. So the * report survives the session even if nobody clicks anything. * 2. A prefilled `github.com/.../issues/new?title=&body=` URL is handed * back. One click lands the user in the issue form with the body already * written, no auth, no elicitation, no CLI. * 3. A short confirmation token is written into the report file. If the * user says "yes, submit it" in plain text, they can read the token out * of the file and have the agent pass it back on a second * feedback(submit) call, which posts the exact stored bytes. * * Door 3 is a human-confirmation speed bump, not an authorization boundary: * an agent with filesystem access can read the token itself. It exists so a * user who WANTS to approve has a path that ends in a posted issue, and it is * deliberately the last of the three. Doors 1 and 2 need no trust at all. */ /** * Cap on the whole prefilled URL, measured AFTER percent-encoding. * * Browsers and GitHub both start truncating somewhere north of 8KB, and a * truncated query string produces a half-written issue body rather than an * error, which is worse than no link. 6000 leaves headroom for the origin, * the path, and any redirect GitHub adds on the way to the login wall. * * Measuring the encoded length is the part that matters: a body of 5000 plain * characters full of newlines and backticks encodes to well over 15000. */ export declare const MaxIssueUrlChars = 6000; export interface PrefilledIssueUrl { /** The URL, or null when even a body-less link would blow the cap. */ url: string | null; /** True when the body in the URL is a prefix of the real body. */ truncated: boolean; /** Encoded length of the URL that was produced, for diagnostics/tests. */ length: number; } /** * Build a prefilled new-issue URL that is guaranteed to fit under the cap. * * Full body if it fits. Otherwise the longest prefix that still fits once the * "rest of the report is on disk" pointer is appended. If not even that fits, * `url` is null and the caller tells the user to open a blank issue and paste * from the file, which beats handing out a link that silently loses half the * report. */ export declare function buildPrefilledIssueUrl(repo: GitHubRepo, title: string, body: string, reportPath?: string): PrefilledIssueUrl; export interface FallbackReport { /** Pending-store id. Also `npx ue-mcp feedback approve `. */ id: string; /** Human-readable markdown copy of the report, token at the top. */ path: string; /** JSON entry the CLI reads. */ jsonPath: string; /** Confirmation token, written into `path` and NEVER into a tool result. */ token: string; /** Prefilled issue URL, or null when the body cannot fit in one. */ url: string | null; /** True when the URL carries a prefix of the body rather than all of it. */ urlTruncated: boolean; } export interface FallbackInput { title: string; body: string; labels: string[]; repo: GitHubRepo; routing: string; project: string | null; author: "user" | "bot"; /** Why the elicitation gate could not be used. Recorded in the file. */ reason: string; } /** * Persist the payload and produce the click-through URL. * * Writing lands in the SAME store as `feedback.mode = "defer"`, on purpose: * the CLI review commands already understand it, so an unreachable * elicitation gate degrades into the deferred flow rather than into a new * parallel one nobody knows how to drain. */ export declare function writeFallbackReport(input: FallbackInput): FallbackReport; /** Look up a pending entry by its confirmation token. */ export declare function findByConfirmToken(token: string): DeferredFeedback | null; /** Drop the markdown copy that accompanies a pending entry, if any. */ export declare function deleteFallbackReport(id: string): void;