/** * The "portable continue" brief: a bounded markdown reconstruction of a prior * session, rendered from the SessionRecord our own events already produce (see * core/session-record.ts). * * Why this exists rather than a vendor resume: `claude --resume` / `codex * resume` only work when that agent, on this machine, still holds that * conversation. H1 (SAP-2057) made us stop pretending otherwise; this is the * other half — for every row where the agent does NOT hold it, start a fresh * session seeded with what we recorded. Because the input is our normalized * record and the output is plain text, the same code path serves claude-code * (`--append-system-prompt`) and codex (`developer_instructions`), and would * serve a harness with no prompt flag at all via post-ready injection. * * HONESTY IS THE PRODUCT HERE. A brief that reads like restored memory is * worse than no brief: the agent will assert a file's contents or a command's * outcome it never saw. So the header says outright that this is a * reconstruction, `record.limitations` are spelled out, and every omission * (dropped turns, clamped text, capped file lists) is stated in the text * rather than silently applied. * * BUDGET. The brief is capped at `maxTokens` (default * {@link RESUME_BRIEF_DEFAULT_MAX_TOKENS}), estimated at * {@link CHARS_PER_TOKEN} characters per token — deliberately a crude * estimator, since the consumer is a system prompt with room to spare, not a * hard API limit, and a tokenizer dependency here would have to be right for * two different vendors' tokenizers anyway. What gets squeezed, in order: * * 1. turns, oldest-first — the newest are the ones that explain what is in * flight right now, and an old turn is the most verbose thing here per * unit of usefulness; * 2. the derived digests (files written, commands run). They go AFTER turns, * not before, because they are hard-capped ({@link MAX_FILES} / * {@link MAX_COMMANDS}) at a few hundred tokens for a whole session, * where each turn costs a comparable amount on its own — trading a * whole-session view for one more ancient turn is a bad deal. They can * also be recovered by looking at the repository, so this only bites * once every turn is already gone; * 3. only then the rolling summary, tail-clamped. It is squeezed last * because it is the one section that is a lossy fold of turns already * dropped in step 1: cutting it loses history nothing else carries. * * The honesty header and the identity block are never dropped — a brief * without them is a lie, not a shorter brief — so they act as a floor of * roughly {@link RESUME_BRIEF_MIN_TOKENS} tokens that a smaller `maxTokens` * cannot squeeze below. */ import type { SessionRecord, SessionRecordToolCall } from "../shared/types.js"; /** Default token ceiling for a brief — see the module header on the estimate. */ export declare const RESUME_BRIEF_DEFAULT_MAX_TOKENS = 6000; /** Turns considered before budgeting; the budget may keep fewer, never more. */ export declare const RESUME_BRIEF_DEFAULT_MAX_TURNS = 12; /** * Characters per token. Roughly right for English prose and code across both * vendors' tokenizers, and biased the safe way: real text averages slightly * more than 4 chars/token, so this over-counts and lands under the ceiling. */ export declare const CHARS_PER_TOKEN = 4; /** * Floor the budget can never squeeze below — the honesty header plus the * identity block. Approximate (it moves with the header's wording); it exists * so callers can reason about "what does maxTokens: 200 even mean" rather than * as an assertion about exact size. */ export declare const RESUME_BRIEF_MIN_TOKENS = 400; /** The workflow the prior session was bound to, resolved by the caller against * the live workflow registry (the record itself has no binding). */ export interface ResumeBriefWorkflow { name: string; path: string; definitionId: number | null; } export interface BuildResumeBriefOptions { /** Display title of the prior session, when a history row knew one. */ title?: string | null; /** Git branch the prior session was last on, when a history row knew one. */ gitBranch?: string | null; /** The prior session's bound workflow. */ workflow?: ResumeBriefWorkflow | null; /** * The rolling summary (`//summary.md`) when one was * ever produced. Absent, the brief degrades to the last N turns — which is * the normal case, since the summary is opt-in (see core/rolling-summary.ts). */ summary?: string | null; /** Token ceiling. Default {@link RESUME_BRIEF_DEFAULT_MAX_TOKENS}. */ maxTokens?: number; /** Newest turns considered. Default {@link RESUME_BRIEF_DEFAULT_MAX_TURNS}. */ maxTurns?: number; } /** The crude char-count estimate the budget is enforced against. */ export declare function estimateBriefTokens(text: string): number; /** * The tool's target as a human would name it: the file it edited, the command * it ran, or — failing both — a clamped slice of the raw input. * * `toolInput` is a string on the wire for both harnesses: claude-code's * normalizer stores `JSON.stringify(tool_input)` and codex's tailer stores the * function call's raw `arguments`, so a JSON parse covers both and a plain * string (an untruncated free-text argument) falls through to the raw slice. * Never throws on malformed input — a brief must render from whatever was * recorded, including a payload the collector truncated mid-JSON. */ export declare function describeToolTarget(call: SessionRecordToolCall, cwd: string | null): string | null; interface DerivedDigests { /** Files written, most-recently-touched first, with their touch counts. */ files: Array<{ display: string; count: number; }>; /** Total distinct files, which may exceed `files.length` after the cap. */ totalFiles: number; /** Distinct shell commands, most recent first. */ commands: string[]; totalCommands: number; } /** * Files touched and commands run, derived from `tool.call` inputs. Exported * for tests and because the same derivation is worth reusing anywhere that * needs "what did this session actually do to the tree". */ export declare function deriveDigests(record: SessionRecord): DerivedDigests; /** * Render a bounded brief for `record`. Pure and synchronous; the caller * supplies the rolling summary and any registry-only context (title, branch, * bound workflow) it can resolve. */ export declare function buildResumeBrief(record: SessionRecord, options?: BuildResumeBriefOptions): string; export {}; //# sourceMappingURL=resume-brief.d.ts.map