/** * exportBugReport / describeBugReport — a bug report IS the evidence. * * The usual bug report is a person's memory of a run: "it said the wrong * thing, I think it called the search tool twice". The run itself — the * timeline, the state, the chart, the narrative — is sitting right there in * the process and never leaves it. This turns that around: the report is the * run, packaged, with the prose attached. * * ## Two calls, because consent needs two * * 1. {@link describeBugReport} — "here is what would be sent." A manifest of * SELECTABLE UNITS: each conversation with its event and turn counts and * its size, each derived file, the redacted keys by name, the total, and * a loud warning with trim hints if it is too big. * 2. {@link exportBugReport} — "send exactly these." The reporter's ticked * unit ids come back as `include`, and the bundle carries only those. * * One call does work for a server-side reporter that has no human in front of * it; the two-call shape is what makes a browser consent dialog possible at * all, because a dialog cannot ask about a blob it has not measured. * * ## What is in the bundle (layout 2 — `manifest.manifestVersion`) * * | file | what it is | * |---|---| * | `manifest.json` | this manifest — always present, never a selectable unit | * | `envelope.json` | the run as a `RecordingEnvelope`: the archive contract, with the canon `{ snapshot, events, structure }` under `recording` | * | `recording.json` | the bare recording — ONLY when the envelope's run facts were not available, with the manifest naming the missing one | * | `conversations/.json` | one file per conversation, when there is more than one run | * | `conversation.json` | the readable transcript, derived from the events | * | `narrative.txt` | the narrative recorder's lines, when one was attached | * | `environment.json` | the HOST that ran it + the reporter's prose | * * The evidence file is named for what it is, and there is never more than one * of it: a bundle carrying both an envelope and a copy of the recording it * already contains would double the biggest file in the archive — and the * archive is store-only, so doubling is real bytes, against a ceiling a * reporter has to fit under. * * `environment.json` is the host and NOTHING that identifies a machine: Node * version, platform, architecture. **No username, no hostname, no working * directory, no environment variables, no file paths.** A bug report should not * be the way an internal directory layout leaves a company. The producer * versions it used to repeat now live where the archive contract stamps them, * in `envelope.json`'s `producer` — one fact, one place. (The manifest's own * `environment` block still prints both for the human reading the issue; it is * a summary, not a second archive.) * * ## Redaction is already done, and the manifest proves it * * The recording arrives ALREADY redacted: footprintjs scrubs at commit time * under the run's `RedactionPolicy`, so a redacted value was never in the * snapshot this reads. Nothing here scrubs anything — it would be too late to * matter and a second policy could only disagree with the first. What this * does do is LIST the redacted keys by name, derived from the placeholders * actually present in the evidence, so a human consenting to the bundle can * see which secrets were protected. A key that is not on that list was not * redacted, and the honest reading of an empty list is "no policy was set" — * which the manifest says in a note. * * @example The consent flow * ```ts * const manifest = describeBugReport(recording); * // …show manifest.units to the human; they tick some… * const report = exportBugReport(recording, { * include: ['conv-1', 'file-narrative', 'file-environment'], * title: 'Agent answered with a stale price', * stepsToReproduce: '1. ask for the price\n2. update it\n3. ask again', * expected: 'the new price', * actual: 'the old one', * }); * fs.writeFileSync(report.filename, report.zip); * ``` */ import type { BugReport, BugReportInput, BugReportManifest, DescribeBugReportOptions, ExportBugReportOptions } from './types.js'; /** * A size a human reads. KB under a megabyte, MB above it — a ceiling reported * as "0.0 MB" teaches nothing, and these strings are the whole content of a * refusal. */ export declare function formatBytes(bytes: number): string; /** * Measure a bug report before anything leaves — the consent step. * * Every unit is "selected" in the manifest this returns, because nothing has * been chosen yet: it is the offer, with sizes and counts attached, for a human * (or a policy) to narrow. Show `units` to the reporter; pass the ids they keep * to {@link exportBugReport} as `include`. * * Cheap enough to call on every dialog open: it serializes the files to measure * them, and throws them away. * * @param input a recording, a `recordRun` handle, a runner, or an array. * @param options size ceiling and a fixed timestamp. */ export declare function describeBugReport(input: BugReportInput, options?: DescribeBugReportOptions): BugReportManifest; /** * Build the bundle: the manifest, the named files, and a real zip of them. * * @param input a recording, a `recordRun` handle, a runner, or an array. * @param options the reporter's prose, plus `include` — the ids from * {@link describeBugReport} that the reporter consented to. * * @throws TypeError naming the unknown id when `include` names a unit that * does not exist, and naming the available conversations when the * selection would carry no evidence at all. */ export declare function exportBugReport(input: BugReportInput, options: ExportBugReportOptions): BugReport; /** `2026-08-11-agent-answered-with-a-stale-price.zip`. */ export declare function bundleFilename(title: string, createdAt: Date): string; /** Lower-case, ASCII, hyphenated, bounded — a filename, not a sentence. */ export declare function slugify(title: string): string;