zidane 5.8.4 → 5.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-CL4nT5Ti.d.ts.map +1 -1
- package/dist/chat.js +2 -2
- package/dist/{headless-AnwtKahq.js → headless-c7MDO-iF.js} +3 -3
- package/dist/{headless-AnwtKahq.js.map → headless-c7MDO-iF.js.map} +1 -1
- package/dist/headless.js +1 -1
- package/dist/index.js +5 -5
- package/dist/{login-9b_Q2DVg.js → login-DYQ6LgLP.js} +3 -3
- package/dist/{login-9b_Q2DVg.js.map → login-DYQ6LgLP.js.map} +1 -1
- package/dist/{mcp-Bp4tWsdM.js → mcp-adAkHM-0.js} +4 -1
- package/dist/mcp-adAkHM-0.js.map +1 -0
- package/dist/mcp.js +1 -1
- package/dist/{presets-J9EyUhvT.js → presets-6SoIBrzL.js} +2 -2
- package/dist/{presets-J9EyUhvT.js.map → presets-6SoIBrzL.js.map} +1 -1
- package/dist/presets.js +1 -1
- package/dist/{tools-DCFiAUel.js → tools-DOUtulS5.js} +2 -2
- package/dist/{tools-DCFiAUel.js.map → tools-DOUtulS5.js.map} +1 -1
- package/dist/tools.js +1 -1
- package/dist/{transcript-anchors-D-X067Sc.js → transcript-anchors-B2h3qt3S.js} +4 -4
- package/dist/{transcript-anchors-D-X067Sc.js.map → transcript-anchors-B2h3qt3S.js.map} +1 -1
- package/dist/tui.js +4 -4
- package/package.json +1 -1
- package/dist/mcp-Bp4tWsdM.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-9b_Q2DVg.js","names":[],"sources":["../src/compact/errors.ts","../src/compact/messages.ts","../src/compact/prompt.ts","../src/compact/compact.ts","../src/compact/restore.ts","../src/logger.ts","../src/mcp/oauth-callback.ts","../src/mcp/login.ts"],"sourcesContent":["/**\n * Typed errors thrown by the compaction helper.\n *\n * Lives in its own file so both the runner and the pure messages module\n * can import without circular dependencies.\n */\n\n/**\n * Raised when the caller's inputs make compaction meaningless before any\n * API call is attempted. Common cases:\n * - empty `turns`\n * - `keepTurns >= turns.length` (no older content to summarize)\n * - `'from'` / `'up_to'` anchor id not found in `turns`\n * - the resolved `toSummarize` slice has no text-bearing content\n *\n * Synchronous — thrown from `compactConversation()` before the provider\n * call so the caller can recover without a network round-trip.\n */\nexport class CompactInvalidInputError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CompactInvalidInputError'\n }\n}\n\n/**\n * Raised when the provider rejects the compaction request with\n * `prompt_too_long` (or an equivalent) and the head-truncation retry\n * budget has been exhausted. Callers can inspect `ptlRetries` to log\n * how far the retry loop got before giving up.\n */\nexport class CompactPromptTooLongError extends Error {\n constructor(message: string, public readonly ptlRetries: number) {\n super(message)\n this.name = 'CompactPromptTooLongError'\n }\n}\n","/**\n * Pure helpers for compaction — slicing, image stripping, head-truncation\n * on `prompt_too_long`, and the synthetic-turn builder.\n *\n * Everything in this file is total and side-effect-free. Inputs are never\n * mutated; helpers return fresh arrays whenever they modify content.\n */\n\nimport type {\n SessionContentBlock,\n SessionTurn,\n ToolResultContent,\n TurnUsage,\n} from '../types'\nimport { CompactInvalidInputError } from './errors'\n\n/**\n * What to summarize and what to preserve verbatim.\n *\n * - `'full'` — summarize everything, no preserved tail.\n * - `'tail'` — summarize everything before the last `keepTurns` turns.\n * - `{ kind: 'from', turnId }` — summarize from the anchor turn onward.\n * - `{ kind: 'up_to', turnId }` — summarize up to (and including) the anchor.\n */\nexport type CompactScope\n = | 'full'\n | 'tail'\n | { kind: 'from', turnId: string }\n | { kind: 'up_to', turnId: string }\n\nexport interface CompactionSlice {\n /** The portion of the conversation that will be summarized. */\n toSummarize: readonly SessionTurn[]\n /** The portion that stays verbatim in the post-compact history. */\n preserved: readonly SessionTurn[]\n}\n\n/**\n * Partition `turns` into `(toSummarize, preserved)` according to `scope`.\n *\n * Throws {@link CompactInvalidInputError} on degenerate inputs so the\n * caller doesn't pay for a doomed provider call:\n * - empty `turns`\n * - `scope: 'tail'` with `keepTurns >= turns.length` (nothing to summarize)\n * - `scope: { from | up_to }` with an anchor id that isn't in `turns`\n * - the resulting `toSummarize` slice contains no text-bearing content\n * (only system turns or empty content)\n *\n * Pure. Returns references to the original `SessionTurn` objects — the\n * caller can compare by identity (=== Object.is) to confirm.\n */\nexport function sliceForCompaction(\n turns: readonly SessionTurn[],\n scope: CompactScope,\n keepTurns: number,\n): CompactionSlice {\n if (turns.length === 0)\n throw new CompactInvalidInputError('No turns to compact.')\n\n let toSummarize: readonly SessionTurn[]\n let preserved: readonly SessionTurn[]\n\n if (scope === 'full') {\n toSummarize = turns\n preserved = []\n }\n else if (scope === 'tail') {\n const keep = Math.max(0, keepTurns)\n if (keep >= turns.length) {\n throw new CompactInvalidInputError(\n `Nothing to compact: keepTurns (${keep}) covers the entire conversation (${turns.length} turns).`,\n )\n }\n const safeCut = findSafeRoundBoundary(turns, turns.length - keep)\n toSummarize = turns.slice(0, safeCut)\n preserved = turns.slice(safeCut)\n }\n else if (scope.kind === 'from') {\n const idx = turns.findIndex(t => t.id === scope.turnId)\n if (idx < 0)\n throw new CompactInvalidInputError(`Anchor turn not found: \"${scope.turnId}\".`)\n // 'from' summarizes the recent portion (anchor onward); everything\n // before stays verbatim. Walk the anchor BACKWARD to the start of\n // the round containing it so neither half ends mid-round (avoids\n // orphan `tool_use` in `preserved` AND orphan `tool_result` at\n // the head of `toSummarize`).\n const safeIdx = findSafeRoundBoundary(turns, idx)\n preserved = turns.slice(0, safeIdx)\n toSummarize = turns.slice(safeIdx)\n }\n else {\n // 'up_to' summarizes the older portion (up to and including the anchor);\n // everything after stays verbatim. Same `tool_use ↔ tool_result`\n // adjacency rule as 'tail': cut backward if the proposed boundary\n // would orphan a `tool_use` at the end of `toSummarize`.\n const idx = turns.findIndex(t => t.id === scope.turnId)\n if (idx < 0)\n throw new CompactInvalidInputError(`Anchor turn not found: \"${scope.turnId}\".`)\n const safeCut = findSafeRoundBoundary(turns, idx + 1)\n toSummarize = turns.slice(0, safeCut)\n preserved = turns.slice(safeCut)\n }\n\n if (toSummarize.length === 0)\n throw new CompactInvalidInputError('Compaction scope resolved to zero turns.')\n if (!hasTextBearingContent(toSummarize))\n throw new CompactInvalidInputError('Compaction scope contains no text-bearing turns to summarize.')\n\n return { toSummarize, preserved }\n}\n\n/**\n * Replace every image block in `turns` with a `[image]` text marker.\n *\n * Covers two shapes:\n * - Top-level `{ type: 'image', ... }` content blocks on user turns.\n * - Image entries inside `tool_result.output` array form (multimodal\n * tool results — e.g. an MCP browser screenshot).\n *\n * Unconditional by design: even on vision-capable models, the summary\n * call doesn't benefit from raw image bytes (the model can't refer to\n * them after the summary lands), and stripping uniformly avoids\n * `prompt_too_long` on image-heavy sessions.\n *\n * Returns a fresh array; input turns / blocks are never mutated.\n */\nexport function stripImagesFromTurns(turns: readonly SessionTurn[]): SessionTurn[] {\n return turns.map(turn => stripImagesFromTurn(turn))\n}\n\nfunction stripImagesFromTurn(turn: SessionTurn): SessionTurn {\n let touched = false\n const nextContent: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'image') {\n touched = true\n nextContent.push({ type: 'text', text: '[image]' })\n continue\n }\n if (block.type === 'tool_result' && Array.isArray(block.output)) {\n const flat = stripImagesFromToolResult(block.output)\n if (flat) {\n touched = true\n nextContent.push({ ...block, output: flat })\n continue\n }\n }\n nextContent.push(block)\n }\n return touched ? { ...turn, content: nextContent } : turn\n}\n\n/**\n * Return a fresh `ToolResultContent[]` with images flattened to `[image]`\n * text placeholders, or `null` when no image blocks were present (caller\n * keeps the original input). Returning a new mutable array — never the\n * input — keeps the `tool_result.output` slot's mutable type contract\n * satisfied without leaky `as`-casts at the call site.\n */\nfunction stripImagesFromToolResult(parts: readonly ToolResultContent[]): ToolResultContent[] | null {\n let touched = false\n const out: ToolResultContent[] = []\n for (const part of parts) {\n if (part.type === 'image') {\n touched = true\n out.push({ type: 'text', text: '[image]' })\n }\n else {\n out.push(part)\n }\n }\n return touched ? out : null\n}\n\n/**\n * Drop the oldest \"round\" from `turns` and return a fresh array. Used by\n * the PTL retry path to shrink the prompt one round at a time.\n *\n * A round is a contiguous `[user, assistant?, tool_results?]` group. The\n * function walks forward from index 0, advances through the user turn\n * and any trailing assistant + tool-result turns belonging to the same\n * exchange, and returns the remainder.\n *\n * Adjacency-safe: when the oldest user turn carries `tool_result` blocks\n * answering an assistant turn ahead of it (rare — happens during\n * resume), the function keeps walking until the next clean boundary so\n * the resulting array still respects every provider's `tool_use ↔\n * tool_result` adjacency rule.\n *\n * Returns `turns` unchanged when only one round (or less) remains — the\n * caller is expected to interpret that as \"cannot shrink further\" and\n * give up the retry loop.\n */\nexport function truncateHeadForPtlRetry(turns: readonly SessionTurn[]): SessionTurn[] {\n if (turns.length <= 1)\n return turns.slice()\n\n // Find the first turn that opens a clean conversational round we can\n // drop ending on — start with the first `user` turn (it's the natural\n // start-of-round marker).\n const firstUserIdx = turns.findIndex(t => t.role === 'user')\n if (firstUserIdx < 0)\n return turns.slice()\n\n // Skip past every turn that belongs to this round: the user turn\n // itself, the assistant reply (if any), and the next user turn whose\n // content is *only* tool_result blocks (an immediate follow-up that\n // closes out tool calls). Stop when we reach the next round-opening\n // user turn (i.e. one that carries non-tool_result content).\n let cursor = firstUserIdx + 1\n while (cursor < turns.length) {\n const turn = turns[cursor]\n if (turn.role === 'assistant') {\n cursor++\n continue\n }\n if (turn.role === 'user' && isToolResultsOnlyTurn(turn)) {\n cursor++\n continue\n }\n break\n }\n\n // If the cursor walked all the way to the end, refuse to truncate —\n // the remaining slice would be empty, which is never useful.\n if (cursor >= turns.length)\n return turns.slice()\n\n return turns.slice(cursor)\n}\n\nfunction isToolResultsOnlyTurn(turn: SessionTurn): boolean {\n if (turn.content.length === 0)\n return false\n return turn.content.every(block => block.type === 'tool_result')\n}\n\n/**\n * Walk `proposedCut` backward to the nearest position where splitting at\n * that index produces a round-boundary-clean partition — i.e. neither\n * half breaks the `tool_use ↔ tool_result` adjacency rule that every\n * provider (most strictly Anthropic) enforces.\n *\n * The hazardous case: the proposed cut lands BETWEEN an assistant turn\n * carrying `tool_call` blocks and the user turn carrying the matching\n * `tool_result` blocks. Then `toSummarize` ends with an orphan\n * `tool_use` (provider 400 on the summarization request) AND `preserved`\n * starts with an orphan `tool_result` (provider 400 on the next live\n * agent run against the wire-level cutoff output).\n *\n * Algorithm: keep walking `cut` backward as long as `turns[cut - 1]`\n * is an assistant turn with at least one `tool_call` block. The walk\n * stops when:\n * - we reach `cut = 0` (slice would be empty; caller's existing\n * \"scope resolved to zero turns\" guard handles it), or\n * - the trailing turn is user-role (clean — model emits no pending\n * tool_use from user turns), or\n * - the trailing turn is assistant text without tool_use (clean —\n * text-only response is a complete round).\n *\n * Returning the adjusted cut over-preserves the tail relative to the\n * caller's request — `keepTurns` is interpreted as the MINIMUM number\n * of turns kept verbatim, not the exact count.\n */\nfunction findSafeRoundBoundary(turns: readonly SessionTurn[], proposedCut: number): number {\n let cut = Math.max(0, Math.min(turns.length, proposedCut))\n while (cut > 0 && hasPendingToolUse(turns[cut - 1]))\n cut--\n return cut\n}\n\n/** Does this turn end with any unanswered `tool_use` blocks? */\nfunction hasPendingToolUse(turn: SessionTurn): boolean {\n if (turn.role !== 'assistant')\n return false\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n return true\n }\n return false\n}\n\nfunction hasTextBearingContent(turns: readonly SessionTurn[]): boolean {\n for (const turn of turns) {\n if (turn.role === 'system')\n continue\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim().length > 0)\n return true\n if (block.type === 'tool_call' || block.type === 'tool_result')\n return true\n }\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Summary turn builder\n// ---------------------------------------------------------------------------\n\n/**\n * Maximum length of an anchor turn's textual preview, in characters. Long\n * enough to give the model recognizable context (the first paragraph of\n * a typical user message), short enough that it doesn't blow the\n * cache-stability invariant for the prompt prefix.\n */\nexport const ANCHOR_PREVIEW_MAX_CHARS = 200\n\n/**\n * Extract the first ~200 chars of text-bearing content from a turn — the\n * preview surfaced in `from` / `up_to` direction prompts so the model\n * knows where the slice begins.\n */\nexport function anchorPreviewFor(turn: SessionTurn): string {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim().length > 0) {\n const flat = block.text.replace(/\\s+/g, ' ').trim()\n return flat.length > ANCHOR_PREVIEW_MAX_CHARS\n ? `${flat.slice(0, ANCHOR_PREVIEW_MAX_CHARS - 1)}…`\n : flat\n }\n }\n return '(no preview available)'\n}\n\n/**\n * Input shape for {@link summaryToTurn}. Designed to align with the\n * fields of `CompactResult` so a caller can spread the runner's output\n * with a single `replacesTurnIds` rename — no field-by-field unpacking\n * required.\n *\n * Primitive shape (no `CompactResult` import) keeps this module free of\n * dependencies on the runner — `compact.ts` imports from here, not the\n * other way around.\n */\nexport interface SummaryToTurnInput {\n /** Summary text — typically `CompactResult.summary`. */\n summary: string\n /** Turn ids being replaced — typically `CompactResult.summarizedTurnIds`. */\n replacesTurnIds: readonly string[]\n /** Model id that produced the summary. */\n model: string\n /** Token usage from the summary call. */\n usage: TurnUsage\n /** Defaults to `Date.now()` when omitted. */\n compactedAt?: number\n}\n\n/**\n * Build a synthetic `SessionTurn` carrying a single `compact-summary`\n * block, ready to append to a session.\n *\n * The turn's role is `'user'` so it sits at a conversational boundary\n * the way the model expects. The caller is responsible for\n * `session.appendTurns([turn])`. The id is freshly generated via\n * `crypto.randomUUID()` so collisions are statistically impossible.\n *\n * Typical use after running `compactConversation`:\n *\n * ```ts\n * const result = await compactConversation({ provider, turns })\n * const turn = summaryToTurn({\n * summary: result.summary,\n * replacesTurnIds: result.summarizedTurnIds,\n * model: result.model,\n * usage: result.usage,\n * })\n * await session.appendTurns([turn])\n * ```\n */\nexport function summaryToTurn(input: SummaryToTurnInput): SessionTurn {\n const compactedAt = input.compactedAt ?? Date.now()\n return {\n id: crypto.randomUUID(),\n role: 'user',\n content: [{\n type: 'compact-summary',\n replacesTurnIds: input.replacesTurnIds,\n summary: input.summary,\n model: input.model,\n usage: input.usage,\n compactedAt,\n }],\n createdAt: compactedAt,\n }\n}\n","/**\n * Pure prompt builders for conversation compaction.\n *\n * The builders produce the **system prompt** for a no-tools summary call.\n * They are total functions of `(direction, anchorPreview?)` and produce\n * byte-stable output for the same inputs — that's load-bearing for the\n * provider's prompt cache: repeated compactions in the same host process\n * share the same prefix and read cache instead of writing it.\n *\n * Inspired by Claude Code's `services/compact/prompt.ts` — same 9-section\n * scaffold, same `<analysis> + <summary>` envelope, same no-tools\n * preamble. Adapted to zidane-specific wording (no \"Claude\" references)\n * and trimmed of the bits that don't apply (no `marble_origami`-style\n * query sources, no compaction-fingerprint header).\n */\n\n/** Identifier for the section of the conversation being summarized. */\nexport type CompactDirection = 'full' | 'tail' | 'from' | 'up_to'\n\nexport interface CompactPromptOptions {\n direction: CompactDirection\n /**\n * Short preview of the anchor turn's text — only used by `'from'` and\n * `'up_to'`. Pass the last ~200 chars of the anchor turn so the model\n * has a recognizable handle on where the slice begins / ends. Empty /\n * undefined for `'full'` and `'tail'`.\n */\n anchorPreview?: string\n}\n\n/**\n * Function shape for callers that want to swap in a domain-specific\n * summary prompt (security review handoff, support-ticket continuation,\n * etc.) without touching the runner. Default: {@link buildCompactPrompt}.\n */\nexport type CompactPromptBuilder = (opts: CompactPromptOptions) => string\n\n// ---------------------------------------------------------------------------\n// Composable blocks — each one is a frozen string for cache-stability and\n// quoted verbatim from inside the named builders below. Exported so\n// callers building custom prompts can stitch them together.\n// ---------------------------------------------------------------------------\n\n/**\n * No-tools guard. The runner sends `tools: []` to the provider already,\n * but some models still hallucinate tool-call intent on a long\n * conversation. The prose guard is cheap insurance.\n */\nexport const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.\n\n- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.\n- You already have all the context you need in the conversation above.\n- Tool calls will be REJECTED and will waste your only turn — you will fail the task.\n- Your entire response must be plain text: an <analysis> block followed by a <summary> block.`\n\n/**\n * Body shared by every direction. Lays out the 9-section scaffold,\n * mirrors Claude Code's `BASE_COMPACT_PROMPT` so a model already trained\n * on the layout produces the same shape.\n */\nexport const BASE_INSTRUCTIONS = `Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.\n\nThis summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.\n\nBefore providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:\n\n1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:\n - The user's explicit requests and intents\n - Your approach to addressing the user's requests\n - Key decisions, technical concepts and code patterns\n - Specific details like file names, full code snippets, function signatures, file edits\n - Errors that you ran into and how you fixed them\n - Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.\n2. Double-check for technical accuracy and completeness.\n\nYour summary, wrapped in <summary> tags, must include the following sections:\n\n1. Primary Request and Intent\n2. Key Technical Concepts\n3. Files and Code Sections (with paths; include code snippets only when load-bearing)\n4. Errors and fixes\n5. Problem Solving\n6. All user messages (list ALL non-tool user messages, verbatim)\n7. Pending Tasks\n8. Current Work\n9. Optional Next Step (include direct quotes from the most recent conversation when relevant)`\n\n/** Trailer prompting the model to begin. Same on every direction. */\nexport const TRAILER = 'Provide your <analysis> and <summary> now.'\n\n// ---------------------------------------------------------------------------\n// Direction blurbs — the only knob that varies between builders. Each is\n// frozen and concise so cache-key drift is impossible between calls with\n// the same direction.\n// ---------------------------------------------------------------------------\n\nconst FULL_BLURB = `## Scope\nThe conversation above is being summarized in full. Capture every section the user might need to resume from a fresh context.`\n\nconst TAIL_BLURB = `## Scope\nSummarize the conversation above. The most recent turns will be preserved verbatim alongside your summary, so prioritize older context that would otherwise be lost.`\n\nconst FROM_BLURB = `## Scope\nSummarize the conversation FROM the marked anchor onward (the recent portion). Everything before the anchor will be preserved verbatim.\n\nAnchor turn (preview):\n%ANCHOR_PREVIEW%`\n\nconst UP_TO_BLURB = `## Scope\nSummarize the conversation UP TO the marked anchor (the older portion). Everything from the anchor onward will be preserved verbatim — your summary's job is to compress the prior context the user can no longer scroll back to.\n\nAnchor turn (preview):\n%ANCHOR_PREVIEW%`\n\n// ---------------------------------------------------------------------------\n// Named builders — discoverable, single-purpose, cache-stable.\n// ---------------------------------------------------------------------------\n\n/** Compose the full prompt with a custom direction-blurb. Internal helper. */\nfunction compose(blurb: string): string {\n return [NO_TOOLS_PREAMBLE, BASE_INSTRUCTIONS, blurb, TRAILER].join('\\n\\n')\n}\n\nexport function buildFullCompactPrompt(): string {\n return compose(FULL_BLURB)\n}\n\nexport function buildTailCompactPrompt(): string {\n return compose(TAIL_BLURB)\n}\n\nexport function buildFromCompactPrompt(anchorPreview: string): string {\n return compose(FROM_BLURB.replace('%ANCHOR_PREVIEW%', anchorPreview))\n}\n\nexport function buildUpToCompactPrompt(anchorPreview: string): string {\n return compose(UP_TO_BLURB.replace('%ANCHOR_PREVIEW%', anchorPreview))\n}\n\n/**\n * Default public builder. Dispatches by direction to the four named\n * builders. Throws when `from` / `up_to` are passed without an\n * `anchorPreview` — those scopes only make sense with an anchor and a\n * silent fallback would produce a prompt that doesn't tell the model\n * where the slice begins.\n */\nexport const buildCompactPrompt: CompactPromptBuilder = (opts) => {\n switch (opts.direction) {\n case 'full':\n return buildFullCompactPrompt()\n case 'tail':\n return buildTailCompactPrompt()\n case 'from': {\n const preview = opts.anchorPreview ?? ''\n if (preview.length === 0)\n throw new Error('buildCompactPrompt: `anchorPreview` is required for direction \"from\".')\n return buildFromCompactPrompt(preview)\n }\n case 'up_to': {\n const preview = opts.anchorPreview ?? ''\n if (preview.length === 0)\n throw new Error('buildCompactPrompt: `anchorPreview` is required for direction \"up_to\".')\n return buildUpToCompactPrompt(preview)\n }\n }\n}\n","/**\n * `compactConversation` — drive a one-shot summary call against a\n * provider and return a structured envelope describing what was\n * summarized and what stays verbatim.\n *\n * This is the harness primitive. It does not mutate the session, does\n * not own re-entrancy guards, does not enforce a circuit breaker — those\n * belong to whoever wires compaction into a control loop (the agent\n * loop's autocompact trigger, the TUI's session-details modal action,\n * an SDK consumer's batch job).\n *\n * Architecture: mirrors {@link generateSessionTitle} verbatim. The\n * provider's `stream()` is the one-shot completion primitive zidane\n * already exposes; this module reuses it directly instead of layering\n * a second abstraction on top.\n *\n * Caching: the system prompt is byte-stable per `(direction,\n * anchorPreview)` pair (see `./prompt.ts`). Repeated compactions in the\n * same host process share that prefix and read provider cache instead\n * of writing it.\n */\n\nimport type { Provider } from '../providers'\nimport type {\n SessionContentBlock,\n SessionMessage,\n SessionTurn,\n ThinkingLevel,\n TurnUsage,\n} from '../types'\nimport type { CompactionSlice, CompactScope } from './messages'\nimport type { CompactPromptBuilder } from './prompt'\nimport { ensureEndsWithUserMessage, ensureToolResultPairing } from '../session/messages'\nimport { toolOutputByteLength } from '../types'\nimport { CompactPromptTooLongError } from './errors'\nimport {\n anchorPreviewFor,\n sliceForCompaction,\n stripImagesFromTurns,\n truncateHeadForPtlRetry,\n} from './messages'\nimport { buildCompactPrompt } from './prompt'\nimport { utf8ByteLength } from './utils'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface CompactOptions {\n /** Provider used for the summary call. Called with empty tools list. */\n provider: Provider\n /** Conversation to compact, in chronological order. */\n turns: readonly SessionTurn[]\n /**\n * What to summarize. Default: `'tail'` — summarize everything before\n * the last `keepTurns` turns. See {@link CompactScope}.\n */\n scope?: CompactScope\n /**\n * Trailing turns left untouched when `scope: 'tail'`. Default: 4.\n * Matches `AgentBehavior.compactKeepTurns` so a host that uses both\n * mechanisms shares one knob.\n */\n keepTurns?: number\n /** Model id used for the summary call. Default: `provider.meta.defaultModel`. */\n model?: string\n /**\n * Maximum tokens the summary itself can occupy. Default: 20_000 — the\n * p99.99 of summary output size in Claude Code's `COMPACT_MAX_OUTPUT`.\n * Reasonable for any model with a 200k+ window.\n */\n maxOutputTokens?: number\n /** Optional reasoning level. Default: `'off'` — summarization rarely benefits from thinking. */\n thinking?: ThinkingLevel\n /** Optional cancellation signal forwarded to `provider.stream()`. */\n signal?: AbortSignal\n /**\n * Retries with head-truncation when the provider reports\n * `prompt_too_long`. Each retry drops the oldest conversational round\n * before re-issuing the call. Default: 3. Set 0 to fail-fast.\n */\n maxPtlRetries?: number\n /**\n * Optional builder override. The default builder dispatches by\n * direction to the named builders in `./prompt.ts`; pass a custom\n * function to swap in a domain-specific summary prompt.\n */\n prompt?: CompactPromptBuilder\n /**\n * Lifecycle hook for observability. Fires once per provider call,\n * including retries. The `kind` field tells you whether the call is\n * the initial attempt, a head-truncated retry, or a transient-error\n * retry — useful for surfacing progress in a UI spinner.\n */\n onAttempt?: (event: { attempt: number, kind: 'initial' | 'ptl-retry' | 'transient-retry' }) => void\n}\n\n/**\n * Directive appended as the trailing user message when the conversation\n * to summarize ends with an assistant turn. Mirrors the system-prompt\n * TRAILER text so the model gets the same imperative cue from both\n * surfaces — system instruction + final user message.\n *\n * Compaction-specific: paired with the compaction system prompt. Other\n * call-sites of {@link ensureEndsWithUserMessage} pass their own directive\n * (or the neutral `DEFAULT_USER_TAIL_DIRECTIVE`) so unrelated codepaths\n * don't inject a \"give me a summary\" cue into a regular turn.\n */\nconst SUMMARY_USER_DIRECTIVE = 'Provide your <analysis> and <summary> now.'\n\nexport interface CompactResult {\n /** The summary text, with any `<analysis>` block stripped. */\n summary: string\n /** Token usage from the (last successful) summary call. */\n usage: TurnUsage\n /** Model id used to produce the summary. */\n model: string\n /** Number of `prompt_too_long` retries that fired before success. 0 on first-try success. */\n ptlRetries: number\n /**\n * Turn ids actually covered by the summary — i.e., the ids of turns\n * that made it to the provider. **PTL-retry safe**: when head-truncation\n * shrank the scope, only the surviving (post-truncation) turn ids\n * appear here. Drives `summaryToTurn`'s `replacesTurnIds`; the wire-\n * level cutoff in `applyCompactSummaryCutoff` reads the same field\n * from the persisted marker.\n */\n summarizedTurnIds: readonly string[]\n /**\n * Turn ids dropped by PTL head-truncation before reaching the\n * provider. **Empty on first-try success.** These turns are NOT\n * covered by the summary — callers should either leave them in the\n * conversation history (they stay visible to the model verbatim,\n * since the id-based cutoff only elides ids the marker explicitly\n * claims) or surface a \"compaction lost N turns of context\" warning.\n *\n * The harness keeps these in the wire-level conversation by default;\n * the safety contract is \"the summary describes exactly what's in\n * `summarizedTurnIds` — nothing more, nothing less\".\n */\n droppedDueToPtl: readonly string[]\n /** Turns left untouched (the preserved tail / verbatim slice). */\n preservedTurns: readonly SessionTurn[]\n /** Byte length of turns actually summarized (post-PTL-truncation). */\n beforeBytes: number\n /** UTF-8 byte length of the summary text (rough \"after\" measure). */\n afterBytes: number\n}\n\nexport { CompactInvalidInputError, CompactPromptTooLongError } from './errors'\nexport type { CompactionSlice, CompactScope, SummaryToTurnInput } from './messages'\nexport type { CompactDirection, CompactPromptBuilder, CompactPromptOptions } from './prompt'\n\n// ---------------------------------------------------------------------------\n// Defaults — locked constants so behavior is predictable across host setups.\n// ---------------------------------------------------------------------------\n\n/** Default `keepTurns` for `scope: 'tail'`. Matches `AgentBehavior.compactKeepTurns`. */\nconst DEFAULT_KEEP_TURNS = 4\n\n/** Default max output tokens for the summary call. */\nconst DEFAULT_MAX_OUTPUT_TOKENS = 20_000\n\n/** Default PTL retry budget. */\nconst DEFAULT_MAX_PTL_RETRIES = 3\n\n/** Maximum transient-error retries before giving up. Independent of PTL. */\nconst TRANSIENT_RETRY_BUDGET = 2\n\n// ---------------------------------------------------------------------------\n// Runner\n// ---------------------------------------------------------------------------\n\nexport async function compactConversation(opts: CompactOptions): Promise<CompactResult> {\n // ---- Phase 1: pure preflight (no API call) -----------------------------\n // Throws CompactInvalidInputError on degenerate inputs so the caller can\n // bail without spending a network round-trip on a doomed request.\n const slice = sliceForCompaction(\n opts.turns,\n opts.scope ?? 'tail',\n opts.keepTurns ?? DEFAULT_KEEP_TURNS,\n )\n\n const direction = scopeToDirection(opts.scope ?? 'tail')\n const anchorPreview = direction === 'from' || direction === 'up_to'\n ? anchorPreviewFor(anchorTurnFor(slice, opts.scope!))\n : undefined\n\n const builder = opts.prompt ?? buildCompactPrompt\n const systemPrompt = builder({\n direction,\n ...(anchorPreview !== undefined ? { anchorPreview } : {}),\n })\n\n const model = opts.model ?? opts.provider.meta.defaultModel\n const maxOutputTokens = opts.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS\n const maxPtlRetries = Math.max(0, opts.maxPtlRetries ?? DEFAULT_MAX_PTL_RETRIES)\n\n // ---- Phase 2: retry-aware provider call --------------------------------\n // Strips images once up-front (cheap, deterministic) so each retry\n // doesn't re-flatten the same blocks.\n let workingTurns: readonly SessionTurn[] = stripImagesFromTurns(slice.toSummarize)\n let ptlRetries = 0\n let transientRetries = 0\n let attempt = 0\n\n while (true) {\n attempt++\n const kind: 'initial' | 'ptl-retry' | 'transient-retry'\n = ptlRetries > 0 ? 'ptl-retry' : transientRetries > 0 ? 'transient-retry' : 'initial'\n opts.onAttempt?.({ attempt, kind })\n\n try {\n // Build wire messages, then run the same defensive pairing pass\n // the agent loop uses on every live turn (see `applyPairingRepair`\n // in `src/loop.ts`). A session can contain orphan `tool_use` blocks\n // even after `sliceForCompaction`'s round-boundary walk: PTL\n // head-truncation shrinks the working set after the first attempt\n // (which can lop the head off an in-flight round), prior-loop\n // crashes can leave dangling tool_use ids in the persisted history,\n // and `turnsToMessages` itself can drop the only `text` block on\n // an assistant turn carrying signed thinking that has no signature.\n // Without this pass the summarizer fires a request the provider 400s\n // on with `'tool_use' ids were found without 'tool_result' blocks\n // immediately after`, which surfaces to the user as\n // \"compaction failed\" with a raw provider message.\n const paired = ensureToolResultPairing(turnsToMessages(workingTurns))\n const messages = ensureEndsWithUserMessage(paired, opts.provider, SUMMARY_USER_DIRECTIVE)\n const { summary, usage } = await runOnce({\n provider: opts.provider,\n model,\n system: systemPrompt,\n messages,\n maxTokens: maxOutputTokens,\n ...(opts.thinking !== undefined ? { thinking: opts.thinking } : {}),\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n })\n\n // PTL retries shrink the scope — the marker should ONLY claim\n // ownership of turns that actually fed the summary. Compute the\n // dropped set up-front (cheap; turn counts are O(100s) at worst)\n // so callers can decide whether to warn the user.\n const workingIds = new Set(workingTurns.map(t => t.id))\n const droppedDueToPtl: string[] = []\n if (ptlRetries > 0) {\n for (const t of slice.toSummarize) {\n if (!workingIds.has(t.id))\n droppedDueToPtl.push(t.id)\n }\n }\n\n return {\n summary,\n usage: { ...usage, modelId: usage.modelId ?? model },\n model,\n ptlRetries,\n summarizedTurnIds: workingTurns.map(t => t.id),\n droppedDueToPtl,\n preservedTurns: slice.preserved,\n beforeBytes: bytesIn(workingTurns),\n afterBytes: utf8ByteLength(summary),\n }\n }\n catch (err) {\n // Abort flows propagate unchanged so callers can pattern-match.\n if (isAbortError(err, opts.signal))\n throw err\n\n if (isPromptTooLongError(err)) {\n if (ptlRetries >= maxPtlRetries) {\n throw new CompactPromptTooLongError(\n `Compaction failed: prompt_too_long after ${ptlRetries} retries.`,\n ptlRetries,\n )\n }\n const truncated = truncateHeadForPtlRetry(workingTurns)\n if (truncated.length === workingTurns.length) {\n // Nothing more to truncate — refuse to spin.\n throw new CompactPromptTooLongError(\n `Compaction failed: prompt_too_long and conversation cannot be shrunk further.`,\n ptlRetries,\n )\n }\n workingTurns = truncated\n ptlRetries++\n continue\n }\n\n if (isTransientError(err) && transientRetries < TRANSIENT_RETRY_BUDGET) {\n transientRetries++\n continue\n }\n\n throw err\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface RunOnceOptions {\n provider: Provider\n model: string\n system: string\n messages: SessionMessage[]\n maxTokens: number\n thinking?: ThinkingLevel\n signal?: AbortSignal\n}\n\ninterface RunOnceResult {\n summary: string\n usage: TurnUsage\n}\n\n/**\n * Single provider call. Strips the `<analysis>` block and returns the\n * post-trim summary text along with the usage report.\n *\n * Why not just use `result.text`: some providers stream deltas without\n * emitting a concatenated `text` field at the end (or set it to an\n * empty string when the stream finished via tool-call). Accumulating\n * deltas in `text` and falling back to `result.text` mirrors\n * `generateSessionTitle` and handles every adapter shape.\n */\nasync function runOnce(opts: RunOnceOptions): Promise<RunOnceResult> {\n let streamed = ''\n const result = await opts.provider.stream(\n {\n model: opts.model,\n system: opts.system,\n tools: [],\n messages: opts.messages,\n maxTokens: opts.maxTokens,\n ...(opts.thinking !== undefined ? { thinking: opts.thinking } : {}),\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n },\n {\n onText: (delta) => { streamed += delta },\n },\n )\n\n const raw = streamed.length > 0 ? streamed : result.text\n let summary = stripAnalysisBlock(raw).trim()\n\n // Reasoning-model fallback. Some models (Qwen-thinking, GLM, DeepSeek\n // reasoning variants, …) write the entire response into their\n // reasoning channel rather than `delta.content`. From this layer the\n // `text` stream is empty, but the assistant message carries\n // `{ type: 'thinking', text }` blocks containing the actual summary\n // (often still wrapped in `<analysis>` / `<summary>` tags from the\n // prompt). Walk those blocks and apply the same extraction waterfall\n // before giving up. Saves the user from \"model misbehaved, retry\" on\n // every compaction when their selected model happens to reason\n // instead of speak.\n if (summary.length === 0) {\n const thinking = collectThinkingText(result.assistantMessage)\n if (thinking.length > 0)\n summary = stripAnalysisBlock(thinking).trim()\n }\n\n if (summary.length === 0) {\n // Truly empty — aborted stream, tool-call-only turn, or a model\n // that refused to summarize. `stripAnalysisBlock` already fell back\n // to raw `<analysis>` content when the model wrapped everything in\n // the wrong tag; the thinking fallback above caught the reasoning-\n // channel case. Reaching this branch means there's nothing usable\n // anywhere in the response.\n throw new Error('Compaction failed: provider returned no summary text.')\n }\n return { summary, usage: result.usage }\n}\n\n/**\n * Concatenate every `thinking`-block's text from an assistant message.\n *\n * Reasoning models route their output through provider-specific\n * \"thinking\" channels (Anthropic native thinking, OpenAI o-series\n * reasoning, OpenAI-compat `reasoning_content`, OpenRouter\n * `reasoning_details`). All of them surface as `{ type: 'thinking', text }`\n * blocks on the normalized `SessionMessage.content` by the time the\n * provider's `stream()` returns. Walking the blocks is provider-agnostic\n * — works for every adapter without bespoke per-provider handling.\n *\n * Returns the empty string when no thinking blocks are present, so the\n * caller's \"did we get anything?\" check stays a single `.length === 0`.\n */\nfunction collectThinkingText(message: SessionMessage): string {\n const parts: string[] = []\n for (const block of message.content) {\n if (block.type === 'thinking' && typeof block.text === 'string' && block.text.length > 0)\n parts.push(block.text)\n }\n return parts.join('\\n')\n}\n\n/**\n * Extract the summary text from the provider's response, peeling off\n * any envelope the model wrapped it in.\n *\n * The compact prompt asks for `<analysis>...</analysis><summary>...</summary>`\n * but real models drift from the format. This function tries four\n * extraction paths in order of strictness:\n *\n * 1. **Strict path** — strip `<analysis>...</analysis>` blocks and\n * extract the `<summary>...</summary>` envelope. Matches the\n * prompt-following ideal.\n * 2. **Loose path** — same strip, but accept whatever's outside the\n * `<analysis>` tags as the summary (no `<summary>` envelope\n * required). Handles models that drop the wrapper but keep the\n * analysis.\n * 3. **Analysis-as-summary fallback** — when the entire response is a\n * single `<analysis>` block (model conflated the two concepts),\n * return the analysis content. Better than failing the compaction\n * and forcing the user to retry.\n * 4. **Raw passthrough** — no recognized envelope. Return the text\n * as-is and let `runOnce`'s empty-check decide whether to throw.\n *\n * Matches Claude Code's `formatCompactSummary` for path (1) + (2) and\n * adds (3) as a graceful-degradation layer we ran into in the wild\n * (smaller / non-Anthropic models sometimes produce analysis-only).\n */\nfunction stripAnalysisBlock(text: string): string {\n // Strict + loose: strip `<analysis>...</analysis>` non-greedy + dot-all\n // (no `s` flag in Bun's V8 — `[\\s\\S]` does the same).\n const analysisStripped = text.replace(/<analysis>[\\s\\S]*?<\\/analysis>/g, '')\n\n // Path 1: explicit `<summary>...</summary>` envelope.\n const summaryMatch = analysisStripped.match(/<summary>([\\s\\S]*?)<\\/summary>/)\n if (summaryMatch)\n return summaryMatch[1]\n\n // Path 2: anything outside the `<analysis>` block.\n if (analysisStripped.trim().length > 0)\n return analysisStripped\n\n // Path 3: response was nothing but an `<analysis>` block — extract\n // its content and use that as the summary. The model meant to give\n // us a summary; it just wrapped it in the wrong tag.\n const analysisMatch = text.match(/<analysis>([\\s\\S]*?)<\\/analysis>/)\n if (analysisMatch)\n return analysisMatch[1]\n\n // Path 4: no envelope of any shape — return verbatim.\n return text\n}\n\n/**\n * Convert turns into the wire-level `SessionMessage[]` shape. Drops\n * system turns (rare; they'd confuse a summary call), and inlines any\n * pre-existing `compact-summary` markers as plain text so the\n * provider's wire converter doesn't have to know about zidane's\n * internal block type.\n *\n * Inlining (instead of dropping) is intentional: a session already\n * compacted once still contains the prior summary as load-bearing\n * context. Surfacing it as `[Previous compaction summary]\\n…` lets the\n * new summarization integrate it instead of forgetting it.\n */\nfunction turnsToMessages(turns: readonly SessionTurn[]): SessionMessage[] {\n const out: SessionMessage[] = []\n for (const turn of turns) {\n if (turn.role === 'system')\n continue\n const content: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'compact-summary') {\n content.push({\n type: 'text',\n text: `[Previous compaction summary]\\n${block.summary}`,\n })\n continue\n }\n // Strip `thinking` blocks. They're the model's internal reasoning,\n // signed by the producing provider (`signatureProducer: 'anthropic'`),\n // and only valid in a follow-up request when that same request has\n // extended thinking enabled. The compaction call deliberately runs\n // with `thinking: undefined` (summarization rarely benefits from\n // reasoning, and we don't want to pay the budget), so re-sending\n // thinking blocks puts Anthropic in an inconsistent state: it\n // accepts the request without 400-ing but silently returns an\n // empty `text` response. The user-visible symptom is \"Compaction\n // failed: provider returned no summary text\" on sessions where\n // any turn used extended thinking. Thinking is opaque-by-design;\n // dropping it for the summary call doesn't lose conversational\n // content (the model's visible text is in adjacent `text` blocks).\n if (block.type === 'thinking')\n continue\n content.push(block)\n }\n if (content.length === 0)\n continue\n out.push({ role: turn.role, content })\n }\n return out\n}\n\nfunction scopeToDirection(scope: CompactScope): 'full' | 'tail' | 'from' | 'up_to' {\n if (scope === 'full' || scope === 'tail')\n return scope\n return scope.kind\n}\n\nfunction anchorTurnFor(slice: CompactionSlice, scope: CompactScope): SessionTurn {\n // Caller-side invariant: `sliceForCompaction` already verified the anchor\n // exists when scope.kind is set. We re-locate it here so the preview is\n // built from the exact same turn the prompt builder will reference.\n if (typeof scope === 'string')\n throw new Error('anchorTurnFor: scope must be object form')\n if (scope.kind === 'from')\n return slice.toSummarize[0]!\n return slice.toSummarize[slice.toSummarize.length - 1]!\n}\n\nfunction bytesIn(turns: readonly SessionTurn[]): number {\n let total = 0\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'text')\n total += utf8ByteLength(block.text)\n else if (block.type === 'tool_result')\n total += toolOutputByteLength(block.output)\n else if (block.type === 'tool_call')\n total += utf8ByteLength(JSON.stringify(block.input))\n else if (block.type === 'thinking')\n total += utf8ByteLength(block.text)\n }\n }\n return total\n}\n\n// ---------------------------------------------------------------------------\n// Error classification\n// ---------------------------------------------------------------------------\n\n/**\n * Provider-agnostic predicate for the \"prompt is too long\" rejection.\n * Inspects error code, type, status, and message substring — every\n * provider names this case differently but the message is recognizable.\n */\nfunction isPromptTooLongError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (typeof e.code === 'string' && /prompt[_ ]too[_ ]long/i.test(e.code))\n return true\n if (typeof e.status === 'number' && (e.status === 413 || e.status === 400)) {\n const message = typeof e.message === 'string' ? e.message : ''\n if (/prompt[_ ]too[_ ]long|context[_ ]length|maximum[_ ]context|too many tokens/i.test(message))\n return true\n }\n if (typeof e.message === 'string'\n && /prompt[_ ]too[_ ]long|context[_ ]length[_ ]exceeded|context window/i.test(e.message)) {\n return true\n }\n // Anthropic SDK shape: `error.error.type === 'invalid_request_error'`\n // with a `prompt is too long` message.\n const nested = (e.error ?? {}) as Record<string, unknown>\n if (typeof nested.type === 'string' && nested.type === 'invalid_request_error') {\n const message = typeof nested.message === 'string' ? nested.message : ''\n if (/prompt[_ ]too[_ ]long|too long|context window/i.test(message))\n return true\n }\n return false\n}\n\n/**\n * Transient network / 5xx errors worth retrying once or twice.\n */\nfunction isTransientError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (typeof e.status === 'number' && e.status >= 500 && e.status < 600)\n return true\n if (typeof e.code === 'string' && /ECONNRESET|ETIMEDOUT|ENETUNREACH|EAI_AGAIN|fetch failed/i.test(e.code))\n return true\n if (typeof e.message === 'string'\n && /socket hang up|fetch failed|network error|terminated|ECONNRESET|read ETIMEDOUT/i.test(e.message)) {\n return true\n }\n return false\n}\n\nfunction isAbortError(err: unknown, signal: AbortSignal | undefined): boolean {\n if (signal?.aborted)\n return true\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (e.name === 'AbortError')\n return true\n if (typeof e.message === 'string' && /aborted/i.test(e.message))\n return true\n return false\n}\n","/**\n * Post-compact restoration — re-inject load-bearing working state as\n * synthetic tool-call/tool-result pairs after a {@link compactConversation}\n * marker lands in a session.\n *\n * Without this step, the model has a narrative summary but loses direct\n * access to the files it was actively editing and the skills it was\n * following. Restoration re-attaches the top-N recently-read files and\n * active skills so the next turn starts with full working context — no\n * forced re-reads, no degraded continuation.\n *\n * Design — synthetic tool_call/tool_result pairs:\n *\n * Two turns are appended after the compaction marker:\n *\n * [marker] ← from `summaryToTurn(result)`\n * [assistant, tool_calls × N] ← synthetic, one tool_call per item\n * [user, tool_results × N] ← synthetic, matching results\n * [new prompt] ← user's next message\n *\n * The synthetic turns look identical to what the agent would produce if it\n * had actually run `read_file` / `skills_use` — by design, because at the\n * moment of compaction those operations had just happened with that data.\n *\n * Persisted blocks use **canonical** tool names (e.g. `read_file`). The\n * agent loop's `rewriteMessagesToWire` translates them to whatever alias\n * the host configured before they reach the provider, exactly as it does\n * for real calls. Restoration therefore \"just works\" through aliasing —\n * the helper does not need to consult the alias map.\n *\n * Budgets — mirror Claude Code's `services/compact/compact.ts:122-130`:\n *\n * - 50_000 tokens total file budget, 5_000 per file, max 5 files\n * - 25_000 tokens total skill budget, 5_000 per skill (no count cap)\n *\n * Tokens are estimated at 4 chars/token — same heuristic Claude Code uses\n * for budget arithmetic. Hosts can override every limit.\n *\n * Failure modes:\n *\n * - File read fails (deleted, permissions) → skip silently. Other items\n * still proceed.\n * - No `execution` / `handle` passed → file restoration is a no-op;\n * skill restoration still works (skills carry their content inline).\n * - Empty `recentFiles` AND empty `activeSkills` → returns `{ turns: [] }`\n * so the caller's `appendTurns([summary, ...attachments])` is a no-op\n * on the attachments.\n */\n\nimport type { ExecutionContext, ExecutionHandle } from '../contexts'\nimport type { Session } from '../session'\nimport type { ActiveSkill } from '../skills/activation'\nimport type { SessionContentBlock, SessionTurn } from '../types'\nimport { getReadState } from '../tools/read-state'\nimport { BYTES_PER_TOKEN, estimateTokens, utf8ByteLength } from './utils'\n\n// ---------------------------------------------------------------------------\n// Defaults — match Claude Code's published constants for parity.\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_FILE_TOKEN_BUDGET = 50_000\nconst DEFAULT_FILE_TOKEN_PER_FILE_CAP = 5_000\nconst DEFAULT_MAX_FILES_TO_RESTORE = 5\n\nconst DEFAULT_SKILL_TOKEN_BUDGET = 25_000\nconst DEFAULT_SKILL_TOKEN_PER_SKILL_CAP = 5_000\n\n/** Default canonical tool names — `rewriteMessagesToWire` handles aliasing. */\nconst DEFAULT_READ_FILE_TOOL_NAME = 'read_file'\nconst DEFAULT_SKILLS_USE_TOOL_NAME = 'skills_use'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** One file selected for restoration, with its last-read timestamp for ranking. */\nexport interface RecentFile {\n /** Path relative to the execution context's `handle.cwd`. */\n path: string\n /** Wall-clock when the file was last read, in ms. Drives recency ranking. */\n mtimeMs: number\n}\n\nexport interface PostCompactRestoreOptions {\n // ---- What to restore ---------------------------------------------------\n\n /**\n * Files to consider for restoration, ranked by recency. Typically derived\n * via {@link selectFilesFromReadState} from `getReadState(session)`.\n * The helper takes the top {@link PostCompactRestoreOptions.maxFilesToRestore}\n * by `mtimeMs` descending, fetches their current content, and synthesizes\n * `read_file` tool_call/tool_result pairs.\n */\n recentFiles?: readonly RecentFile[]\n\n /**\n * Active skills to re-inject. Typically `agent.activeSkills`. Each entry\n * yields a synthetic `skills_use` tool_call/tool_result pair carrying the\n * skill's instructions (possibly truncated to the per-skill budget).\n */\n activeSkills?: readonly ActiveSkill[]\n\n // ---- I/O ---------------------------------------------------------------\n\n /**\n * Execution context used to fetch file content. **Required for file\n * restoration.** Skills come pre-loaded (instructions live on the\n * `SkillConfig`), so they don't need the context.\n */\n execution?: ExecutionContext\n handle?: ExecutionHandle\n\n // ---- Tool naming -------------------------------------------------------\n\n /**\n * Canonical name of the file-read tool. Defaults to `read_file`. Aliases\n * (e.g. `Read`) are NOT specified here — the agent loop's\n * `rewriteMessagesToWire` handles wire conversion automatically.\n *\n * Override only when a host registered file-read under a different\n * canonical name (not just an alias).\n */\n readFileToolName?: string\n /**\n * Canonical name of the skill-activation tool. Defaults to `skills_use`.\n * Same aliasing semantics as `readFileToolName`.\n */\n skillsUseToolName?: string\n\n // ---- Budgets -----------------------------------------------------------\n\n /** Total token budget for file restoration. Default: 50_000. */\n fileTokenBudget?: number\n /** Per-file token cap. Default: 5_000. */\n fileTokenPerFileCap?: number\n /** Maximum file count. Default: 5. */\n maxFilesToRestore?: number\n\n /** Total token budget for skill restoration. Default: 25_000. */\n skillTokenBudget?: number\n /** Per-skill token cap. Default: 5_000. */\n skillTokenPerSkillCap?: number\n\n // ---- Filtering ---------------------------------------------------------\n\n /**\n * Paths to skip — typically the file paths already covered by the\n * compaction result's `preservedTurns`. Avoids double-injection when the\n * recent reads sit in the preserved tail.\n */\n excludePaths?: readonly string[]\n\n // ---- Synthesis ---------------------------------------------------------\n\n /**\n * Optional `runId` to tag the synthetic turns with — useful for hosts\n * that want the restoration turns to roll up under the same run as the\n * compaction marker. Defaults to undefined (orphan turns).\n */\n runId?: string\n}\n\n/**\n * Envelope returned by {@link buildPostCompactAttachments}. The caller\n * spreads `turns` into `session.appendTurns([summaryTurn, ...turns])`.\n * Count fields drive UI banners (\"restored 5 files + 2 skills\").\n */\nexport interface PostCompactAttachments {\n /**\n * Two synthetic turns when at least one item was restored, otherwise\n * an empty array. The pair is `[assistant_with_tool_calls,\n * user_with_tool_results]` — adjacent and well-formed for every\n * provider's `tool_use ↔ tool_result` invariant.\n */\n turns: readonly SessionTurn[]\n /** Count of files actually restored (post-budget). */\n restoredFiles: number\n /** Count of skills actually restored (post-budget). */\n restoredSkills: number\n /** Rough total token cost of the restoration payload (sum of all content). */\n estimatedTokens: number\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for tests + caller convenience\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a raw read-state map into a deduped, path-ranked list ready for\n * restoration. Multiple entries for the same path (different\n * `(offset, limit, maxBytes)` slices) collapse to one — keeping the most\n * recent `mtimeMs`.\n *\n * Filters out entries whose key doesn't share the given `cwd` prefix:\n * those came from a different execution context and can't be read back\n * through this agent's handle.\n *\n * Pure. Most callers want {@link selectFilesFromSession}, which wraps\n * `getReadState(session)` + this function in one call so the host\n * doesn't have to reach into the tools layer.\n */\nexport function selectFilesFromReadState(\n readState: ReadonlyMap<string, { mtimeMs: number }>,\n cwd: string,\n): RecentFile[] {\n const prefix = `${cwd}::`\n const byPath = new Map<string, number>()\n for (const [key, entry] of readState) {\n if (!key.startsWith(prefix))\n continue\n const path = key.slice(prefix.length)\n if (path.length === 0)\n continue\n const prior = byPath.get(path) ?? -Infinity\n if (entry.mtimeMs > prior)\n byPath.set(path, entry.mtimeMs)\n }\n return Array.from(byPath, ([path, mtimeMs]) => ({ path, mtimeMs }))\n .sort((a, b) => b.mtimeMs - a.mtimeMs)\n}\n\n/**\n * Session-aware convenience: extract recently-read files directly from\n * a {@link Session} via its per-session read-state map.\n *\n * Hosts (TUI / SDK consumers) typically have a `Session` and a `cwd`\n * (the active agent's `handle.cwd`) on hand — this wrapper saves them\n * from reaching into `src/tools/read-state.ts` directly. Returns an\n * empty list when no read state has been recorded yet (fresh session,\n * or `behavior.dedupReads === false`).\n *\n * Equivalent to:\n *\n * ```ts\n * const state = getReadState(session)\n * return state ? selectFilesFromReadState(state, cwd) : []\n * ```\n */\nexport function selectFilesFromSession(\n session: Session,\n cwd: string,\n): RecentFile[] {\n const state = getReadState(session)\n return state ? selectFilesFromReadState(state, cwd) : []\n}\n\n/**\n * Pick the top `maxFiles` from `files` (descending by `mtimeMs`),\n * dropping any whose path appears in `excludePaths`.\n *\n * Stable for equal mtimes — files with the same timestamp retain their\n * input order. Pure.\n */\nexport function selectRecentFiles(\n files: readonly RecentFile[],\n opts: { maxFiles: number, excludePaths?: readonly string[] },\n): RecentFile[] {\n const excluded = new Set(opts.excludePaths ?? [])\n const filtered = files.filter(f => !excluded.has(f.path))\n // Defensive sort — caller may pass an already-sorted list, but doing it\n // here keeps the function total regardless of input order.\n const sorted = filtered.slice().sort((a, b) => b.mtimeMs - a.mtimeMs)\n return sorted.slice(0, Math.max(0, opts.maxFiles))\n}\n\n// ---------------------------------------------------------------------------\n// Internal — content formatting + truncation\n// ---------------------------------------------------------------------------\n\ninterface FormattedFile {\n /** Tool-result content (line-numbered, with optional truncation footer). */\n body: string\n /** Whether the content was truncated to fit the per-file budget. */\n truncated: boolean\n /** Estimated token cost of `body` (post-truncation). */\n estimatedTokens: number\n}\n\ninterface FormattedSkill {\n body: string\n truncated: boolean\n estimatedTokens: number\n}\n\n/**\n * Format a file's contents to match `read_file`'s output shape — 1-indexed\n * line numbers separated by tabs, identical to what the model has seen\n * from real `read_file` calls. Applies the per-file token cap by truncating\n * at the nearest line boundary; appends a footer pointing at the next\n * offset so the model can re-read the rest if needed.\n */\nfunction formatFileForRestoration(\n content: string,\n perFileTokenCap: number,\n): FormattedFile {\n const totalBytes = utf8ByteLength(content)\n const allLines = content.split('\\n')\n const totalLines = allLines.length\n\n // Build the line-numbered body first; tokens are counted on the\n // numbered form because that's what reaches the provider.\n const numbered: string[] = []\n let runningChars = 0\n const charCap = Math.max(1, perFileTokenCap) * BYTES_PER_TOKEN\n let truncatedAt = -1\n let midLineCut = false\n\n for (let i = 0; i < allLines.length; i++) {\n const numberedLine = `${i + 1}\\t${allLines[i]}`\n const lineCharCost = numberedLine.length + (i < allLines.length - 1 ? 1 : 0) // +1 for the joining '\\n' on every line but the last\n if (runningChars + lineCharCost > charCap) {\n if (numbered.length === 0) {\n // The first line on its own already overflows (typical for\n // minified JS, CSV-on-one-line, etc.). Cut it at the char\n // boundary so we return SOMETHING useful — the head of the\n // file — instead of either failing or admitting a huge first\n // line that blows the budget. Mid-line cuts mean we can't\n // suggest a precise re-read offset; the footer says so.\n const cutTo = Math.max(1, charCap - `${i + 1}\\t`.length)\n numbered.push(`${i + 1}\\t${allLines[i].slice(0, cutTo)}`)\n midLineCut = true\n }\n truncatedAt = i\n break\n }\n numbered.push(numberedLine)\n runningChars += lineCharCost\n }\n\n const body = numbered.join('\\n')\n if (truncatedAt < 0)\n return { body, truncated: false, estimatedTokens: estimateTokens(body) }\n\n const lineLabel = midLineCut\n ? `line ${truncatedAt + 1} (mid-line)`\n : `line ${truncatedAt}`\n const offsetHint = midLineCut\n ? `mid-line cut prevents a precise offset — re-read with offset=${truncatedAt + 1} and a larger maxBytes`\n : `re-read with offset=${truncatedAt + 1} to continue`\n const footer = `\\n\\n…truncated at ${lineLabel} (post-compact restoration cap: ${perFileTokenCap} tokens). File has ${totalLines} lines, ${totalBytes} bytes total — ${offsetHint}.`\n const truncated = body + footer\n return { body: truncated, truncated: true, estimatedTokens: estimateTokens(truncated) }\n}\n\n/**\n * Format a skill's instructions for restoration. Skills are plain markdown\n * — no line numbering, no wrapping XML envelope (the model already\n * understands the format from real `skills_use` calls). Truncation cuts\n * at a line boundary when possible; appends a marker so the model knows\n * the body is incomplete.\n */\nfunction formatSkillForRestoration(\n instructions: string,\n perSkillTokenCap: number,\n): FormattedSkill {\n const charCap = Math.max(1, perSkillTokenCap) * BYTES_PER_TOKEN\n if (instructions.length <= charCap) {\n return { body: instructions, truncated: false, estimatedTokens: estimateTokens(instructions) }\n }\n // Truncate at the nearest line boundary at or before charCap so the\n // body stays markdown-parseable.\n const head = instructions.slice(0, charCap)\n const lastNewline = head.lastIndexOf('\\n')\n const cutoff = lastNewline > 0 ? lastNewline : charCap\n const truncatedBody\n = `${instructions.slice(0, cutoff).trimEnd()}\\n\\n…[truncated post-compact at ${perSkillTokenCap} tokens; full skill body lives at the skill's location]`\n return {\n body: truncatedBody,\n truncated: true,\n estimatedTokens: estimateTokens(truncatedBody),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public runner\n// ---------------------------------------------------------------------------\n\n/**\n * Build the synthetic turns to append after a `compact-summary` marker.\n *\n * Returns a `PostCompactAttachments` envelope; the caller is responsible\n * for `session.appendTurns([summaryTurn, ...result.turns])`. The two\n * synthetic turns are always emitted together (or both omitted when\n * nothing was restored) so the `tool_use ↔ tool_result` adjacency\n * invariant holds regardless of caller code path.\n *\n * Failure isolation: a single file's read failure never blocks the rest\n * of restoration — that file is skipped silently and processing\n * continues. The returned `restoredFiles` count reflects what actually\n * landed in the synthesized turns.\n */\nexport async function buildPostCompactAttachments(\n opts: PostCompactRestoreOptions,\n): Promise<PostCompactAttachments> {\n const fileTokenBudget = opts.fileTokenBudget ?? DEFAULT_FILE_TOKEN_BUDGET\n const fileTokenPerFileCap = opts.fileTokenPerFileCap ?? DEFAULT_FILE_TOKEN_PER_FILE_CAP\n const maxFilesToRestore = opts.maxFilesToRestore ?? DEFAULT_MAX_FILES_TO_RESTORE\n const skillTokenBudget = opts.skillTokenBudget ?? DEFAULT_SKILL_TOKEN_BUDGET\n const skillTokenPerSkillCap = opts.skillTokenPerSkillCap ?? DEFAULT_SKILL_TOKEN_PER_SKILL_CAP\n const readFileToolName = opts.readFileToolName ?? DEFAULT_READ_FILE_TOOL_NAME\n const skillsUseToolName = opts.skillsUseToolName ?? DEFAULT_SKILLS_USE_TOOL_NAME\n\n // ---- Phase 1: pick candidates --------------------------------------\n //\n // Tool-availability is governed at the input layer: callers control\n // which categories are restored by what they pass. To skip files,\n // pass empty `recentFiles` (or none). To skip skills, pass empty\n // `activeSkills` (or none). The runner trusts the caller to know\n // what tools the next agent run will have access to — auto-injected\n // tools (`skills_use`, MCP, interaction tools) aren't introspectable\n // from a profile config and constructing a tool-allowlist here would\n // be incomplete by design.\n const candidateFiles = opts.recentFiles && opts.recentFiles.length > 0\n ? selectRecentFiles(opts.recentFiles, {\n maxFiles: maxFilesToRestore,\n ...(opts.excludePaths ? { excludePaths: opts.excludePaths } : {}),\n })\n : []\n const candidateSkills = opts.activeSkills ?? []\n\n // ---- Phase 2: fetch + format files (with per-file + group budget) --\n const fileCalls: Array<{ callId: string, path: string, body: string, estimatedTokens: number }> = []\n let fileBudgetUsed = 0\n if (candidateFiles.length > 0 && opts.execution && opts.handle) {\n for (let i = 0; i < candidateFiles.length; i++) {\n const file = candidateFiles[i]\n let content: string\n try {\n content = await opts.execution.readFile(opts.handle, file.path)\n }\n catch {\n // File deleted / unreadable since the last read — skip silently.\n continue\n }\n const formatted = formatFileForRestoration(content, fileTokenPerFileCap)\n if (fileBudgetUsed + formatted.estimatedTokens > fileTokenBudget) {\n // Adding this file would blow the group budget. Stop here — the\n // remaining files are older anyway (candidates are pre-sorted by\n // recency descending).\n break\n }\n fileBudgetUsed += formatted.estimatedTokens\n fileCalls.push({\n callId: `compact-restore-file-${i}`,\n path: file.path,\n body: formatted.body,\n estimatedTokens: formatted.estimatedTokens,\n })\n }\n }\n\n // ---- Phase 3: format skills (no I/O — instructions are inline) -----\n const skillCalls: Array<{ callId: string, name: string, body: string, estimatedTokens: number }> = []\n let skillBudgetUsed = 0\n for (let i = 0; i < candidateSkills.length; i++) {\n const active = candidateSkills[i]\n const instructions = active.skill.instructions ?? ''\n if (instructions.trim().length === 0)\n continue\n const formatted = formatSkillForRestoration(instructions, skillTokenPerSkillCap)\n if (skillBudgetUsed + formatted.estimatedTokens > skillTokenBudget)\n break\n skillBudgetUsed += formatted.estimatedTokens\n skillCalls.push({\n callId: `compact-restore-skill-${i}`,\n name: active.skill.name,\n body: formatted.body,\n estimatedTokens: formatted.estimatedTokens,\n })\n }\n\n // ---- Phase 4: synthesize turns -------------------------------------\n if (fileCalls.length === 0 && skillCalls.length === 0) {\n return { turns: [], restoredFiles: 0, restoredSkills: 0, estimatedTokens: 0 }\n }\n\n const assistantBlocks: SessionContentBlock[] = []\n const userBlocks: SessionContentBlock[] = []\n\n for (const fc of fileCalls) {\n assistantBlocks.push({\n type: 'tool_call',\n id: fc.callId,\n name: readFileToolName,\n input: { path: fc.path },\n })\n userBlocks.push({\n type: 'tool_result',\n callId: fc.callId,\n output: fc.body,\n })\n }\n for (const sc of skillCalls) {\n assistantBlocks.push({\n type: 'tool_call',\n id: sc.callId,\n name: skillsUseToolName,\n input: { name: sc.name },\n })\n userBlocks.push({\n type: 'tool_result',\n callId: sc.callId,\n output: sc.body,\n })\n }\n\n const now = Date.now()\n const tag = opts.runId ? { runId: opts.runId } : {}\n const turns: SessionTurn[] = [\n {\n id: crypto.randomUUID(),\n role: 'assistant',\n content: assistantBlocks,\n createdAt: now,\n ...tag,\n },\n {\n id: crypto.randomUUID(),\n role: 'user',\n content: userBlocks,\n // +1 ms so the user turn sorts after the assistant turn under\n // tie-breaking sorts that walk by `createdAt`.\n createdAt: now + 1,\n ...tag,\n },\n ]\n\n return {\n turns,\n restoredFiles: fileCalls.length,\n restoredSkills: skillCalls.length,\n estimatedTokens: fileBudgetUsed + skillBudgetUsed,\n }\n}\n","/**\n * Structured logging primitive with auto-correlation.\n *\n * Every consumer of the harness used to roll its own logger; the TUI has\n * one, downstream apps have theirs, observability backends each want a\n * slightly different shape. This module ships the smallest reasonable\n * shared surface:\n *\n * - {@link Logger} — minimal level-tagged interface (`debug` / `info` /\n * `warn` / `error`) plus a `with(...)` method that returns a child\n * logger carrying baseline attributes (`runId`, `turnId`, `callId`,\n * `childId`, `depth`, anything you want).\n * - {@link createLogger} — builds a Logger from a {@link LogSink}.\n * - {@link consoleSink} / {@link jsonSink} — built-in sinks that cover\n * the common cases (human-readable to a terminal vs. one JSON object\n * per line to a log aggregator).\n * - {@link createLoggingHooks} — installs Logger-attached hook handlers\n * on an agent so every lifecycle event lands on the sink with the\n * right correlation ids already stamped in.\n *\n * Hosts that already own a logger (pino, winston, structlog binding,\n * platform-native) plug it in via a custom {@link LogSink} — the helper\n * itself does no I/O.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\nexport interface LogRecord {\n level: LogLevel\n /** Unix ms — set by `Logger` at emit time. */\n timestamp: number\n /** Free-form message. Sinks render this as the human-facing line. */\n message: string\n /** Structured fields. Correlation ids land here automatically. */\n attrs: Record<string, unknown>\n}\n\nexport interface LogSink {\n emit: (record: LogRecord) => void\n}\n\nexport interface Logger {\n debug: (message: string, attrs?: Record<string, unknown>) => void\n info: (message: string, attrs?: Record<string, unknown>) => void\n warn: (message: string, attrs?: Record<string, unknown>) => void\n error: (message: string, attrs?: Record<string, unknown>) => void\n /**\n * Returns a child logger that prepends the given attributes onto every\n * subsequent emit. Equivalent to `pino.child` / `winston.child`. The\n * parent and child share the same sink — children are zero-cost.\n */\n with: (extra: Record<string, unknown>) => Logger\n /**\n * Inspectable baseline attributes — handy for tests and for hook\n * handlers that want to clone-with-extra without recursing.\n */\n readonly baseAttributes: Readonly<Record<string, unknown>>\n}\n\n// ---------------------------------------------------------------------------\n// createLogger\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Logger from a sink. Stateless and cheap; create one per agent\n * (or per app) and use `.with()` to attach correlation ids per-call.\n */\nexport function createLogger(\n sink: LogSink,\n baseAttributes: Readonly<Record<string, unknown>> = {},\n): Logger {\n function emit(level: LogLevel, message: string, attrs?: Record<string, unknown>): void {\n try {\n sink.emit({\n level,\n timestamp: Date.now(),\n message,\n attrs: attrs ? { ...baseAttributes, ...attrs } : { ...baseAttributes },\n })\n }\n catch {\n // Sinks should never crash the run.\n }\n }\n\n return {\n debug: (m, a) => emit('debug', m, a),\n info: (m, a) => emit('info', m, a),\n warn: (m, a) => emit('warn', m, a),\n error: (m, a) => emit('error', m, a),\n with: extra => createLogger(sink, { ...baseAttributes, ...extra }),\n baseAttributes,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in sinks\n// ---------------------------------------------------------------------------\n\nexport interface ConsoleSinkOptions {\n /**\n * Minimum level to emit. Defaults to `'info'` — `debug` is dropped so\n * the harness's lifecycle logging is not noisy by default. Set to\n * `'debug'` to see every event.\n */\n minLevel?: LogLevel\n /** Custom output stream. Defaults to `process.stderr` so logs don't pollute stdout. */\n stream?: { write: (chunk: string) => void }\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n}\n\n/**\n * Human-readable terminal sink. Renders each record as\n * `<ISO timestamp> <LEVEL> <message> <attrs as kv pairs>`.\n *\n * Honors `process.stderr` by default so log lines don't interleave with\n * the agent's stdout-bound output (chat responses, JSON results).\n */\nexport function consoleSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n const ts = new Date(record.timestamp).toISOString()\n const kv = Object.entries(record.attrs)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`)\n .join(' ')\n stream.write(`${ts} ${record.level.toUpperCase().padEnd(5)} ${record.message}${kv ? ` ${kv}` : ''}\\n`)\n },\n }\n}\n\n/**\n * One-JSON-object-per-line sink. Suitable for piping into log aggregators\n * (Datadog Agent, Fluent Bit, Loki, Vector) that expect JSONL.\n */\nexport function jsonSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n try {\n stream.write(`${JSON.stringify(record)}\\n`)\n }\n catch {\n // Non-serializable attr (cycle, BigInt, etc.). Skip rather than throw.\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// createLoggingHooks\n// ---------------------------------------------------------------------------\n\nexport interface LoggingHooksOptions {\n logger: Logger\n /**\n * Minimum interesting level for harness-emitted lines. Default `'info'`.\n * Set to `'debug'` to see every tool dispatch / stream event. Set to\n * `'warn'` to mute the chatty ones and only see failures + budgets.\n */\n level?: LogLevel\n /**\n * When true (default), lifecycle events (`agent:start`, `turn:before`,\n * `tool:before`, `mcp:bootstrap:start`) emit at `debug` level so they\n * stay quiet by default. Set false to mute them entirely regardless of\n * the configured minimum level — useful when piping into a tracer\n * that already captures lifecycle.\n */\n includeLifecycle?: boolean\n}\n\nexport interface LoggingHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n/**\n * Install a bundle of hook handlers that emit a structured line per\n * relevant lifecycle event, automatically attaching correlation ids\n * (`runId`, `turnId`, `callId`, `childId`, `depth`, `agentName`).\n *\n * @example\n * ```ts\n * const logger = createLogger(consoleSink({ minLevel: 'debug' }), { service: 'tui' })\n * const lh = createLoggingHooks({ logger })\n * const uninstall = lh.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createLoggingHooks(options: LoggingHooksOptions): LoggingHookSet {\n const root = options.logger\n const includeLifecycle = options.includeLifecycle ?? true\n const minLevel = LEVEL_ORDER[options.level ?? 'info']\n\n /**\n * Wrap a Logger so emissions below `minLevel` are dropped before they\n * hit the sink. Lets us gate harness chatter without forcing every\n * `LogSink` to re-implement level filtering. Preserves `.with()`\n * composition (children inherit the filter).\n */\n function gateLevel(logger: Logger): Logger {\n const skip = (level: LogLevel): boolean => LEVEL_ORDER[level] < minLevel\n const wrap = (l: Logger): Logger => ({\n debug: (m, a) => {\n if (!skip('debug'))\n l.debug(m, a)\n },\n info: (m, a) => {\n if (!skip('info'))\n l.info(m, a)\n },\n warn: (m, a) => {\n if (!skip('warn'))\n l.warn(m, a)\n },\n error: (m, a) => {\n if (!skip('error'))\n l.error(m, a)\n },\n with: extra => wrap(l.with(extra)),\n baseAttributes: l.baseAttributes,\n })\n return wrap(logger)\n }\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n const unregisters: Array<() => void> = []\n // Per-install loggers, refreshed on `agent:start` / `turn:before`.\n // Scoped INSIDE install() so concurrent installs across different\n // agents don't share state — each call to `install()` gets its own\n // attribution state.\n const gatedRoot = gateLevel(root)\n let runLogger = gatedRoot\n let turnLogger = gatedRoot\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n runLogger = gatedRoot.with({\n runId: ctx.runId,\n ...(ctx.parentRunId ? { parentRunId: ctx.parentRunId } : {}),\n depth: ctx.depth,\n ...(ctx.agentName ? { agentName: ctx.agentName } : {}),\n })\n turnLogger = runLogger\n if (includeLifecycle)\n runLogger.debug('agent run started')\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n runLogger.info('agent run completed', {\n turns: stats.turns,\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n ...(typeof stats.cost === 'number' ? { cost: stats.cost } : {}),\n elapsedMs: stats.elapsed,\n ...(typeof stats.timeTillFirstTokenMs === 'number' ? { ttftMs: stats.timeTillFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n runLogger.warn('agent run aborted')\n }))\n\n // ---- Turn / stream ---------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnLogger = runLogger.with({ turnId: ctx.turnId, turn: ctx.turn })\n if (includeLifecycle)\n turnLogger.debug('turn started')\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n turnLogger.debug('turn ended', {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n ...(ctx.usage.finishReason ? { finishReason: ctx.usage.finishReason } : {}),\n ...(ctx.usage.modelId ? { modelId: ctx.usage.modelId } : {}),\n ...(typeof ctx.usage.timeToFirstTokenMs === 'number' ? { ttftMs: ctx.usage.timeToFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n turnLogger.error('stream error', {\n message: ctx.err instanceof Error ? ctx.err.message : String(ctx.err),\n ...(ctx.statusCode !== undefined ? { statusCode: ctx.statusCode } : {}),\n ...(ctx.requestId !== undefined ? { requestId: ctx.requestId } : {}),\n })\n }))\n\n // ---- Tool calls -------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool started', {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n })\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool ended', {\n toolName: ctx.name,\n callId: ctx.callId,\n outputBytes: ctx.outputBytes,\n })\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n turnLogger.error('tool error', {\n toolName: ctx.name,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n // Successful + gate-substitute paths are routine — only log at\n // debug level. The three \"something refused / went wrong\" paths\n // (gate-block, unknown tool, invalid input) land at warn so they\n // surface in default-config dashboards without forcing debug.\n const isAnomaly = ctx.outcome === 'gate-block'\n || ctx.outcome === 'unknown'\n || ctx.outcome === 'invalid-input'\n if (!isAnomaly && !includeLifecycle)\n return\n const lvl: LogLevel = isAnomaly ? 'warn' : 'debug'\n turnLogger[lvl]('tool dispatched', {\n toolName: ctx.name,\n callId: ctx.callId,\n outcome: ctx.outcome,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n turnLogger.warn('tool input rejected', {\n toolName: ctx.name,\n callId: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n // ---- Budgets ----------------------------------------------------\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n turnLogger.warn('byte budget exceeded', {\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n turnLogger.warn('tool budget exceeded', {\n toolName: ctx.tool,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n // ---- MCP --------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n if (ctx.ok) {\n if (includeLifecycle) {\n runLogger.debug('mcp bootstrap ok', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n toolCount: ctx.toolCount,\n ...(ctx.lazy ? { lazy: true } : {}),\n ...(ctx.cached ? { cached: true } : {}),\n })\n }\n }\n else {\n runLogger.warn('mcp bootstrap failed', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n message: ctx.error.message,\n })\n }\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n runLogger.error('mcp error', {\n server: ctx.name,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:warn', (ctx) => {\n runLogger.warn('mcp warn', {\n server: ctx.name,\n message: ctx.message,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n runLogger.warn('mcp auth required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n turnLogger.error('mcp tool error', {\n server: ctx.server,\n tool: ctx.displayName,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n // ---- Spawn ------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n if (!includeLifecycle)\n return\n runLogger.debug('spawn started', {\n childId: ctx.id,\n depth: ctx.depth,\n })\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n runLogger.info('spawn completed', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n status: ctx.status ?? 'completed',\n turns: ctx.stats.turns,\n totalIn: ctx.stats.totalIn,\n totalOut: ctx.stats.totalOut,\n ...(typeof ctx.stats.cost === 'number' ? { cost: ctx.stats.cost } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n runLogger.error('spawn error', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n message: ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n }\n },\n }\n}\n","/**\n * Local loopback HTTP callback for OAuth 2.0 authorization code flows.\n *\n * Stands up a one-shot server on `127.0.0.1:<random>` that captures the\n * `?code=...` redirect from a browser-driven OAuth flow and resolves a\n * promise with the code. Used as the `redirectUrl` half of the MCP SDK's\n * `OAuthClientProvider` (the persistence half lives separately).\n *\n * Design:\n * - Loopback-only (`127.0.0.1`) — the OAuth spec treats `http://127.0.0.1:<port>`\n * as a public-client redirect URI per RFC 8252 §7.3. Browsers do NOT block it,\n * and Anthropic / OpenAI / Linear / GitHub all accept it.\n * - Random port (`port = 0`) — the OS picks an unused one. We read the actual\n * port back from `server.address()` after `listen()`.\n * - Single-shot — the first GET to `path` with a `code` (or `error`) wins;\n * subsequent requests get 404. The server keeps listening (in case the user\n * hits \"back\" and re-authorizes), so callers must `close()` once they have\n * the code or have given up.\n * - Abort-aware — wiring an external `AbortSignal` rejects the promise and\n * closes the server immediately. Required for the TUI's \"esc cancels login\"\n * UX.\n * - No HTML framework — a single inline `<html>` string keeps this isolated\n * from any UI dependency.\n */\n\nimport type { AddressInfo } from 'node:net'\nimport { createServer } from 'node:http'\n\n/**\n * Result of a successful callback. `state` is forwarded verbatim from the\n * query string — callers verify it against their pre-flight value to defend\n * against CSRF (the MCP SDK does this internally when it controls `state`).\n */\nexport interface OAuthCallbackResult {\n code: string\n state?: string\n}\n\nexport interface OAuthCallbackHandle {\n /**\n * Full URI to register with the authorization server, e.g.\n * `http://127.0.0.1:51823/callback`. Stable for the lifetime of the\n * handle.\n */\n redirectUri: string\n /**\n * Resolves with `{ code, state }` on a successful callback. Rejects with:\n * - The OAuth-spec `error` field (`access_denied`, `server_error`, ...)\n * when the authorization server redirects with `?error=...`.\n * - `'OAuth callback aborted'` when the external `AbortSignal` fires.\n * - `'OAuth callback server closed'` when `close()` is called before any\n * callback arrives.\n *\n * Single-shot — only the first matching request resolves the promise.\n */\n promise: Promise<OAuthCallbackResult>\n /**\n * Idempotent shutdown. Safe to call from a `finally` block whether the\n * flow succeeded, failed, or was aborted. Resolves once the server stops\n * accepting connections.\n */\n close: () => Promise<void>\n}\n\nexport interface OAuthCallbackOptions {\n /** Cancels the flow — rejects `promise` and closes the server. */\n signal?: AbortSignal\n /**\n * Path component the authorization server should redirect to. Defaults\n * to `/callback`. Useful when matching a pre-registered URI that uses a\n * different path.\n */\n path?: string\n /**\n * Override the loopback host. Defaults to `127.0.0.1`. Don't bind to\n * `0.0.0.0` here — the OAuth code is a one-time secret and the server\n * would otherwise accept it from any host on the LAN.\n */\n host?: string\n /**\n * Override the port. Defaults to `0` (OS-assigned). Pin to a fixed port\n * only when the authorization server requires a pre-registered redirect\n * URI; the random-port path is preferred so concurrent flows don't clash.\n */\n port?: number\n}\n\nconst DEFAULT_PATH = '/callback'\nconst DEFAULT_HOST = '127.0.0.1'\n\nconst SUCCESS_HTML = `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>Logged in</title>\n<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:6rem auto;max-width:28rem;text-align:center;color:#1d1d1f}.ok{color:#1f8a4c;font-weight:600}</style>\n</head><body>\n<p class=\"ok\">Logged in.</p>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n\nfunction errorHtml(message: string): string {\n // No interpolation into HTML attributes — message goes only inside <p> text\n // where the SAMEORIGIN browser context is rendering. Escape angle brackets\n // and ampersands defensively anyway: a malicious authorization server could\n // theoretically craft an `error_description` containing markup, and we\n // don't want stored XSS even in a one-shot loopback page.\n const escaped = message\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>Login failed</title>\n<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:6rem auto;max-width:28rem;text-align:center;color:#1d1d1f}.err{color:#c43c2c;font-weight:600}</style>\n</head><body>\n<p class=\"err\">Login failed.</p>\n<p>${escaped}</p>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n}\n\n/**\n * Start a one-shot OAuth callback server. The returned handle's `redirectUri`\n * should be passed to the authorization server as the `redirect_uri` query\n * parameter; `promise` resolves once the user finishes the browser flow.\n *\n * Always `await handle.close()` in a `finally` block — even on success, the\n * server stays open until told to shut down (so it can serve the\n * \"you can close this tab\" page).\n */\nexport async function startOAuthCallback(\n opts: OAuthCallbackOptions = {},\n): Promise<OAuthCallbackHandle> {\n const path = opts.path ?? DEFAULT_PATH\n const host = opts.host ?? DEFAULT_HOST\n const port = opts.port ?? 0\n\n if (!path.startsWith('/'))\n throw new Error(`OAuth callback path must start with \"/\" (got: ${JSON.stringify(path)})`)\n\n if (opts.signal?.aborted)\n throw new Error('OAuth callback aborted')\n\n let resolveResult: (value: OAuthCallbackResult) => void\n let rejectResult: (error: Error) => void\n const promise = new Promise<OAuthCallbackResult>((resolve, reject) => {\n resolveResult = resolve\n rejectResult = reject\n })\n // Attach a default no-op catch so a rejection without a consumer doesn't\n // raise an unhandled-rejection warning. Callers that DO `await promise`\n // still receive the rejection — Promises fan out to all listeners.\n promise.catch(() => {})\n\n let settled = false\n const resolveOnce = (value: OAuthCallbackResult) => {\n if (settled)\n return\n settled = true\n resolveResult(value)\n }\n const rejectOnce = (error: Error) => {\n if (settled)\n return\n settled = true\n rejectResult(error)\n }\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://${host}`)\n if (url.pathname !== path) {\n res.writeHead(404, { 'content-type': 'text/plain' })\n res.end('Not Found')\n return\n }\n\n // `no-store` prevents the browser from replaying the page (with the\n // now-spent code in its URL bar) when the user hits back, refreshes,\n // or restores the tab from history. The code is a one-time secret.\n const htmlHeaders = {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error\n res.writeHead(400, htmlHeaders)\n res.end(errorHtml(desc))\n rejectOnce(new Error(`OAuth authorization failed: ${desc}`))\n return\n }\n\n const code = url.searchParams.get('code')\n if (!code) {\n // No code, no error — likely a stray probe. Serve a minimal page,\n // don't resolve. The browser will eventually arrive with the real\n // redirect, or the caller will time out via signal.\n res.writeHead(400, { 'content-type': 'text/plain' })\n res.end('Missing \"code\" query parameter.')\n return\n }\n\n const state = url.searchParams.get('state') ?? undefined\n res.writeHead(200, htmlHeaders)\n res.end(SUCCESS_HTML)\n resolveOnce({ code, state })\n })\n\n // Surface socket errors that arrive before the request handler — e.g. EADDRINUSE\n // on a pinned port, or an early TLS-style probe that the parser rejects.\n server.on('error', (err) => {\n rejectOnce(err instanceof Error ? err : new Error(String(err)))\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n\n const addr = server.address() as AddressInfo | null\n if (!addr || typeof addr === 'string') {\n server.close()\n throw new Error('OAuth callback server did not bind to a TCP port')\n }\n\n // `closeServer` and `onAbort` reference each other — pre-declare both so\n // either ordering reads cleanly and the lint doesn't trip on the cycle.\n let closing: Promise<void> | undefined\n let closeServer: () => Promise<void>\n const onAbort = () => {\n rejectOnce(new Error('OAuth callback aborted'))\n void closeServer()\n }\n closeServer = (): Promise<void> => {\n if (closing)\n return closing\n closing = new Promise<void>((resolve) => {\n // `close()` on a Node http server waits for in-flight requests to drain.\n // The flow has already settled (or is being aborted), so we also call\n // `closeAllConnections()` to drop keep-alive sockets immediately — a\n // browser keeps the connection open after the success page, which would\n // otherwise hang the close for the keep-alive timeout (~5s).\n const anyServer = server as { closeAllConnections?: () => void }\n anyServer.closeAllConnections?.()\n server.close(() => {\n opts.signal?.removeEventListener('abort', onAbort)\n // If neither a callback nor an abort happened before close(), reject\n // pending awaits so callers don't hang. Tag the rejection as\n // 'aborted' when the signal already fired — Bun fires the abort\n // listener via a microtask, so server.close() may resolve first\n // even though semantically the abort came first.\n if (opts.signal?.aborted)\n rejectOnce(new Error('OAuth callback aborted'))\n else\n rejectOnce(new Error('OAuth callback server closed'))\n resolve()\n })\n })\n return closing\n }\n opts.signal?.addEventListener('abort', onAbort, { once: true })\n\n return {\n redirectUri: `http://${host}:${addr.port}${path}`,\n promise,\n close: closeServer,\n }\n}\n","/**\n * Interactive OAuth login orchestrator for one MCP server.\n *\n * Composes the three pieces shipped separately:\n * - `startOAuthCallback` (`./oauth-callback`) — local loopback HTTP\n * listener that catches the `?code=...` redirect.\n * - `McpOAuthProvider` (`./oauth-provider`) — SDK adapter that persists\n * tokens / client info via an injected `McpCredentialStore`.\n * - The MCP SDK's transport `authProvider` + `finishAuth` plumbing —\n * `client.connect()` triggers the SDK to call `redirectToAuthorization`\n * (which opens the browser); we wait for the loopback callback to\n * deliver the code, hand it to `transport.finishAuth`, and reconnect.\n *\n * Returns once tokens are persisted. Caller decides whether to immediately\n * reconnect the server (re-run `connectMcpServers` / `agent.warmup()`) or\n * defer to the next session activation. The returned `tools` array is\n * provided as a convenience — callers that want the connection live in\n * this call rather than rebuilding can wire them in directly.\n */\n\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpServerConfig } from '../types'\nimport type { OAuthCallbackHandle } from './oauth-callback'\nimport type { McpCredentialStore } from './oauth-provider'\nimport { startOAuthCallback } from './oauth-callback'\nimport { McpOAuthProvider } from './oauth-provider'\nimport { sseToJsonFetchIfNeeded } from './sse-to-json-fetch'\nimport { createTolerantClient } from './tolerant-client'\n\nexport interface LoginMcpServerOptions {\n /** Persistence — same store the bootstrap path reads from. */\n store: McpCredentialStore\n /**\n * Invoked with the authorization URL once it's ready. Hosts typically\n * (a) emit `mcp:auth:url` for the TUI, and (b) call `tryOpenBrowser`.\n * The URL is identical to the one passed to the `mcp:auth:url` hook\n * fired automatically — this callback is a synchronous hook for callers\n * that don't want to wire the agent hook machinery.\n */\n onAuthorizationUrl?: (url: URL) => void | Promise<void>\n /** Cancels the flow (esc / close modal / SIGINT). */\n signal?: AbortSignal\n /** Agent hooks. The flow emits `mcp:auth:url`/`success`/`error` when wired. */\n hooks?: Hookable<AgentHooks>\n /** Override `client_name` shown on consent screens. Default: 'zidane'. */\n clientName?: string\n /** Override the requested OAuth scope. */\n scope?: string\n /**\n * Override the loopback callback path. Default: `/callback`. Useful only\n * for servers that pinned a different path during registration.\n */\n callbackPath?: string\n /**\n * Maximum time to wait for the user to complete the browser flow, in ms.\n * The user can also cancel via `signal`. Default: 5 minutes.\n */\n timeoutMs?: number\n}\n\nexport interface LoginMcpServerResult {\n /** Stored OAuth tokens after a successful exchange. */\n tokens: NonNullable<ReturnType<McpOAuthProvider['tokens']>>\n /**\n * Upstream tool descriptors discovered after re-connecting with the new\n * tokens. Already filtered by the server's `enabledTools` / `disabledTools`\n * is NOT applied here — that's a bootstrap concern. Hosts that want filtering\n * should pass the result through `connectMcpServers` rebuild on the next\n * session activation rather than reusing this list verbatim.\n */\n tools: Array<{ name: string, description?: string | null, inputSchema?: unknown }>\n}\n\nconst DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60_000\n\n/**\n * Run the full interactive OAuth flow for `config`. Only supports `sse` and\n * `streamable-http` transports — `stdio` MCP servers don't speak OAuth.\n *\n * Throws on:\n * - Wrong transport.\n * - Abort signal.\n * - Browser-side error (user denied, server rejected, etc.).\n * - Code exchange failure.\n * - Post-exchange connect failure.\n *\n * Always closes the loopback callback server before returning, success or\n * failure.\n */\nexport async function loginMcpServer(\n config: McpServerConfig,\n options: LoginMcpServerOptions,\n): Promise<LoginMcpServerResult> {\n if (config.transport !== 'sse' && config.transport !== 'streamable-http')\n throw new Error(`MCP OAuth: cannot login for transport \"${config.transport}\" — only sse / streamable-http are supported`)\n if (!config.url)\n throw new Error(`MCP OAuth: server \"${config.name}\" is missing a url`)\n\n const hooks = options.hooks\n\n let callback: OAuthCallbackHandle | undefined\n try {\n callback = await startOAuthCallback({\n signal: options.signal,\n path: options.callbackPath,\n })\n\n const handle = callback\n const provider = new McpOAuthProvider({\n name: config.name,\n store: options.store,\n redirectUri: handle.redirectUri,\n clientName: options.clientName,\n scope: options.scope,\n onAuthorizationUrl: async (url) => {\n await hooks?.callHook('mcp:auth:url', { name: config.name, url: url.toString() })\n await options.onAuthorizationUrl?.(url)\n },\n })\n\n const transport = await createInteractiveTransport(config, provider)\n const client = await createTolerantClient({ name: 'zidane', version: '1.0.0' })\n\n // First connect → SDK has no tokens, so it calls\n // `provider.redirectToAuthorization(url)` (which routes to our\n // `onAuthorizationUrl`), then throws `UnauthorizedError`. This is the\n // expected path — we catch and wait on the loopback for the code.\n let needsAuth = false\n try {\n await client.connect(transport)\n }\n catch (err) {\n if (!isUnauthorizedError(err)) {\n await client.close().catch(() => {})\n throw err\n }\n needsAuth = true\n }\n\n if (needsAuth) {\n const { code } = await raceLoginCallback(handle, options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS, options.signal)\n // SDK exchanges code → tokens; provider.saveTokens persists them.\n await (transport as { finishAuth: (code: string) => Promise<void> }).finishAuth(code)\n // Reconnect with a FRESH transport. The previous one is already in\n // the \"started\" state from the failed connect — reusing it throws\n // \"StreamableHTTPClientTransport already started!\" inside\n // `transport.start()`. The SDK's canonical example\n // (`simpleOAuthClient.js`) uses the same pattern: recurse with a\n // new transport, reuse the client. Close the stale transport\n // first so its abort controllers + sockets don't leak.\n await transport.close().catch(() => {})\n const freshTransport = await createInteractiveTransport(config, provider)\n await client.connect(freshTransport)\n }\n\n const { tools } = await client.listTools()\n await client.close()\n\n const tokens = provider.tokens()\n if (!tokens)\n throw new Error(`MCP OAuth: login for \"${config.name}\" returned no tokens (server may have rejected the exchange silently)`)\n\n await hooks?.callHook('mcp:auth:success', { name: config.name })\n return { tokens, tools }\n }\n catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n await hooks?.callHook('mcp:auth:error', { name: config.name, error })\n throw error\n }\n finally {\n await callback?.close()\n }\n}\n\n/**\n * Build an `sse` / `streamable-http` transport pre-wired with `authProvider`.\n * Mirrors the bootstrap-side `createTransport` shape but inlined here so the\n * login flow doesn't depend on the bootstrap module's private export.\n */\nasync function createInteractiveTransport(config: McpServerConfig, authProvider: OAuthClientProvider) {\n if (config.transport === 'sse') {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n return new SSEClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n authProvider,\n })\n }\n // streamable-http\n const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js')\n return new StreamableHTTPClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n fetch: sseToJsonFetchIfNeeded(),\n authProvider,\n })\n}\n\nfunction isUnauthorizedError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as { name?: string, message?: string, constructor?: { name?: string } }\n if (e.name === 'UnauthorizedError' || e.constructor?.name === 'UnauthorizedError')\n return true\n return typeof e.message === 'string' && e.message.toLowerCase().startsWith('unauthorized')\n}\n\n/**\n * Wait on the callback handle, with a hard timeout AND honor the external\n * abort signal. Unlike `callback.promise` alone, this provides a deterministic\n * failure mode for \"user opened the browser then walked away\" — the modal\n * doesn't hang forever waiting for a redirect that may never come.\n */\nasync function raceLoginCallback(\n handle: OAuthCallbackHandle,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n): Promise<{ code: string, state?: string }> {\n if (signal?.aborted)\n throw new Error('OAuth login aborted')\n let timer: ReturnType<typeof setTimeout> | undefined\n let onAbort: (() => void) | undefined\n try {\n return await new Promise<{ code: string, state?: string }>((resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`OAuth login timed out after ${timeoutMs}ms`)),\n timeoutMs,\n )\n if (signal) {\n onAbort = () => reject(new Error('OAuth login aborted'))\n signal.addEventListener('abort', onAbort, { once: true })\n }\n handle.promise.then(resolve, reject)\n })\n }\n finally {\n if (timer)\n clearTimeout(timer)\n if (signal && onAbort)\n signal.removeEventListener('abort', onAbort)\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-DYQ6LgLP.js","names":[],"sources":["../src/compact/errors.ts","../src/compact/messages.ts","../src/compact/prompt.ts","../src/compact/compact.ts","../src/compact/restore.ts","../src/logger.ts","../src/mcp/oauth-callback.ts","../src/mcp/login.ts"],"sourcesContent":["/**\n * Typed errors thrown by the compaction helper.\n *\n * Lives in its own file so both the runner and the pure messages module\n * can import without circular dependencies.\n */\n\n/**\n * Raised when the caller's inputs make compaction meaningless before any\n * API call is attempted. Common cases:\n * - empty `turns`\n * - `keepTurns >= turns.length` (no older content to summarize)\n * - `'from'` / `'up_to'` anchor id not found in `turns`\n * - the resolved `toSummarize` slice has no text-bearing content\n *\n * Synchronous — thrown from `compactConversation()` before the provider\n * call so the caller can recover without a network round-trip.\n */\nexport class CompactInvalidInputError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CompactInvalidInputError'\n }\n}\n\n/**\n * Raised when the provider rejects the compaction request with\n * `prompt_too_long` (or an equivalent) and the head-truncation retry\n * budget has been exhausted. Callers can inspect `ptlRetries` to log\n * how far the retry loop got before giving up.\n */\nexport class CompactPromptTooLongError extends Error {\n constructor(message: string, public readonly ptlRetries: number) {\n super(message)\n this.name = 'CompactPromptTooLongError'\n }\n}\n","/**\n * Pure helpers for compaction — slicing, image stripping, head-truncation\n * on `prompt_too_long`, and the synthetic-turn builder.\n *\n * Everything in this file is total and side-effect-free. Inputs are never\n * mutated; helpers return fresh arrays whenever they modify content.\n */\n\nimport type {\n SessionContentBlock,\n SessionTurn,\n ToolResultContent,\n TurnUsage,\n} from '../types'\nimport { CompactInvalidInputError } from './errors'\n\n/**\n * What to summarize and what to preserve verbatim.\n *\n * - `'full'` — summarize everything, no preserved tail.\n * - `'tail'` — summarize everything before the last `keepTurns` turns.\n * - `{ kind: 'from', turnId }` — summarize from the anchor turn onward.\n * - `{ kind: 'up_to', turnId }` — summarize up to (and including) the anchor.\n */\nexport type CompactScope\n = | 'full'\n | 'tail'\n | { kind: 'from', turnId: string }\n | { kind: 'up_to', turnId: string }\n\nexport interface CompactionSlice {\n /** The portion of the conversation that will be summarized. */\n toSummarize: readonly SessionTurn[]\n /** The portion that stays verbatim in the post-compact history. */\n preserved: readonly SessionTurn[]\n}\n\n/**\n * Partition `turns` into `(toSummarize, preserved)` according to `scope`.\n *\n * Throws {@link CompactInvalidInputError} on degenerate inputs so the\n * caller doesn't pay for a doomed provider call:\n * - empty `turns`\n * - `scope: 'tail'` with `keepTurns >= turns.length` (nothing to summarize)\n * - `scope: { from | up_to }` with an anchor id that isn't in `turns`\n * - the resulting `toSummarize` slice contains no text-bearing content\n * (only system turns or empty content)\n *\n * Pure. Returns references to the original `SessionTurn` objects — the\n * caller can compare by identity (=== Object.is) to confirm.\n */\nexport function sliceForCompaction(\n turns: readonly SessionTurn[],\n scope: CompactScope,\n keepTurns: number,\n): CompactionSlice {\n if (turns.length === 0)\n throw new CompactInvalidInputError('No turns to compact.')\n\n let toSummarize: readonly SessionTurn[]\n let preserved: readonly SessionTurn[]\n\n if (scope === 'full') {\n toSummarize = turns\n preserved = []\n }\n else if (scope === 'tail') {\n const keep = Math.max(0, keepTurns)\n if (keep >= turns.length) {\n throw new CompactInvalidInputError(\n `Nothing to compact: keepTurns (${keep}) covers the entire conversation (${turns.length} turns).`,\n )\n }\n const safeCut = findSafeRoundBoundary(turns, turns.length - keep)\n toSummarize = turns.slice(0, safeCut)\n preserved = turns.slice(safeCut)\n }\n else if (scope.kind === 'from') {\n const idx = turns.findIndex(t => t.id === scope.turnId)\n if (idx < 0)\n throw new CompactInvalidInputError(`Anchor turn not found: \"${scope.turnId}\".`)\n // 'from' summarizes the recent portion (anchor onward); everything\n // before stays verbatim. Walk the anchor BACKWARD to the start of\n // the round containing it so neither half ends mid-round (avoids\n // orphan `tool_use` in `preserved` AND orphan `tool_result` at\n // the head of `toSummarize`).\n const safeIdx = findSafeRoundBoundary(turns, idx)\n preserved = turns.slice(0, safeIdx)\n toSummarize = turns.slice(safeIdx)\n }\n else {\n // 'up_to' summarizes the older portion (up to and including the anchor);\n // everything after stays verbatim. Same `tool_use ↔ tool_result`\n // adjacency rule as 'tail': cut backward if the proposed boundary\n // would orphan a `tool_use` at the end of `toSummarize`.\n const idx = turns.findIndex(t => t.id === scope.turnId)\n if (idx < 0)\n throw new CompactInvalidInputError(`Anchor turn not found: \"${scope.turnId}\".`)\n const safeCut = findSafeRoundBoundary(turns, idx + 1)\n toSummarize = turns.slice(0, safeCut)\n preserved = turns.slice(safeCut)\n }\n\n if (toSummarize.length === 0)\n throw new CompactInvalidInputError('Compaction scope resolved to zero turns.')\n if (!hasTextBearingContent(toSummarize))\n throw new CompactInvalidInputError('Compaction scope contains no text-bearing turns to summarize.')\n\n return { toSummarize, preserved }\n}\n\n/**\n * Replace every image block in `turns` with a `[image]` text marker.\n *\n * Covers two shapes:\n * - Top-level `{ type: 'image', ... }` content blocks on user turns.\n * - Image entries inside `tool_result.output` array form (multimodal\n * tool results — e.g. an MCP browser screenshot).\n *\n * Unconditional by design: even on vision-capable models, the summary\n * call doesn't benefit from raw image bytes (the model can't refer to\n * them after the summary lands), and stripping uniformly avoids\n * `prompt_too_long` on image-heavy sessions.\n *\n * Returns a fresh array; input turns / blocks are never mutated.\n */\nexport function stripImagesFromTurns(turns: readonly SessionTurn[]): SessionTurn[] {\n return turns.map(turn => stripImagesFromTurn(turn))\n}\n\nfunction stripImagesFromTurn(turn: SessionTurn): SessionTurn {\n let touched = false\n const nextContent: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'image') {\n touched = true\n nextContent.push({ type: 'text', text: '[image]' })\n continue\n }\n if (block.type === 'tool_result' && Array.isArray(block.output)) {\n const flat = stripImagesFromToolResult(block.output)\n if (flat) {\n touched = true\n nextContent.push({ ...block, output: flat })\n continue\n }\n }\n nextContent.push(block)\n }\n return touched ? { ...turn, content: nextContent } : turn\n}\n\n/**\n * Return a fresh `ToolResultContent[]` with images flattened to `[image]`\n * text placeholders, or `null` when no image blocks were present (caller\n * keeps the original input). Returning a new mutable array — never the\n * input — keeps the `tool_result.output` slot's mutable type contract\n * satisfied without leaky `as`-casts at the call site.\n */\nfunction stripImagesFromToolResult(parts: readonly ToolResultContent[]): ToolResultContent[] | null {\n let touched = false\n const out: ToolResultContent[] = []\n for (const part of parts) {\n if (part.type === 'image') {\n touched = true\n out.push({ type: 'text', text: '[image]' })\n }\n else {\n out.push(part)\n }\n }\n return touched ? out : null\n}\n\n/**\n * Drop the oldest \"round\" from `turns` and return a fresh array. Used by\n * the PTL retry path to shrink the prompt one round at a time.\n *\n * A round is a contiguous `[user, assistant?, tool_results?]` group. The\n * function walks forward from index 0, advances through the user turn\n * and any trailing assistant + tool-result turns belonging to the same\n * exchange, and returns the remainder.\n *\n * Adjacency-safe: when the oldest user turn carries `tool_result` blocks\n * answering an assistant turn ahead of it (rare — happens during\n * resume), the function keeps walking until the next clean boundary so\n * the resulting array still respects every provider's `tool_use ↔\n * tool_result` adjacency rule.\n *\n * Returns `turns` unchanged when only one round (or less) remains — the\n * caller is expected to interpret that as \"cannot shrink further\" and\n * give up the retry loop.\n */\nexport function truncateHeadForPtlRetry(turns: readonly SessionTurn[]): SessionTurn[] {\n if (turns.length <= 1)\n return turns.slice()\n\n // Find the first turn that opens a clean conversational round we can\n // drop ending on — start with the first `user` turn (it's the natural\n // start-of-round marker).\n const firstUserIdx = turns.findIndex(t => t.role === 'user')\n if (firstUserIdx < 0)\n return turns.slice()\n\n // Skip past every turn that belongs to this round: the user turn\n // itself, the assistant reply (if any), and the next user turn whose\n // content is *only* tool_result blocks (an immediate follow-up that\n // closes out tool calls). Stop when we reach the next round-opening\n // user turn (i.e. one that carries non-tool_result content).\n let cursor = firstUserIdx + 1\n while (cursor < turns.length) {\n const turn = turns[cursor]\n if (turn.role === 'assistant') {\n cursor++\n continue\n }\n if (turn.role === 'user' && isToolResultsOnlyTurn(turn)) {\n cursor++\n continue\n }\n break\n }\n\n // If the cursor walked all the way to the end, refuse to truncate —\n // the remaining slice would be empty, which is never useful.\n if (cursor >= turns.length)\n return turns.slice()\n\n return turns.slice(cursor)\n}\n\nfunction isToolResultsOnlyTurn(turn: SessionTurn): boolean {\n if (turn.content.length === 0)\n return false\n return turn.content.every(block => block.type === 'tool_result')\n}\n\n/**\n * Walk `proposedCut` backward to the nearest position where splitting at\n * that index produces a round-boundary-clean partition — i.e. neither\n * half breaks the `tool_use ↔ tool_result` adjacency rule that every\n * provider (most strictly Anthropic) enforces.\n *\n * The hazardous case: the proposed cut lands BETWEEN an assistant turn\n * carrying `tool_call` blocks and the user turn carrying the matching\n * `tool_result` blocks. Then `toSummarize` ends with an orphan\n * `tool_use` (provider 400 on the summarization request) AND `preserved`\n * starts with an orphan `tool_result` (provider 400 on the next live\n * agent run against the wire-level cutoff output).\n *\n * Algorithm: keep walking `cut` backward as long as `turns[cut - 1]`\n * is an assistant turn with at least one `tool_call` block. The walk\n * stops when:\n * - we reach `cut = 0` (slice would be empty; caller's existing\n * \"scope resolved to zero turns\" guard handles it), or\n * - the trailing turn is user-role (clean — model emits no pending\n * tool_use from user turns), or\n * - the trailing turn is assistant text without tool_use (clean —\n * text-only response is a complete round).\n *\n * Returning the adjusted cut over-preserves the tail relative to the\n * caller's request — `keepTurns` is interpreted as the MINIMUM number\n * of turns kept verbatim, not the exact count.\n */\nfunction findSafeRoundBoundary(turns: readonly SessionTurn[], proposedCut: number): number {\n let cut = Math.max(0, Math.min(turns.length, proposedCut))\n while (cut > 0 && hasPendingToolUse(turns[cut - 1]))\n cut--\n return cut\n}\n\n/** Does this turn end with any unanswered `tool_use` blocks? */\nfunction hasPendingToolUse(turn: SessionTurn): boolean {\n if (turn.role !== 'assistant')\n return false\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n return true\n }\n return false\n}\n\nfunction hasTextBearingContent(turns: readonly SessionTurn[]): boolean {\n for (const turn of turns) {\n if (turn.role === 'system')\n continue\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim().length > 0)\n return true\n if (block.type === 'tool_call' || block.type === 'tool_result')\n return true\n }\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Summary turn builder\n// ---------------------------------------------------------------------------\n\n/**\n * Maximum length of an anchor turn's textual preview, in characters. Long\n * enough to give the model recognizable context (the first paragraph of\n * a typical user message), short enough that it doesn't blow the\n * cache-stability invariant for the prompt prefix.\n */\nexport const ANCHOR_PREVIEW_MAX_CHARS = 200\n\n/**\n * Extract the first ~200 chars of text-bearing content from a turn — the\n * preview surfaced in `from` / `up_to` direction prompts so the model\n * knows where the slice begins.\n */\nexport function anchorPreviewFor(turn: SessionTurn): string {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim().length > 0) {\n const flat = block.text.replace(/\\s+/g, ' ').trim()\n return flat.length > ANCHOR_PREVIEW_MAX_CHARS\n ? `${flat.slice(0, ANCHOR_PREVIEW_MAX_CHARS - 1)}…`\n : flat\n }\n }\n return '(no preview available)'\n}\n\n/**\n * Input shape for {@link summaryToTurn}. Designed to align with the\n * fields of `CompactResult` so a caller can spread the runner's output\n * with a single `replacesTurnIds` rename — no field-by-field unpacking\n * required.\n *\n * Primitive shape (no `CompactResult` import) keeps this module free of\n * dependencies on the runner — `compact.ts` imports from here, not the\n * other way around.\n */\nexport interface SummaryToTurnInput {\n /** Summary text — typically `CompactResult.summary`. */\n summary: string\n /** Turn ids being replaced — typically `CompactResult.summarizedTurnIds`. */\n replacesTurnIds: readonly string[]\n /** Model id that produced the summary. */\n model: string\n /** Token usage from the summary call. */\n usage: TurnUsage\n /** Defaults to `Date.now()` when omitted. */\n compactedAt?: number\n}\n\n/**\n * Build a synthetic `SessionTurn` carrying a single `compact-summary`\n * block, ready to append to a session.\n *\n * The turn's role is `'user'` so it sits at a conversational boundary\n * the way the model expects. The caller is responsible for\n * `session.appendTurns([turn])`. The id is freshly generated via\n * `crypto.randomUUID()` so collisions are statistically impossible.\n *\n * Typical use after running `compactConversation`:\n *\n * ```ts\n * const result = await compactConversation({ provider, turns })\n * const turn = summaryToTurn({\n * summary: result.summary,\n * replacesTurnIds: result.summarizedTurnIds,\n * model: result.model,\n * usage: result.usage,\n * })\n * await session.appendTurns([turn])\n * ```\n */\nexport function summaryToTurn(input: SummaryToTurnInput): SessionTurn {\n const compactedAt = input.compactedAt ?? Date.now()\n return {\n id: crypto.randomUUID(),\n role: 'user',\n content: [{\n type: 'compact-summary',\n replacesTurnIds: input.replacesTurnIds,\n summary: input.summary,\n model: input.model,\n usage: input.usage,\n compactedAt,\n }],\n createdAt: compactedAt,\n }\n}\n","/**\n * Pure prompt builders for conversation compaction.\n *\n * The builders produce the **system prompt** for a no-tools summary call.\n * They are total functions of `(direction, anchorPreview?)` and produce\n * byte-stable output for the same inputs — that's load-bearing for the\n * provider's prompt cache: repeated compactions in the same host process\n * share the same prefix and read cache instead of writing it.\n *\n * Inspired by Claude Code's `services/compact/prompt.ts` — same 9-section\n * scaffold, same `<analysis> + <summary>` envelope, same no-tools\n * preamble. Adapted to zidane-specific wording (no \"Claude\" references)\n * and trimmed of the bits that don't apply (no `marble_origami`-style\n * query sources, no compaction-fingerprint header).\n */\n\n/** Identifier for the section of the conversation being summarized. */\nexport type CompactDirection = 'full' | 'tail' | 'from' | 'up_to'\n\nexport interface CompactPromptOptions {\n direction: CompactDirection\n /**\n * Short preview of the anchor turn's text — only used by `'from'` and\n * `'up_to'`. Pass the last ~200 chars of the anchor turn so the model\n * has a recognizable handle on where the slice begins / ends. Empty /\n * undefined for `'full'` and `'tail'`.\n */\n anchorPreview?: string\n}\n\n/**\n * Function shape for callers that want to swap in a domain-specific\n * summary prompt (security review handoff, support-ticket continuation,\n * etc.) without touching the runner. Default: {@link buildCompactPrompt}.\n */\nexport type CompactPromptBuilder = (opts: CompactPromptOptions) => string\n\n// ---------------------------------------------------------------------------\n// Composable blocks — each one is a frozen string for cache-stability and\n// quoted verbatim from inside the named builders below. Exported so\n// callers building custom prompts can stitch them together.\n// ---------------------------------------------------------------------------\n\n/**\n * No-tools guard. The runner sends `tools: []` to the provider already,\n * but some models still hallucinate tool-call intent on a long\n * conversation. The prose guard is cheap insurance.\n */\nexport const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.\n\n- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.\n- You already have all the context you need in the conversation above.\n- Tool calls will be REJECTED and will waste your only turn — you will fail the task.\n- Your entire response must be plain text: an <analysis> block followed by a <summary> block.`\n\n/**\n * Body shared by every direction. Lays out the 9-section scaffold,\n * mirrors Claude Code's `BASE_COMPACT_PROMPT` so a model already trained\n * on the layout produces the same shape.\n */\nexport const BASE_INSTRUCTIONS = `Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.\n\nThis summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.\n\nBefore providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:\n\n1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:\n - The user's explicit requests and intents\n - Your approach to addressing the user's requests\n - Key decisions, technical concepts and code patterns\n - Specific details like file names, full code snippets, function signatures, file edits\n - Errors that you ran into and how you fixed them\n - Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.\n2. Double-check for technical accuracy and completeness.\n\nYour summary, wrapped in <summary> tags, must include the following sections:\n\n1. Primary Request and Intent\n2. Key Technical Concepts\n3. Files and Code Sections (with paths; include code snippets only when load-bearing)\n4. Errors and fixes\n5. Problem Solving\n6. All user messages (list ALL non-tool user messages, verbatim)\n7. Pending Tasks\n8. Current Work\n9. Optional Next Step (include direct quotes from the most recent conversation when relevant)`\n\n/** Trailer prompting the model to begin. Same on every direction. */\nexport const TRAILER = 'Provide your <analysis> and <summary> now.'\n\n// ---------------------------------------------------------------------------\n// Direction blurbs — the only knob that varies between builders. Each is\n// frozen and concise so cache-key drift is impossible between calls with\n// the same direction.\n// ---------------------------------------------------------------------------\n\nconst FULL_BLURB = `## Scope\nThe conversation above is being summarized in full. Capture every section the user might need to resume from a fresh context.`\n\nconst TAIL_BLURB = `## Scope\nSummarize the conversation above. The most recent turns will be preserved verbatim alongside your summary, so prioritize older context that would otherwise be lost.`\n\nconst FROM_BLURB = `## Scope\nSummarize the conversation FROM the marked anchor onward (the recent portion). Everything before the anchor will be preserved verbatim.\n\nAnchor turn (preview):\n%ANCHOR_PREVIEW%`\n\nconst UP_TO_BLURB = `## Scope\nSummarize the conversation UP TO the marked anchor (the older portion). Everything from the anchor onward will be preserved verbatim — your summary's job is to compress the prior context the user can no longer scroll back to.\n\nAnchor turn (preview):\n%ANCHOR_PREVIEW%`\n\n// ---------------------------------------------------------------------------\n// Named builders — discoverable, single-purpose, cache-stable.\n// ---------------------------------------------------------------------------\n\n/** Compose the full prompt with a custom direction-blurb. Internal helper. */\nfunction compose(blurb: string): string {\n return [NO_TOOLS_PREAMBLE, BASE_INSTRUCTIONS, blurb, TRAILER].join('\\n\\n')\n}\n\nexport function buildFullCompactPrompt(): string {\n return compose(FULL_BLURB)\n}\n\nexport function buildTailCompactPrompt(): string {\n return compose(TAIL_BLURB)\n}\n\nexport function buildFromCompactPrompt(anchorPreview: string): string {\n return compose(FROM_BLURB.replace('%ANCHOR_PREVIEW%', anchorPreview))\n}\n\nexport function buildUpToCompactPrompt(anchorPreview: string): string {\n return compose(UP_TO_BLURB.replace('%ANCHOR_PREVIEW%', anchorPreview))\n}\n\n/**\n * Default public builder. Dispatches by direction to the four named\n * builders. Throws when `from` / `up_to` are passed without an\n * `anchorPreview` — those scopes only make sense with an anchor and a\n * silent fallback would produce a prompt that doesn't tell the model\n * where the slice begins.\n */\nexport const buildCompactPrompt: CompactPromptBuilder = (opts) => {\n switch (opts.direction) {\n case 'full':\n return buildFullCompactPrompt()\n case 'tail':\n return buildTailCompactPrompt()\n case 'from': {\n const preview = opts.anchorPreview ?? ''\n if (preview.length === 0)\n throw new Error('buildCompactPrompt: `anchorPreview` is required for direction \"from\".')\n return buildFromCompactPrompt(preview)\n }\n case 'up_to': {\n const preview = opts.anchorPreview ?? ''\n if (preview.length === 0)\n throw new Error('buildCompactPrompt: `anchorPreview` is required for direction \"up_to\".')\n return buildUpToCompactPrompt(preview)\n }\n }\n}\n","/**\n * `compactConversation` — drive a one-shot summary call against a\n * provider and return a structured envelope describing what was\n * summarized and what stays verbatim.\n *\n * This is the harness primitive. It does not mutate the session, does\n * not own re-entrancy guards, does not enforce a circuit breaker — those\n * belong to whoever wires compaction into a control loop (the agent\n * loop's autocompact trigger, the TUI's session-details modal action,\n * an SDK consumer's batch job).\n *\n * Architecture: mirrors {@link generateSessionTitle} verbatim. The\n * provider's `stream()` is the one-shot completion primitive zidane\n * already exposes; this module reuses it directly instead of layering\n * a second abstraction on top.\n *\n * Caching: the system prompt is byte-stable per `(direction,\n * anchorPreview)` pair (see `./prompt.ts`). Repeated compactions in the\n * same host process share that prefix and read provider cache instead\n * of writing it.\n */\n\nimport type { Provider } from '../providers'\nimport type {\n SessionContentBlock,\n SessionMessage,\n SessionTurn,\n ThinkingLevel,\n TurnUsage,\n} from '../types'\nimport type { CompactionSlice, CompactScope } from './messages'\nimport type { CompactPromptBuilder } from './prompt'\nimport { ensureEndsWithUserMessage, ensureToolResultPairing } from '../session/messages'\nimport { toolOutputByteLength } from '../types'\nimport { CompactPromptTooLongError } from './errors'\nimport {\n anchorPreviewFor,\n sliceForCompaction,\n stripImagesFromTurns,\n truncateHeadForPtlRetry,\n} from './messages'\nimport { buildCompactPrompt } from './prompt'\nimport { utf8ByteLength } from './utils'\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport interface CompactOptions {\n /** Provider used for the summary call. Called with empty tools list. */\n provider: Provider\n /** Conversation to compact, in chronological order. */\n turns: readonly SessionTurn[]\n /**\n * What to summarize. Default: `'tail'` — summarize everything before\n * the last `keepTurns` turns. See {@link CompactScope}.\n */\n scope?: CompactScope\n /**\n * Trailing turns left untouched when `scope: 'tail'`. Default: 4.\n * Matches `AgentBehavior.compactKeepTurns` so a host that uses both\n * mechanisms shares one knob.\n */\n keepTurns?: number\n /** Model id used for the summary call. Default: `provider.meta.defaultModel`. */\n model?: string\n /**\n * Maximum tokens the summary itself can occupy. Default: 20_000 — the\n * p99.99 of summary output size in Claude Code's `COMPACT_MAX_OUTPUT`.\n * Reasonable for any model with a 200k+ window.\n */\n maxOutputTokens?: number\n /** Optional reasoning level. Default: `'off'` — summarization rarely benefits from thinking. */\n thinking?: ThinkingLevel\n /** Optional cancellation signal forwarded to `provider.stream()`. */\n signal?: AbortSignal\n /**\n * Retries with head-truncation when the provider reports\n * `prompt_too_long`. Each retry drops the oldest conversational round\n * before re-issuing the call. Default: 3. Set 0 to fail-fast.\n */\n maxPtlRetries?: number\n /**\n * Optional builder override. The default builder dispatches by\n * direction to the named builders in `./prompt.ts`; pass a custom\n * function to swap in a domain-specific summary prompt.\n */\n prompt?: CompactPromptBuilder\n /**\n * Lifecycle hook for observability. Fires once per provider call,\n * including retries. The `kind` field tells you whether the call is\n * the initial attempt, a head-truncated retry, or a transient-error\n * retry — useful for surfacing progress in a UI spinner.\n */\n onAttempt?: (event: { attempt: number, kind: 'initial' | 'ptl-retry' | 'transient-retry' }) => void\n}\n\n/**\n * Directive appended as the trailing user message when the conversation\n * to summarize ends with an assistant turn. Mirrors the system-prompt\n * TRAILER text so the model gets the same imperative cue from both\n * surfaces — system instruction + final user message.\n *\n * Compaction-specific: paired with the compaction system prompt. Other\n * call-sites of {@link ensureEndsWithUserMessage} pass their own directive\n * (or the neutral `DEFAULT_USER_TAIL_DIRECTIVE`) so unrelated codepaths\n * don't inject a \"give me a summary\" cue into a regular turn.\n */\nconst SUMMARY_USER_DIRECTIVE = 'Provide your <analysis> and <summary> now.'\n\nexport interface CompactResult {\n /** The summary text, with any `<analysis>` block stripped. */\n summary: string\n /** Token usage from the (last successful) summary call. */\n usage: TurnUsage\n /** Model id used to produce the summary. */\n model: string\n /** Number of `prompt_too_long` retries that fired before success. 0 on first-try success. */\n ptlRetries: number\n /**\n * Turn ids actually covered by the summary — i.e., the ids of turns\n * that made it to the provider. **PTL-retry safe**: when head-truncation\n * shrank the scope, only the surviving (post-truncation) turn ids\n * appear here. Drives `summaryToTurn`'s `replacesTurnIds`; the wire-\n * level cutoff in `applyCompactSummaryCutoff` reads the same field\n * from the persisted marker.\n */\n summarizedTurnIds: readonly string[]\n /**\n * Turn ids dropped by PTL head-truncation before reaching the\n * provider. **Empty on first-try success.** These turns are NOT\n * covered by the summary — callers should either leave them in the\n * conversation history (they stay visible to the model verbatim,\n * since the id-based cutoff only elides ids the marker explicitly\n * claims) or surface a \"compaction lost N turns of context\" warning.\n *\n * The harness keeps these in the wire-level conversation by default;\n * the safety contract is \"the summary describes exactly what's in\n * `summarizedTurnIds` — nothing more, nothing less\".\n */\n droppedDueToPtl: readonly string[]\n /** Turns left untouched (the preserved tail / verbatim slice). */\n preservedTurns: readonly SessionTurn[]\n /** Byte length of turns actually summarized (post-PTL-truncation). */\n beforeBytes: number\n /** UTF-8 byte length of the summary text (rough \"after\" measure). */\n afterBytes: number\n}\n\nexport { CompactInvalidInputError, CompactPromptTooLongError } from './errors'\nexport type { CompactionSlice, CompactScope, SummaryToTurnInput } from './messages'\nexport type { CompactDirection, CompactPromptBuilder, CompactPromptOptions } from './prompt'\n\n// ---------------------------------------------------------------------------\n// Defaults — locked constants so behavior is predictable across host setups.\n// ---------------------------------------------------------------------------\n\n/** Default `keepTurns` for `scope: 'tail'`. Matches `AgentBehavior.compactKeepTurns`. */\nconst DEFAULT_KEEP_TURNS = 4\n\n/** Default max output tokens for the summary call. */\nconst DEFAULT_MAX_OUTPUT_TOKENS = 20_000\n\n/** Default PTL retry budget. */\nconst DEFAULT_MAX_PTL_RETRIES = 3\n\n/** Maximum transient-error retries before giving up. Independent of PTL. */\nconst TRANSIENT_RETRY_BUDGET = 2\n\n// ---------------------------------------------------------------------------\n// Runner\n// ---------------------------------------------------------------------------\n\nexport async function compactConversation(opts: CompactOptions): Promise<CompactResult> {\n // ---- Phase 1: pure preflight (no API call) -----------------------------\n // Throws CompactInvalidInputError on degenerate inputs so the caller can\n // bail without spending a network round-trip on a doomed request.\n const slice = sliceForCompaction(\n opts.turns,\n opts.scope ?? 'tail',\n opts.keepTurns ?? DEFAULT_KEEP_TURNS,\n )\n\n const direction = scopeToDirection(opts.scope ?? 'tail')\n const anchorPreview = direction === 'from' || direction === 'up_to'\n ? anchorPreviewFor(anchorTurnFor(slice, opts.scope!))\n : undefined\n\n const builder = opts.prompt ?? buildCompactPrompt\n const systemPrompt = builder({\n direction,\n ...(anchorPreview !== undefined ? { anchorPreview } : {}),\n })\n\n const model = opts.model ?? opts.provider.meta.defaultModel\n const maxOutputTokens = opts.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS\n const maxPtlRetries = Math.max(0, opts.maxPtlRetries ?? DEFAULT_MAX_PTL_RETRIES)\n\n // ---- Phase 2: retry-aware provider call --------------------------------\n // Strips images once up-front (cheap, deterministic) so each retry\n // doesn't re-flatten the same blocks.\n let workingTurns: readonly SessionTurn[] = stripImagesFromTurns(slice.toSummarize)\n let ptlRetries = 0\n let transientRetries = 0\n let attempt = 0\n\n while (true) {\n attempt++\n const kind: 'initial' | 'ptl-retry' | 'transient-retry'\n = ptlRetries > 0 ? 'ptl-retry' : transientRetries > 0 ? 'transient-retry' : 'initial'\n opts.onAttempt?.({ attempt, kind })\n\n try {\n // Build wire messages, then run the same defensive pairing pass\n // the agent loop uses on every live turn (see `applyPairingRepair`\n // in `src/loop.ts`). A session can contain orphan `tool_use` blocks\n // even after `sliceForCompaction`'s round-boundary walk: PTL\n // head-truncation shrinks the working set after the first attempt\n // (which can lop the head off an in-flight round), prior-loop\n // crashes can leave dangling tool_use ids in the persisted history,\n // and `turnsToMessages` itself can drop the only `text` block on\n // an assistant turn carrying signed thinking that has no signature.\n // Without this pass the summarizer fires a request the provider 400s\n // on with `'tool_use' ids were found without 'tool_result' blocks\n // immediately after`, which surfaces to the user as\n // \"compaction failed\" with a raw provider message.\n const paired = ensureToolResultPairing(turnsToMessages(workingTurns))\n const messages = ensureEndsWithUserMessage(paired, opts.provider, SUMMARY_USER_DIRECTIVE)\n const { summary, usage } = await runOnce({\n provider: opts.provider,\n model,\n system: systemPrompt,\n messages,\n maxTokens: maxOutputTokens,\n ...(opts.thinking !== undefined ? { thinking: opts.thinking } : {}),\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n })\n\n // PTL retries shrink the scope — the marker should ONLY claim\n // ownership of turns that actually fed the summary. Compute the\n // dropped set up-front (cheap; turn counts are O(100s) at worst)\n // so callers can decide whether to warn the user.\n const workingIds = new Set(workingTurns.map(t => t.id))\n const droppedDueToPtl: string[] = []\n if (ptlRetries > 0) {\n for (const t of slice.toSummarize) {\n if (!workingIds.has(t.id))\n droppedDueToPtl.push(t.id)\n }\n }\n\n return {\n summary,\n usage: { ...usage, modelId: usage.modelId ?? model },\n model,\n ptlRetries,\n summarizedTurnIds: workingTurns.map(t => t.id),\n droppedDueToPtl,\n preservedTurns: slice.preserved,\n beforeBytes: bytesIn(workingTurns),\n afterBytes: utf8ByteLength(summary),\n }\n }\n catch (err) {\n // Abort flows propagate unchanged so callers can pattern-match.\n if (isAbortError(err, opts.signal))\n throw err\n\n if (isPromptTooLongError(err)) {\n if (ptlRetries >= maxPtlRetries) {\n throw new CompactPromptTooLongError(\n `Compaction failed: prompt_too_long after ${ptlRetries} retries.`,\n ptlRetries,\n )\n }\n const truncated = truncateHeadForPtlRetry(workingTurns)\n if (truncated.length === workingTurns.length) {\n // Nothing more to truncate — refuse to spin.\n throw new CompactPromptTooLongError(\n `Compaction failed: prompt_too_long and conversation cannot be shrunk further.`,\n ptlRetries,\n )\n }\n workingTurns = truncated\n ptlRetries++\n continue\n }\n\n if (isTransientError(err) && transientRetries < TRANSIENT_RETRY_BUDGET) {\n transientRetries++\n continue\n }\n\n throw err\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\ninterface RunOnceOptions {\n provider: Provider\n model: string\n system: string\n messages: SessionMessage[]\n maxTokens: number\n thinking?: ThinkingLevel\n signal?: AbortSignal\n}\n\ninterface RunOnceResult {\n summary: string\n usage: TurnUsage\n}\n\n/**\n * Single provider call. Strips the `<analysis>` block and returns the\n * post-trim summary text along with the usage report.\n *\n * Why not just use `result.text`: some providers stream deltas without\n * emitting a concatenated `text` field at the end (or set it to an\n * empty string when the stream finished via tool-call). Accumulating\n * deltas in `text` and falling back to `result.text` mirrors\n * `generateSessionTitle` and handles every adapter shape.\n */\nasync function runOnce(opts: RunOnceOptions): Promise<RunOnceResult> {\n let streamed = ''\n const result = await opts.provider.stream(\n {\n model: opts.model,\n system: opts.system,\n tools: [],\n messages: opts.messages,\n maxTokens: opts.maxTokens,\n ...(opts.thinking !== undefined ? { thinking: opts.thinking } : {}),\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n },\n {\n onText: (delta) => { streamed += delta },\n },\n )\n\n const raw = streamed.length > 0 ? streamed : result.text\n let summary = stripAnalysisBlock(raw).trim()\n\n // Reasoning-model fallback. Some models (Qwen-thinking, GLM, DeepSeek\n // reasoning variants, …) write the entire response into their\n // reasoning channel rather than `delta.content`. From this layer the\n // `text` stream is empty, but the assistant message carries\n // `{ type: 'thinking', text }` blocks containing the actual summary\n // (often still wrapped in `<analysis>` / `<summary>` tags from the\n // prompt). Walk those blocks and apply the same extraction waterfall\n // before giving up. Saves the user from \"model misbehaved, retry\" on\n // every compaction when their selected model happens to reason\n // instead of speak.\n if (summary.length === 0) {\n const thinking = collectThinkingText(result.assistantMessage)\n if (thinking.length > 0)\n summary = stripAnalysisBlock(thinking).trim()\n }\n\n if (summary.length === 0) {\n // Truly empty — aborted stream, tool-call-only turn, or a model\n // that refused to summarize. `stripAnalysisBlock` already fell back\n // to raw `<analysis>` content when the model wrapped everything in\n // the wrong tag; the thinking fallback above caught the reasoning-\n // channel case. Reaching this branch means there's nothing usable\n // anywhere in the response.\n throw new Error('Compaction failed: provider returned no summary text.')\n }\n return { summary, usage: result.usage }\n}\n\n/**\n * Concatenate every `thinking`-block's text from an assistant message.\n *\n * Reasoning models route their output through provider-specific\n * \"thinking\" channels (Anthropic native thinking, OpenAI o-series\n * reasoning, OpenAI-compat `reasoning_content`, OpenRouter\n * `reasoning_details`). All of them surface as `{ type: 'thinking', text }`\n * blocks on the normalized `SessionMessage.content` by the time the\n * provider's `stream()` returns. Walking the blocks is provider-agnostic\n * — works for every adapter without bespoke per-provider handling.\n *\n * Returns the empty string when no thinking blocks are present, so the\n * caller's \"did we get anything?\" check stays a single `.length === 0`.\n */\nfunction collectThinkingText(message: SessionMessage): string {\n const parts: string[] = []\n for (const block of message.content) {\n if (block.type === 'thinking' && typeof block.text === 'string' && block.text.length > 0)\n parts.push(block.text)\n }\n return parts.join('\\n')\n}\n\n/**\n * Extract the summary text from the provider's response, peeling off\n * any envelope the model wrapped it in.\n *\n * The compact prompt asks for `<analysis>...</analysis><summary>...</summary>`\n * but real models drift from the format. This function tries four\n * extraction paths in order of strictness:\n *\n * 1. **Strict path** — strip `<analysis>...</analysis>` blocks and\n * extract the `<summary>...</summary>` envelope. Matches the\n * prompt-following ideal.\n * 2. **Loose path** — same strip, but accept whatever's outside the\n * `<analysis>` tags as the summary (no `<summary>` envelope\n * required). Handles models that drop the wrapper but keep the\n * analysis.\n * 3. **Analysis-as-summary fallback** — when the entire response is a\n * single `<analysis>` block (model conflated the two concepts),\n * return the analysis content. Better than failing the compaction\n * and forcing the user to retry.\n * 4. **Raw passthrough** — no recognized envelope. Return the text\n * as-is and let `runOnce`'s empty-check decide whether to throw.\n *\n * Matches Claude Code's `formatCompactSummary` for path (1) + (2) and\n * adds (3) as a graceful-degradation layer we ran into in the wild\n * (smaller / non-Anthropic models sometimes produce analysis-only).\n */\nfunction stripAnalysisBlock(text: string): string {\n // Strict + loose: strip `<analysis>...</analysis>` non-greedy + dot-all\n // (no `s` flag in Bun's V8 — `[\\s\\S]` does the same).\n const analysisStripped = text.replace(/<analysis>[\\s\\S]*?<\\/analysis>/g, '')\n\n // Path 1: explicit `<summary>...</summary>` envelope.\n const summaryMatch = analysisStripped.match(/<summary>([\\s\\S]*?)<\\/summary>/)\n if (summaryMatch)\n return summaryMatch[1]\n\n // Path 2: anything outside the `<analysis>` block.\n if (analysisStripped.trim().length > 0)\n return analysisStripped\n\n // Path 3: response was nothing but an `<analysis>` block — extract\n // its content and use that as the summary. The model meant to give\n // us a summary; it just wrapped it in the wrong tag.\n const analysisMatch = text.match(/<analysis>([\\s\\S]*?)<\\/analysis>/)\n if (analysisMatch)\n return analysisMatch[1]\n\n // Path 4: no envelope of any shape — return verbatim.\n return text\n}\n\n/**\n * Convert turns into the wire-level `SessionMessage[]` shape. Drops\n * system turns (rare; they'd confuse a summary call), and inlines any\n * pre-existing `compact-summary` markers as plain text so the\n * provider's wire converter doesn't have to know about zidane's\n * internal block type.\n *\n * Inlining (instead of dropping) is intentional: a session already\n * compacted once still contains the prior summary as load-bearing\n * context. Surfacing it as `[Previous compaction summary]\\n…` lets the\n * new summarization integrate it instead of forgetting it.\n */\nfunction turnsToMessages(turns: readonly SessionTurn[]): SessionMessage[] {\n const out: SessionMessage[] = []\n for (const turn of turns) {\n if (turn.role === 'system')\n continue\n const content: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'compact-summary') {\n content.push({\n type: 'text',\n text: `[Previous compaction summary]\\n${block.summary}`,\n })\n continue\n }\n // Strip `thinking` blocks. They're the model's internal reasoning,\n // signed by the producing provider (`signatureProducer: 'anthropic'`),\n // and only valid in a follow-up request when that same request has\n // extended thinking enabled. The compaction call deliberately runs\n // with `thinking: undefined` (summarization rarely benefits from\n // reasoning, and we don't want to pay the budget), so re-sending\n // thinking blocks puts Anthropic in an inconsistent state: it\n // accepts the request without 400-ing but silently returns an\n // empty `text` response. The user-visible symptom is \"Compaction\n // failed: provider returned no summary text\" on sessions where\n // any turn used extended thinking. Thinking is opaque-by-design;\n // dropping it for the summary call doesn't lose conversational\n // content (the model's visible text is in adjacent `text` blocks).\n if (block.type === 'thinking')\n continue\n content.push(block)\n }\n if (content.length === 0)\n continue\n out.push({ role: turn.role, content })\n }\n return out\n}\n\nfunction scopeToDirection(scope: CompactScope): 'full' | 'tail' | 'from' | 'up_to' {\n if (scope === 'full' || scope === 'tail')\n return scope\n return scope.kind\n}\n\nfunction anchorTurnFor(slice: CompactionSlice, scope: CompactScope): SessionTurn {\n // Caller-side invariant: `sliceForCompaction` already verified the anchor\n // exists when scope.kind is set. We re-locate it here so the preview is\n // built from the exact same turn the prompt builder will reference.\n if (typeof scope === 'string')\n throw new Error('anchorTurnFor: scope must be object form')\n if (scope.kind === 'from')\n return slice.toSummarize[0]!\n return slice.toSummarize[slice.toSummarize.length - 1]!\n}\n\nfunction bytesIn(turns: readonly SessionTurn[]): number {\n let total = 0\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'text')\n total += utf8ByteLength(block.text)\n else if (block.type === 'tool_result')\n total += toolOutputByteLength(block.output)\n else if (block.type === 'tool_call')\n total += utf8ByteLength(JSON.stringify(block.input))\n else if (block.type === 'thinking')\n total += utf8ByteLength(block.text)\n }\n }\n return total\n}\n\n// ---------------------------------------------------------------------------\n// Error classification\n// ---------------------------------------------------------------------------\n\n/**\n * Provider-agnostic predicate for the \"prompt is too long\" rejection.\n * Inspects error code, type, status, and message substring — every\n * provider names this case differently but the message is recognizable.\n */\nfunction isPromptTooLongError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (typeof e.code === 'string' && /prompt[_ ]too[_ ]long/i.test(e.code))\n return true\n if (typeof e.status === 'number' && (e.status === 413 || e.status === 400)) {\n const message = typeof e.message === 'string' ? e.message : ''\n if (/prompt[_ ]too[_ ]long|context[_ ]length|maximum[_ ]context|too many tokens/i.test(message))\n return true\n }\n if (typeof e.message === 'string'\n && /prompt[_ ]too[_ ]long|context[_ ]length[_ ]exceeded|context window/i.test(e.message)) {\n return true\n }\n // Anthropic SDK shape: `error.error.type === 'invalid_request_error'`\n // with a `prompt is too long` message.\n const nested = (e.error ?? {}) as Record<string, unknown>\n if (typeof nested.type === 'string' && nested.type === 'invalid_request_error') {\n const message = typeof nested.message === 'string' ? nested.message : ''\n if (/prompt[_ ]too[_ ]long|too long|context window/i.test(message))\n return true\n }\n return false\n}\n\n/**\n * Transient network / 5xx errors worth retrying once or twice.\n */\nfunction isTransientError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (typeof e.status === 'number' && e.status >= 500 && e.status < 600)\n return true\n if (typeof e.code === 'string' && /ECONNRESET|ETIMEDOUT|ENETUNREACH|EAI_AGAIN|fetch failed/i.test(e.code))\n return true\n if (typeof e.message === 'string'\n && /socket hang up|fetch failed|network error|terminated|ECONNRESET|read ETIMEDOUT/i.test(e.message)) {\n return true\n }\n return false\n}\n\nfunction isAbortError(err: unknown, signal: AbortSignal | undefined): boolean {\n if (signal?.aborted)\n return true\n if (!err || typeof err !== 'object')\n return false\n const e = err as Record<string, unknown>\n if (e.name === 'AbortError')\n return true\n if (typeof e.message === 'string' && /aborted/i.test(e.message))\n return true\n return false\n}\n","/**\n * Post-compact restoration — re-inject load-bearing working state as\n * synthetic tool-call/tool-result pairs after a {@link compactConversation}\n * marker lands in a session.\n *\n * Without this step, the model has a narrative summary but loses direct\n * access to the files it was actively editing and the skills it was\n * following. Restoration re-attaches the top-N recently-read files and\n * active skills so the next turn starts with full working context — no\n * forced re-reads, no degraded continuation.\n *\n * Design — synthetic tool_call/tool_result pairs:\n *\n * Two turns are appended after the compaction marker:\n *\n * [marker] ← from `summaryToTurn(result)`\n * [assistant, tool_calls × N] ← synthetic, one tool_call per item\n * [user, tool_results × N] ← synthetic, matching results\n * [new prompt] ← user's next message\n *\n * The synthetic turns look identical to what the agent would produce if it\n * had actually run `read_file` / `skills_use` — by design, because at the\n * moment of compaction those operations had just happened with that data.\n *\n * Persisted blocks use **canonical** tool names (e.g. `read_file`). The\n * agent loop's `rewriteMessagesToWire` translates them to whatever alias\n * the host configured before they reach the provider, exactly as it does\n * for real calls. Restoration therefore \"just works\" through aliasing —\n * the helper does not need to consult the alias map.\n *\n * Budgets — mirror Claude Code's `services/compact/compact.ts:122-130`:\n *\n * - 50_000 tokens total file budget, 5_000 per file, max 5 files\n * - 25_000 tokens total skill budget, 5_000 per skill (no count cap)\n *\n * Tokens are estimated at 4 chars/token — same heuristic Claude Code uses\n * for budget arithmetic. Hosts can override every limit.\n *\n * Failure modes:\n *\n * - File read fails (deleted, permissions) → skip silently. Other items\n * still proceed.\n * - No `execution` / `handle` passed → file restoration is a no-op;\n * skill restoration still works (skills carry their content inline).\n * - Empty `recentFiles` AND empty `activeSkills` → returns `{ turns: [] }`\n * so the caller's `appendTurns([summary, ...attachments])` is a no-op\n * on the attachments.\n */\n\nimport type { ExecutionContext, ExecutionHandle } from '../contexts'\nimport type { Session } from '../session'\nimport type { ActiveSkill } from '../skills/activation'\nimport type { SessionContentBlock, SessionTurn } from '../types'\nimport { getReadState } from '../tools/read-state'\nimport { BYTES_PER_TOKEN, estimateTokens, utf8ByteLength } from './utils'\n\n// ---------------------------------------------------------------------------\n// Defaults — match Claude Code's published constants for parity.\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_FILE_TOKEN_BUDGET = 50_000\nconst DEFAULT_FILE_TOKEN_PER_FILE_CAP = 5_000\nconst DEFAULT_MAX_FILES_TO_RESTORE = 5\n\nconst DEFAULT_SKILL_TOKEN_BUDGET = 25_000\nconst DEFAULT_SKILL_TOKEN_PER_SKILL_CAP = 5_000\n\n/** Default canonical tool names — `rewriteMessagesToWire` handles aliasing. */\nconst DEFAULT_READ_FILE_TOOL_NAME = 'read_file'\nconst DEFAULT_SKILLS_USE_TOOL_NAME = 'skills_use'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** One file selected for restoration, with its last-read timestamp for ranking. */\nexport interface RecentFile {\n /** Path relative to the execution context's `handle.cwd`. */\n path: string\n /** Wall-clock when the file was last read, in ms. Drives recency ranking. */\n mtimeMs: number\n}\n\nexport interface PostCompactRestoreOptions {\n // ---- What to restore ---------------------------------------------------\n\n /**\n * Files to consider for restoration, ranked by recency. Typically derived\n * via {@link selectFilesFromReadState} from `getReadState(session)`.\n * The helper takes the top {@link PostCompactRestoreOptions.maxFilesToRestore}\n * by `mtimeMs` descending, fetches their current content, and synthesizes\n * `read_file` tool_call/tool_result pairs.\n */\n recentFiles?: readonly RecentFile[]\n\n /**\n * Active skills to re-inject. Typically `agent.activeSkills`. Each entry\n * yields a synthetic `skills_use` tool_call/tool_result pair carrying the\n * skill's instructions (possibly truncated to the per-skill budget).\n */\n activeSkills?: readonly ActiveSkill[]\n\n // ---- I/O ---------------------------------------------------------------\n\n /**\n * Execution context used to fetch file content. **Required for file\n * restoration.** Skills come pre-loaded (instructions live on the\n * `SkillConfig`), so they don't need the context.\n */\n execution?: ExecutionContext\n handle?: ExecutionHandle\n\n // ---- Tool naming -------------------------------------------------------\n\n /**\n * Canonical name of the file-read tool. Defaults to `read_file`. Aliases\n * (e.g. `Read`) are NOT specified here — the agent loop's\n * `rewriteMessagesToWire` handles wire conversion automatically.\n *\n * Override only when a host registered file-read under a different\n * canonical name (not just an alias).\n */\n readFileToolName?: string\n /**\n * Canonical name of the skill-activation tool. Defaults to `skills_use`.\n * Same aliasing semantics as `readFileToolName`.\n */\n skillsUseToolName?: string\n\n // ---- Budgets -----------------------------------------------------------\n\n /** Total token budget for file restoration. Default: 50_000. */\n fileTokenBudget?: number\n /** Per-file token cap. Default: 5_000. */\n fileTokenPerFileCap?: number\n /** Maximum file count. Default: 5. */\n maxFilesToRestore?: number\n\n /** Total token budget for skill restoration. Default: 25_000. */\n skillTokenBudget?: number\n /** Per-skill token cap. Default: 5_000. */\n skillTokenPerSkillCap?: number\n\n // ---- Filtering ---------------------------------------------------------\n\n /**\n * Paths to skip — typically the file paths already covered by the\n * compaction result's `preservedTurns`. Avoids double-injection when the\n * recent reads sit in the preserved tail.\n */\n excludePaths?: readonly string[]\n\n // ---- Synthesis ---------------------------------------------------------\n\n /**\n * Optional `runId` to tag the synthetic turns with — useful for hosts\n * that want the restoration turns to roll up under the same run as the\n * compaction marker. Defaults to undefined (orphan turns).\n */\n runId?: string\n}\n\n/**\n * Envelope returned by {@link buildPostCompactAttachments}. The caller\n * spreads `turns` into `session.appendTurns([summaryTurn, ...turns])`.\n * Count fields drive UI banners (\"restored 5 files + 2 skills\").\n */\nexport interface PostCompactAttachments {\n /**\n * Two synthetic turns when at least one item was restored, otherwise\n * an empty array. The pair is `[assistant_with_tool_calls,\n * user_with_tool_results]` — adjacent and well-formed for every\n * provider's `tool_use ↔ tool_result` invariant.\n */\n turns: readonly SessionTurn[]\n /** Count of files actually restored (post-budget). */\n restoredFiles: number\n /** Count of skills actually restored (post-budget). */\n restoredSkills: number\n /** Rough total token cost of the restoration payload (sum of all content). */\n estimatedTokens: number\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for tests + caller convenience\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a raw read-state map into a deduped, path-ranked list ready for\n * restoration. Multiple entries for the same path (different\n * `(offset, limit, maxBytes)` slices) collapse to one — keeping the most\n * recent `mtimeMs`.\n *\n * Filters out entries whose key doesn't share the given `cwd` prefix:\n * those came from a different execution context and can't be read back\n * through this agent's handle.\n *\n * Pure. Most callers want {@link selectFilesFromSession}, which wraps\n * `getReadState(session)` + this function in one call so the host\n * doesn't have to reach into the tools layer.\n */\nexport function selectFilesFromReadState(\n readState: ReadonlyMap<string, { mtimeMs: number }>,\n cwd: string,\n): RecentFile[] {\n const prefix = `${cwd}::`\n const byPath = new Map<string, number>()\n for (const [key, entry] of readState) {\n if (!key.startsWith(prefix))\n continue\n const path = key.slice(prefix.length)\n if (path.length === 0)\n continue\n const prior = byPath.get(path) ?? -Infinity\n if (entry.mtimeMs > prior)\n byPath.set(path, entry.mtimeMs)\n }\n return Array.from(byPath, ([path, mtimeMs]) => ({ path, mtimeMs }))\n .sort((a, b) => b.mtimeMs - a.mtimeMs)\n}\n\n/**\n * Session-aware convenience: extract recently-read files directly from\n * a {@link Session} via its per-session read-state map.\n *\n * Hosts (TUI / SDK consumers) typically have a `Session` and a `cwd`\n * (the active agent's `handle.cwd`) on hand — this wrapper saves them\n * from reaching into `src/tools/read-state.ts` directly. Returns an\n * empty list when no read state has been recorded yet (fresh session,\n * or `behavior.dedupReads === false`).\n *\n * Equivalent to:\n *\n * ```ts\n * const state = getReadState(session)\n * return state ? selectFilesFromReadState(state, cwd) : []\n * ```\n */\nexport function selectFilesFromSession(\n session: Session,\n cwd: string,\n): RecentFile[] {\n const state = getReadState(session)\n return state ? selectFilesFromReadState(state, cwd) : []\n}\n\n/**\n * Pick the top `maxFiles` from `files` (descending by `mtimeMs`),\n * dropping any whose path appears in `excludePaths`.\n *\n * Stable for equal mtimes — files with the same timestamp retain their\n * input order. Pure.\n */\nexport function selectRecentFiles(\n files: readonly RecentFile[],\n opts: { maxFiles: number, excludePaths?: readonly string[] },\n): RecentFile[] {\n const excluded = new Set(opts.excludePaths ?? [])\n const filtered = files.filter(f => !excluded.has(f.path))\n // Defensive sort — caller may pass an already-sorted list, but doing it\n // here keeps the function total regardless of input order.\n const sorted = filtered.slice().sort((a, b) => b.mtimeMs - a.mtimeMs)\n return sorted.slice(0, Math.max(0, opts.maxFiles))\n}\n\n// ---------------------------------------------------------------------------\n// Internal — content formatting + truncation\n// ---------------------------------------------------------------------------\n\ninterface FormattedFile {\n /** Tool-result content (line-numbered, with optional truncation footer). */\n body: string\n /** Whether the content was truncated to fit the per-file budget. */\n truncated: boolean\n /** Estimated token cost of `body` (post-truncation). */\n estimatedTokens: number\n}\n\ninterface FormattedSkill {\n body: string\n truncated: boolean\n estimatedTokens: number\n}\n\n/**\n * Format a file's contents to match `read_file`'s output shape — 1-indexed\n * line numbers separated by tabs, identical to what the model has seen\n * from real `read_file` calls. Applies the per-file token cap by truncating\n * at the nearest line boundary; appends a footer pointing at the next\n * offset so the model can re-read the rest if needed.\n */\nfunction formatFileForRestoration(\n content: string,\n perFileTokenCap: number,\n): FormattedFile {\n const totalBytes = utf8ByteLength(content)\n const allLines = content.split('\\n')\n const totalLines = allLines.length\n\n // Build the line-numbered body first; tokens are counted on the\n // numbered form because that's what reaches the provider.\n const numbered: string[] = []\n let runningChars = 0\n const charCap = Math.max(1, perFileTokenCap) * BYTES_PER_TOKEN\n let truncatedAt = -1\n let midLineCut = false\n\n for (let i = 0; i < allLines.length; i++) {\n const numberedLine = `${i + 1}\\t${allLines[i]}`\n const lineCharCost = numberedLine.length + (i < allLines.length - 1 ? 1 : 0) // +1 for the joining '\\n' on every line but the last\n if (runningChars + lineCharCost > charCap) {\n if (numbered.length === 0) {\n // The first line on its own already overflows (typical for\n // minified JS, CSV-on-one-line, etc.). Cut it at the char\n // boundary so we return SOMETHING useful — the head of the\n // file — instead of either failing or admitting a huge first\n // line that blows the budget. Mid-line cuts mean we can't\n // suggest a precise re-read offset; the footer says so.\n const cutTo = Math.max(1, charCap - `${i + 1}\\t`.length)\n numbered.push(`${i + 1}\\t${allLines[i].slice(0, cutTo)}`)\n midLineCut = true\n }\n truncatedAt = i\n break\n }\n numbered.push(numberedLine)\n runningChars += lineCharCost\n }\n\n const body = numbered.join('\\n')\n if (truncatedAt < 0)\n return { body, truncated: false, estimatedTokens: estimateTokens(body) }\n\n const lineLabel = midLineCut\n ? `line ${truncatedAt + 1} (mid-line)`\n : `line ${truncatedAt}`\n const offsetHint = midLineCut\n ? `mid-line cut prevents a precise offset — re-read with offset=${truncatedAt + 1} and a larger maxBytes`\n : `re-read with offset=${truncatedAt + 1} to continue`\n const footer = `\\n\\n…truncated at ${lineLabel} (post-compact restoration cap: ${perFileTokenCap} tokens). File has ${totalLines} lines, ${totalBytes} bytes total — ${offsetHint}.`\n const truncated = body + footer\n return { body: truncated, truncated: true, estimatedTokens: estimateTokens(truncated) }\n}\n\n/**\n * Format a skill's instructions for restoration. Skills are plain markdown\n * — no line numbering, no wrapping XML envelope (the model already\n * understands the format from real `skills_use` calls). Truncation cuts\n * at a line boundary when possible; appends a marker so the model knows\n * the body is incomplete.\n */\nfunction formatSkillForRestoration(\n instructions: string,\n perSkillTokenCap: number,\n): FormattedSkill {\n const charCap = Math.max(1, perSkillTokenCap) * BYTES_PER_TOKEN\n if (instructions.length <= charCap) {\n return { body: instructions, truncated: false, estimatedTokens: estimateTokens(instructions) }\n }\n // Truncate at the nearest line boundary at or before charCap so the\n // body stays markdown-parseable.\n const head = instructions.slice(0, charCap)\n const lastNewline = head.lastIndexOf('\\n')\n const cutoff = lastNewline > 0 ? lastNewline : charCap\n const truncatedBody\n = `${instructions.slice(0, cutoff).trimEnd()}\\n\\n…[truncated post-compact at ${perSkillTokenCap} tokens; full skill body lives at the skill's location]`\n return {\n body: truncatedBody,\n truncated: true,\n estimatedTokens: estimateTokens(truncatedBody),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public runner\n// ---------------------------------------------------------------------------\n\n/**\n * Build the synthetic turns to append after a `compact-summary` marker.\n *\n * Returns a `PostCompactAttachments` envelope; the caller is responsible\n * for `session.appendTurns([summaryTurn, ...result.turns])`. The two\n * synthetic turns are always emitted together (or both omitted when\n * nothing was restored) so the `tool_use ↔ tool_result` adjacency\n * invariant holds regardless of caller code path.\n *\n * Failure isolation: a single file's read failure never blocks the rest\n * of restoration — that file is skipped silently and processing\n * continues. The returned `restoredFiles` count reflects what actually\n * landed in the synthesized turns.\n */\nexport async function buildPostCompactAttachments(\n opts: PostCompactRestoreOptions,\n): Promise<PostCompactAttachments> {\n const fileTokenBudget = opts.fileTokenBudget ?? DEFAULT_FILE_TOKEN_BUDGET\n const fileTokenPerFileCap = opts.fileTokenPerFileCap ?? DEFAULT_FILE_TOKEN_PER_FILE_CAP\n const maxFilesToRestore = opts.maxFilesToRestore ?? DEFAULT_MAX_FILES_TO_RESTORE\n const skillTokenBudget = opts.skillTokenBudget ?? DEFAULT_SKILL_TOKEN_BUDGET\n const skillTokenPerSkillCap = opts.skillTokenPerSkillCap ?? DEFAULT_SKILL_TOKEN_PER_SKILL_CAP\n const readFileToolName = opts.readFileToolName ?? DEFAULT_READ_FILE_TOOL_NAME\n const skillsUseToolName = opts.skillsUseToolName ?? DEFAULT_SKILLS_USE_TOOL_NAME\n\n // ---- Phase 1: pick candidates --------------------------------------\n //\n // Tool-availability is governed at the input layer: callers control\n // which categories are restored by what they pass. To skip files,\n // pass empty `recentFiles` (or none). To skip skills, pass empty\n // `activeSkills` (or none). The runner trusts the caller to know\n // what tools the next agent run will have access to — auto-injected\n // tools (`skills_use`, MCP, interaction tools) aren't introspectable\n // from a profile config and constructing a tool-allowlist here would\n // be incomplete by design.\n const candidateFiles = opts.recentFiles && opts.recentFiles.length > 0\n ? selectRecentFiles(opts.recentFiles, {\n maxFiles: maxFilesToRestore,\n ...(opts.excludePaths ? { excludePaths: opts.excludePaths } : {}),\n })\n : []\n const candidateSkills = opts.activeSkills ?? []\n\n // ---- Phase 2: fetch + format files (with per-file + group budget) --\n const fileCalls: Array<{ callId: string, path: string, body: string, estimatedTokens: number }> = []\n let fileBudgetUsed = 0\n if (candidateFiles.length > 0 && opts.execution && opts.handle) {\n for (let i = 0; i < candidateFiles.length; i++) {\n const file = candidateFiles[i]\n let content: string\n try {\n content = await opts.execution.readFile(opts.handle, file.path)\n }\n catch {\n // File deleted / unreadable since the last read — skip silently.\n continue\n }\n const formatted = formatFileForRestoration(content, fileTokenPerFileCap)\n if (fileBudgetUsed + formatted.estimatedTokens > fileTokenBudget) {\n // Adding this file would blow the group budget. Stop here — the\n // remaining files are older anyway (candidates are pre-sorted by\n // recency descending).\n break\n }\n fileBudgetUsed += formatted.estimatedTokens\n fileCalls.push({\n callId: `compact-restore-file-${i}`,\n path: file.path,\n body: formatted.body,\n estimatedTokens: formatted.estimatedTokens,\n })\n }\n }\n\n // ---- Phase 3: format skills (no I/O — instructions are inline) -----\n const skillCalls: Array<{ callId: string, name: string, body: string, estimatedTokens: number }> = []\n let skillBudgetUsed = 0\n for (let i = 0; i < candidateSkills.length; i++) {\n const active = candidateSkills[i]\n const instructions = active.skill.instructions ?? ''\n if (instructions.trim().length === 0)\n continue\n const formatted = formatSkillForRestoration(instructions, skillTokenPerSkillCap)\n if (skillBudgetUsed + formatted.estimatedTokens > skillTokenBudget)\n break\n skillBudgetUsed += formatted.estimatedTokens\n skillCalls.push({\n callId: `compact-restore-skill-${i}`,\n name: active.skill.name,\n body: formatted.body,\n estimatedTokens: formatted.estimatedTokens,\n })\n }\n\n // ---- Phase 4: synthesize turns -------------------------------------\n if (fileCalls.length === 0 && skillCalls.length === 0) {\n return { turns: [], restoredFiles: 0, restoredSkills: 0, estimatedTokens: 0 }\n }\n\n const assistantBlocks: SessionContentBlock[] = []\n const userBlocks: SessionContentBlock[] = []\n\n for (const fc of fileCalls) {\n assistantBlocks.push({\n type: 'tool_call',\n id: fc.callId,\n name: readFileToolName,\n input: { path: fc.path },\n })\n userBlocks.push({\n type: 'tool_result',\n callId: fc.callId,\n output: fc.body,\n })\n }\n for (const sc of skillCalls) {\n assistantBlocks.push({\n type: 'tool_call',\n id: sc.callId,\n name: skillsUseToolName,\n input: { name: sc.name },\n })\n userBlocks.push({\n type: 'tool_result',\n callId: sc.callId,\n output: sc.body,\n })\n }\n\n const now = Date.now()\n const tag = opts.runId ? { runId: opts.runId } : {}\n const turns: SessionTurn[] = [\n {\n id: crypto.randomUUID(),\n role: 'assistant',\n content: assistantBlocks,\n createdAt: now,\n ...tag,\n },\n {\n id: crypto.randomUUID(),\n role: 'user',\n content: userBlocks,\n // +1 ms so the user turn sorts after the assistant turn under\n // tie-breaking sorts that walk by `createdAt`.\n createdAt: now + 1,\n ...tag,\n },\n ]\n\n return {\n turns,\n restoredFiles: fileCalls.length,\n restoredSkills: skillCalls.length,\n estimatedTokens: fileBudgetUsed + skillBudgetUsed,\n }\n}\n","/**\n * Structured logging primitive with auto-correlation.\n *\n * Every consumer of the harness used to roll its own logger; the TUI has\n * one, downstream apps have theirs, observability backends each want a\n * slightly different shape. This module ships the smallest reasonable\n * shared surface:\n *\n * - {@link Logger} — minimal level-tagged interface (`debug` / `info` /\n * `warn` / `error`) plus a `with(...)` method that returns a child\n * logger carrying baseline attributes (`runId`, `turnId`, `callId`,\n * `childId`, `depth`, anything you want).\n * - {@link createLogger} — builds a Logger from a {@link LogSink}.\n * - {@link consoleSink} / {@link jsonSink} — built-in sinks that cover\n * the common cases (human-readable to a terminal vs. one JSON object\n * per line to a log aggregator).\n * - {@link createLoggingHooks} — installs Logger-attached hook handlers\n * on an agent so every lifecycle event lands on the sink with the\n * right correlation ids already stamped in.\n *\n * Hosts that already own a logger (pino, winston, structlog binding,\n * platform-native) plug it in via a custom {@link LogSink} — the helper\n * itself does no I/O.\n */\n\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from './agent'\n\n// ---------------------------------------------------------------------------\n// Core types\n// ---------------------------------------------------------------------------\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\nexport interface LogRecord {\n level: LogLevel\n /** Unix ms — set by `Logger` at emit time. */\n timestamp: number\n /** Free-form message. Sinks render this as the human-facing line. */\n message: string\n /** Structured fields. Correlation ids land here automatically. */\n attrs: Record<string, unknown>\n}\n\nexport interface LogSink {\n emit: (record: LogRecord) => void\n}\n\nexport interface Logger {\n debug: (message: string, attrs?: Record<string, unknown>) => void\n info: (message: string, attrs?: Record<string, unknown>) => void\n warn: (message: string, attrs?: Record<string, unknown>) => void\n error: (message: string, attrs?: Record<string, unknown>) => void\n /**\n * Returns a child logger that prepends the given attributes onto every\n * subsequent emit. Equivalent to `pino.child` / `winston.child`. The\n * parent and child share the same sink — children are zero-cost.\n */\n with: (extra: Record<string, unknown>) => Logger\n /**\n * Inspectable baseline attributes — handy for tests and for hook\n * handlers that want to clone-with-extra without recursing.\n */\n readonly baseAttributes: Readonly<Record<string, unknown>>\n}\n\n// ---------------------------------------------------------------------------\n// createLogger\n// ---------------------------------------------------------------------------\n\n/**\n * Build a Logger from a sink. Stateless and cheap; create one per agent\n * (or per app) and use `.with()` to attach correlation ids per-call.\n */\nexport function createLogger(\n sink: LogSink,\n baseAttributes: Readonly<Record<string, unknown>> = {},\n): Logger {\n function emit(level: LogLevel, message: string, attrs?: Record<string, unknown>): void {\n try {\n sink.emit({\n level,\n timestamp: Date.now(),\n message,\n attrs: attrs ? { ...baseAttributes, ...attrs } : { ...baseAttributes },\n })\n }\n catch {\n // Sinks should never crash the run.\n }\n }\n\n return {\n debug: (m, a) => emit('debug', m, a),\n info: (m, a) => emit('info', m, a),\n warn: (m, a) => emit('warn', m, a),\n error: (m, a) => emit('error', m, a),\n with: extra => createLogger(sink, { ...baseAttributes, ...extra }),\n baseAttributes,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Built-in sinks\n// ---------------------------------------------------------------------------\n\nexport interface ConsoleSinkOptions {\n /**\n * Minimum level to emit. Defaults to `'info'` — `debug` is dropped so\n * the harness's lifecycle logging is not noisy by default. Set to\n * `'debug'` to see every event.\n */\n minLevel?: LogLevel\n /** Custom output stream. Defaults to `process.stderr` so logs don't pollute stdout. */\n stream?: { write: (chunk: string) => void }\n}\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n}\n\n/**\n * Human-readable terminal sink. Renders each record as\n * `<ISO timestamp> <LEVEL> <message> <attrs as kv pairs>`.\n *\n * Honors `process.stderr` by default so log lines don't interleave with\n * the agent's stdout-bound output (chat responses, JSON results).\n */\nexport function consoleSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n const ts = new Date(record.timestamp).toISOString()\n const kv = Object.entries(record.attrs)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`)\n .join(' ')\n stream.write(`${ts} ${record.level.toUpperCase().padEnd(5)} ${record.message}${kv ? ` ${kv}` : ''}\\n`)\n },\n }\n}\n\n/**\n * One-JSON-object-per-line sink. Suitable for piping into log aggregators\n * (Datadog Agent, Fluent Bit, Loki, Vector) that expect JSONL.\n */\nexport function jsonSink(options: ConsoleSinkOptions = {}): LogSink {\n const min = LEVEL_ORDER[options.minLevel ?? 'info']\n const stream = options.stream ?? process.stderr\n return {\n emit(record) {\n if (LEVEL_ORDER[record.level] < min)\n return\n try {\n stream.write(`${JSON.stringify(record)}\\n`)\n }\n catch {\n // Non-serializable attr (cycle, BigInt, etc.). Skip rather than throw.\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// createLoggingHooks\n// ---------------------------------------------------------------------------\n\nexport interface LoggingHooksOptions {\n logger: Logger\n /**\n * Minimum interesting level for harness-emitted lines. Default `'info'`.\n * Set to `'debug'` to see every tool dispatch / stream event. Set to\n * `'warn'` to mute the chatty ones and only see failures + budgets.\n */\n level?: LogLevel\n /**\n * When true (default), lifecycle events (`agent:start`, `turn:before`,\n * `tool:before`, `mcp:bootstrap:start`) emit at `debug` level so they\n * stay quiet by default. Set false to mute them entirely regardless of\n * the configured minimum level — useful when piping into a tracer\n * that already captures lifecycle.\n */\n includeLifecycle?: boolean\n}\n\nexport interface LoggingHookSet {\n install: (hooks: Hookable<AgentHooks>) => () => void\n}\n\n/**\n * Install a bundle of hook handlers that emit a structured line per\n * relevant lifecycle event, automatically attaching correlation ids\n * (`runId`, `turnId`, `callId`, `childId`, `depth`, `agentName`).\n *\n * @example\n * ```ts\n * const logger = createLogger(consoleSink({ minLevel: 'debug' }), { service: 'tui' })\n * const lh = createLoggingHooks({ logger })\n * const uninstall = lh.install(agent.hooks)\n * try { await agent.run({ prompt }) }\n * finally { uninstall() }\n * ```\n */\nexport function createLoggingHooks(options: LoggingHooksOptions): LoggingHookSet {\n const root = options.logger\n const includeLifecycle = options.includeLifecycle ?? true\n const minLevel = LEVEL_ORDER[options.level ?? 'info']\n\n /**\n * Wrap a Logger so emissions below `minLevel` are dropped before they\n * hit the sink. Lets us gate harness chatter without forcing every\n * `LogSink` to re-implement level filtering. Preserves `.with()`\n * composition (children inherit the filter).\n */\n function gateLevel(logger: Logger): Logger {\n const skip = (level: LogLevel): boolean => LEVEL_ORDER[level] < minLevel\n const wrap = (l: Logger): Logger => ({\n debug: (m, a) => {\n if (!skip('debug'))\n l.debug(m, a)\n },\n info: (m, a) => {\n if (!skip('info'))\n l.info(m, a)\n },\n warn: (m, a) => {\n if (!skip('warn'))\n l.warn(m, a)\n },\n error: (m, a) => {\n if (!skip('error'))\n l.error(m, a)\n },\n with: extra => wrap(l.with(extra)),\n baseAttributes: l.baseAttributes,\n })\n return wrap(logger)\n }\n\n return {\n install(hooks: Hookable<AgentHooks>): () => void {\n const unregisters: Array<() => void> = []\n // Per-install loggers, refreshed on `agent:start` / `turn:before`.\n // Scoped INSIDE install() so concurrent installs across different\n // agents don't share state — each call to `install()` gets its own\n // attribution state.\n const gatedRoot = gateLevel(root)\n let runLogger = gatedRoot\n let turnLogger = gatedRoot\n\n // ---- Agent lifecycle --------------------------------------------\n\n unregisters.push(hooks.hook('agent:start', (ctx) => {\n runLogger = gatedRoot.with({\n runId: ctx.runId,\n ...(ctx.parentRunId ? { parentRunId: ctx.parentRunId } : {}),\n depth: ctx.depth,\n ...(ctx.agentName ? { agentName: ctx.agentName } : {}),\n })\n turnLogger = runLogger\n if (includeLifecycle)\n runLogger.debug('agent run started')\n }))\n\n unregisters.push(hooks.hook('agent:done', (stats) => {\n runLogger.info('agent run completed', {\n turns: stats.turns,\n totalIn: stats.totalIn,\n totalOut: stats.totalOut,\n ...(typeof stats.cost === 'number' ? { cost: stats.cost } : {}),\n elapsedMs: stats.elapsed,\n ...(typeof stats.timeTillFirstTokenMs === 'number' ? { ttftMs: stats.timeTillFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('agent:abort', () => {\n runLogger.warn('agent run aborted')\n }))\n\n // ---- Turn / stream ---------------------------------------------\n\n unregisters.push(hooks.hook('turn:before', (ctx) => {\n turnLogger = runLogger.with({ turnId: ctx.turnId, turn: ctx.turn })\n if (includeLifecycle)\n turnLogger.debug('turn started')\n }))\n\n unregisters.push(hooks.hook('turn:after', (ctx) => {\n turnLogger.debug('turn ended', {\n inputTokens: ctx.usage.input,\n outputTokens: ctx.usage.output,\n ...(ctx.usage.finishReason ? { finishReason: ctx.usage.finishReason } : {}),\n ...(ctx.usage.modelId ? { modelId: ctx.usage.modelId } : {}),\n ...(typeof ctx.usage.timeToFirstTokenMs === 'number' ? { ttftMs: ctx.usage.timeToFirstTokenMs } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('stream:error', (ctx) => {\n turnLogger.error('stream error', {\n message: ctx.err instanceof Error ? ctx.err.message : String(ctx.err),\n ...(ctx.statusCode !== undefined ? { statusCode: ctx.statusCode } : {}),\n ...(ctx.requestId !== undefined ? { requestId: ctx.requestId } : {}),\n })\n }))\n\n // ---- Tool calls -------------------------------------------------\n\n unregisters.push(hooks.hook('tool:before', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool started', {\n toolName: ctx.name,\n displayName: ctx.displayName,\n callId: ctx.callId,\n })\n }))\n\n unregisters.push(hooks.hook('tool:after', (ctx) => {\n if (!includeLifecycle)\n return\n turnLogger.debug('tool ended', {\n toolName: ctx.name,\n callId: ctx.callId,\n outputBytes: ctx.outputBytes,\n })\n }))\n\n unregisters.push(hooks.hook('tool:error', (ctx) => {\n turnLogger.error('tool error', {\n toolName: ctx.name,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('tool:dispatched', (ctx) => {\n // Successful + gate-substitute paths are routine — only log at\n // debug level. The three \"something refused / went wrong\" paths\n // (gate-block, unknown tool, invalid input) land at warn so they\n // surface in default-config dashboards without forcing debug.\n const isAnomaly = ctx.outcome === 'gate-block'\n || ctx.outcome === 'unknown'\n || ctx.outcome === 'invalid-input'\n if (!isAnomaly && !includeLifecycle)\n return\n const lvl: LogLevel = isAnomaly ? 'warn' : 'debug'\n turnLogger[lvl]('tool dispatched', {\n toolName: ctx.name,\n callId: ctx.callId,\n outcome: ctx.outcome,\n ...(ctx.reason ? { reason: ctx.reason } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('validation:reject', (ctx) => {\n turnLogger.warn('tool input rejected', {\n toolName: ctx.name,\n callId: ctx.callId,\n reason: ctx.reason,\n })\n }))\n\n // ---- Budgets ----------------------------------------------------\n\n unregisters.push(hooks.hook('budget:exceeded', (ctx) => {\n turnLogger.warn('byte budget exceeded', {\n bytes: ctx.bytes,\n budget: ctx.budget,\n })\n }))\n\n unregisters.push(hooks.hook('tool-budget:exceeded', (ctx) => {\n turnLogger.warn('tool budget exceeded', {\n toolName: ctx.tool,\n count: ctx.count,\n max: ctx.max,\n mode: ctx.mode,\n })\n }))\n\n // ---- MCP --------------------------------------------------------\n\n unregisters.push(hooks.hook('mcp:bootstrap:end', (ctx) => {\n if (ctx.ok) {\n if (includeLifecycle) {\n runLogger.debug('mcp bootstrap ok', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n toolCount: ctx.toolCount,\n ...(ctx.lazy ? { lazy: true } : {}),\n ...(ctx.cached ? { cached: true } : {}),\n })\n }\n }\n else {\n runLogger.warn('mcp bootstrap failed', {\n server: ctx.name,\n transport: ctx.transport,\n durationMs: ctx.durationMs,\n message: ctx.error.message,\n })\n }\n }))\n\n unregisters.push(hooks.hook('mcp:error', (ctx) => {\n runLogger.error('mcp error', {\n server: ctx.name,\n message: ctx.error.message,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:warn', (ctx) => {\n runLogger.warn('mcp warn', {\n server: ctx.name,\n message: ctx.message,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:auth:required', (ctx) => {\n runLogger.warn('mcp auth required', {\n server: ctx.name,\n transport: ctx.transport,\n reason: ctx.reason,\n })\n }))\n\n unregisters.push(hooks.hook('mcp:tool:error', (ctx) => {\n turnLogger.error('mcp tool error', {\n server: ctx.server,\n tool: ctx.displayName,\n callId: ctx.callId,\n message: ctx.error.message,\n })\n }))\n\n // ---- Spawn ------------------------------------------------------\n\n unregisters.push(hooks.hook('spawn:before', (ctx) => {\n if (!includeLifecycle)\n return\n runLogger.debug('spawn started', {\n childId: ctx.id,\n depth: ctx.depth,\n })\n }))\n\n unregisters.push(hooks.hook('spawn:complete', (ctx) => {\n runLogger.info('spawn completed', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n status: ctx.status ?? 'completed',\n turns: ctx.stats.turns,\n totalIn: ctx.stats.totalIn,\n totalOut: ctx.stats.totalOut,\n ...(typeof ctx.stats.cost === 'number' ? { cost: ctx.stats.cost } : {}),\n })\n }))\n\n unregisters.push(hooks.hook('spawn:error', (ctx) => {\n runLogger.error('spawn error', {\n childId: ctx.id,\n ...(ctx.depth ? { depth: ctx.depth } : {}),\n message: ctx.error.message,\n })\n }))\n\n // -----------------------------------------------------------------\n // Disposal\n // -----------------------------------------------------------------\n\n let disposed = false\n return function uninstall() {\n if (disposed)\n return\n disposed = true\n for (const un of unregisters) {\n try {\n un()\n }\n catch { /* ignore */ }\n }\n }\n },\n }\n}\n","/**\n * Local loopback HTTP callback for OAuth 2.0 authorization code flows.\n *\n * Stands up a one-shot server on `127.0.0.1:<random>` that captures the\n * `?code=...` redirect from a browser-driven OAuth flow and resolves a\n * promise with the code. Used as the `redirectUrl` half of the MCP SDK's\n * `OAuthClientProvider` (the persistence half lives separately).\n *\n * Design:\n * - Loopback-only (`127.0.0.1`) — the OAuth spec treats `http://127.0.0.1:<port>`\n * as a public-client redirect URI per RFC 8252 §7.3. Browsers do NOT block it,\n * and Anthropic / OpenAI / Linear / GitHub all accept it.\n * - Random port (`port = 0`) — the OS picks an unused one. We read the actual\n * port back from `server.address()` after `listen()`.\n * - Single-shot — the first GET to `path` with a `code` (or `error`) wins;\n * subsequent requests get 404. The server keeps listening (in case the user\n * hits \"back\" and re-authorizes), so callers must `close()` once they have\n * the code or have given up.\n * - Abort-aware — wiring an external `AbortSignal` rejects the promise and\n * closes the server immediately. Required for the TUI's \"esc cancels login\"\n * UX.\n * - No HTML framework — a single inline `<html>` string keeps this isolated\n * from any UI dependency.\n */\n\nimport type { AddressInfo } from 'node:net'\nimport { createServer } from 'node:http'\n\n/**\n * Result of a successful callback. `state` is forwarded verbatim from the\n * query string — callers verify it against their pre-flight value to defend\n * against CSRF (the MCP SDK does this internally when it controls `state`).\n */\nexport interface OAuthCallbackResult {\n code: string\n state?: string\n}\n\nexport interface OAuthCallbackHandle {\n /**\n * Full URI to register with the authorization server, e.g.\n * `http://127.0.0.1:51823/callback`. Stable for the lifetime of the\n * handle.\n */\n redirectUri: string\n /**\n * Resolves with `{ code, state }` on a successful callback. Rejects with:\n * - The OAuth-spec `error` field (`access_denied`, `server_error`, ...)\n * when the authorization server redirects with `?error=...`.\n * - `'OAuth callback aborted'` when the external `AbortSignal` fires.\n * - `'OAuth callback server closed'` when `close()` is called before any\n * callback arrives.\n *\n * Single-shot — only the first matching request resolves the promise.\n */\n promise: Promise<OAuthCallbackResult>\n /**\n * Idempotent shutdown. Safe to call from a `finally` block whether the\n * flow succeeded, failed, or was aborted. Resolves once the server stops\n * accepting connections.\n */\n close: () => Promise<void>\n}\n\nexport interface OAuthCallbackOptions {\n /** Cancels the flow — rejects `promise` and closes the server. */\n signal?: AbortSignal\n /**\n * Path component the authorization server should redirect to. Defaults\n * to `/callback`. Useful when matching a pre-registered URI that uses a\n * different path.\n */\n path?: string\n /**\n * Override the loopback host. Defaults to `127.0.0.1`. Don't bind to\n * `0.0.0.0` here — the OAuth code is a one-time secret and the server\n * would otherwise accept it from any host on the LAN.\n */\n host?: string\n /**\n * Override the port. Defaults to `0` (OS-assigned). Pin to a fixed port\n * only when the authorization server requires a pre-registered redirect\n * URI; the random-port path is preferred so concurrent flows don't clash.\n */\n port?: number\n}\n\nconst DEFAULT_PATH = '/callback'\nconst DEFAULT_HOST = '127.0.0.1'\n\nconst SUCCESS_HTML = `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>Logged in</title>\n<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:6rem auto;max-width:28rem;text-align:center;color:#1d1d1f}.ok{color:#1f8a4c;font-weight:600}</style>\n</head><body>\n<p class=\"ok\">Logged in.</p>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n\nfunction errorHtml(message: string): string {\n // No interpolation into HTML attributes — message goes only inside <p> text\n // where the SAMEORIGIN browser context is rendering. Escape angle brackets\n // and ampersands defensively anyway: a malicious authorization server could\n // theoretically craft an `error_description` containing markup, and we\n // don't want stored XSS even in a one-shot loopback page.\n const escaped = message\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n return `<!doctype html>\n<html><head><meta charset=\"utf-8\"><title>Login failed</title>\n<style>body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:6rem auto;max-width:28rem;text-align:center;color:#1d1d1f}.err{color:#c43c2c;font-weight:600}</style>\n</head><body>\n<p class=\"err\">Login failed.</p>\n<p>${escaped}</p>\n<p>You can close this tab and return to the terminal.</p>\n</body></html>`\n}\n\n/**\n * Start a one-shot OAuth callback server. The returned handle's `redirectUri`\n * should be passed to the authorization server as the `redirect_uri` query\n * parameter; `promise` resolves once the user finishes the browser flow.\n *\n * Always `await handle.close()` in a `finally` block — even on success, the\n * server stays open until told to shut down (so it can serve the\n * \"you can close this tab\" page).\n */\nexport async function startOAuthCallback(\n opts: OAuthCallbackOptions = {},\n): Promise<OAuthCallbackHandle> {\n const path = opts.path ?? DEFAULT_PATH\n const host = opts.host ?? DEFAULT_HOST\n const port = opts.port ?? 0\n\n if (!path.startsWith('/'))\n throw new Error(`OAuth callback path must start with \"/\" (got: ${JSON.stringify(path)})`)\n\n if (opts.signal?.aborted)\n throw new Error('OAuth callback aborted')\n\n let resolveResult: (value: OAuthCallbackResult) => void\n let rejectResult: (error: Error) => void\n const promise = new Promise<OAuthCallbackResult>((resolve, reject) => {\n resolveResult = resolve\n rejectResult = reject\n })\n // Attach a default no-op catch so a rejection without a consumer doesn't\n // raise an unhandled-rejection warning. Callers that DO `await promise`\n // still receive the rejection — Promises fan out to all listeners.\n promise.catch(() => {})\n\n let settled = false\n const resolveOnce = (value: OAuthCallbackResult) => {\n if (settled)\n return\n settled = true\n resolveResult(value)\n }\n const rejectOnce = (error: Error) => {\n if (settled)\n return\n settled = true\n rejectResult(error)\n }\n\n const server = createServer((req, res) => {\n const url = new URL(req.url ?? '/', `http://${host}`)\n if (url.pathname !== path) {\n res.writeHead(404, { 'content-type': 'text/plain' })\n res.end('Not Found')\n return\n }\n\n // `no-store` prevents the browser from replaying the page (with the\n // now-spent code in its URL bar) when the user hits back, refreshes,\n // or restores the tab from history. The code is a one-time secret.\n const htmlHeaders = {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'no-store',\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n const desc = url.searchParams.get('error_description') ?? error\n res.writeHead(400, htmlHeaders)\n res.end(errorHtml(desc))\n rejectOnce(new Error(`OAuth authorization failed: ${desc}`))\n return\n }\n\n const code = url.searchParams.get('code')\n if (!code) {\n // No code, no error — likely a stray probe. Serve a minimal page,\n // don't resolve. The browser will eventually arrive with the real\n // redirect, or the caller will time out via signal.\n res.writeHead(400, { 'content-type': 'text/plain' })\n res.end('Missing \"code\" query parameter.')\n return\n }\n\n const state = url.searchParams.get('state') ?? undefined\n res.writeHead(200, htmlHeaders)\n res.end(SUCCESS_HTML)\n resolveOnce({ code, state })\n })\n\n // Surface socket errors that arrive before the request handler — e.g. EADDRINUSE\n // on a pinned port, or an early TLS-style probe that the parser rejects.\n server.on('error', (err) => {\n rejectOnce(err instanceof Error ? err : new Error(String(err)))\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => {\n server.off('error', reject)\n resolve()\n })\n })\n\n const addr = server.address() as AddressInfo | null\n if (!addr || typeof addr === 'string') {\n server.close()\n throw new Error('OAuth callback server did not bind to a TCP port')\n }\n\n // `closeServer` and `onAbort` reference each other — pre-declare both so\n // either ordering reads cleanly and the lint doesn't trip on the cycle.\n let closing: Promise<void> | undefined\n let closeServer: () => Promise<void>\n const onAbort = () => {\n rejectOnce(new Error('OAuth callback aborted'))\n void closeServer()\n }\n closeServer = (): Promise<void> => {\n if (closing)\n return closing\n closing = new Promise<void>((resolve) => {\n // `close()` on a Node http server waits for in-flight requests to drain.\n // The flow has already settled (or is being aborted), so we also call\n // `closeAllConnections()` to drop keep-alive sockets immediately — a\n // browser keeps the connection open after the success page, which would\n // otherwise hang the close for the keep-alive timeout (~5s).\n const anyServer = server as { closeAllConnections?: () => void }\n anyServer.closeAllConnections?.()\n server.close(() => {\n opts.signal?.removeEventListener('abort', onAbort)\n // If neither a callback nor an abort happened before close(), reject\n // pending awaits so callers don't hang. Tag the rejection as\n // 'aborted' when the signal already fired — Bun fires the abort\n // listener via a microtask, so server.close() may resolve first\n // even though semantically the abort came first.\n if (opts.signal?.aborted)\n rejectOnce(new Error('OAuth callback aborted'))\n else\n rejectOnce(new Error('OAuth callback server closed'))\n resolve()\n })\n })\n return closing\n }\n opts.signal?.addEventListener('abort', onAbort, { once: true })\n\n return {\n redirectUri: `http://${host}:${addr.port}${path}`,\n promise,\n close: closeServer,\n }\n}\n","/**\n * Interactive OAuth login orchestrator for one MCP server.\n *\n * Composes the three pieces shipped separately:\n * - `startOAuthCallback` (`./oauth-callback`) — local loopback HTTP\n * listener that catches the `?code=...` redirect.\n * - `McpOAuthProvider` (`./oauth-provider`) — SDK adapter that persists\n * tokens / client info via an injected `McpCredentialStore`.\n * - The MCP SDK's transport `authProvider` + `finishAuth` plumbing —\n * `client.connect()` triggers the SDK to call `redirectToAuthorization`\n * (which opens the browser); we wait for the loopback callback to\n * deliver the code, hand it to `transport.finishAuth`, and reconnect.\n *\n * Returns once tokens are persisted. Caller decides whether to immediately\n * reconnect the server (re-run `connectMcpServers` / `agent.warmup()`) or\n * defer to the next session activation. The returned `tools` array is\n * provided as a convenience — callers that want the connection live in\n * this call rather than rebuilding can wire them in directly.\n */\n\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpServerConfig } from '../types'\nimport type { OAuthCallbackHandle } from './oauth-callback'\nimport type { McpCredentialStore } from './oauth-provider'\nimport { startOAuthCallback } from './oauth-callback'\nimport { McpOAuthProvider } from './oauth-provider'\nimport { sseToJsonFetchIfNeeded } from './sse-to-json-fetch'\nimport { createTolerantClient } from './tolerant-client'\n\nexport interface LoginMcpServerOptions {\n /** Persistence — same store the bootstrap path reads from. */\n store: McpCredentialStore\n /**\n * Invoked with the authorization URL once it's ready. Hosts typically\n * (a) emit `mcp:auth:url` for the TUI, and (b) call `tryOpenBrowser`.\n * The URL is identical to the one passed to the `mcp:auth:url` hook\n * fired automatically — this callback is a synchronous hook for callers\n * that don't want to wire the agent hook machinery.\n */\n onAuthorizationUrl?: (url: URL) => void | Promise<void>\n /** Cancels the flow (esc / close modal / SIGINT). */\n signal?: AbortSignal\n /** Agent hooks. The flow emits `mcp:auth:url`/`success`/`error` when wired. */\n hooks?: Hookable<AgentHooks>\n /** Override `client_name` shown on consent screens. Default: 'zidane'. */\n clientName?: string\n /** Override the requested OAuth scope. */\n scope?: string\n /**\n * Override the loopback callback path. Default: `/callback`. Useful only\n * for servers that pinned a different path during registration.\n */\n callbackPath?: string\n /**\n * Maximum time to wait for the user to complete the browser flow, in ms.\n * The user can also cancel via `signal`. Default: 5 minutes.\n */\n timeoutMs?: number\n}\n\nexport interface LoginMcpServerResult {\n /** Stored OAuth tokens after a successful exchange. */\n tokens: NonNullable<ReturnType<McpOAuthProvider['tokens']>>\n /**\n * Upstream tool descriptors discovered after re-connecting with the new\n * tokens. Already filtered by the server's `enabledTools` / `disabledTools`\n * is NOT applied here — that's a bootstrap concern. Hosts that want filtering\n * should pass the result through `connectMcpServers` rebuild on the next\n * session activation rather than reusing this list verbatim.\n */\n tools: Array<{ name: string, description?: string | null, inputSchema?: unknown }>\n}\n\nconst DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60_000\n\n/**\n * Run the full interactive OAuth flow for `config`. Only supports `sse` and\n * `streamable-http` transports — `stdio` MCP servers don't speak OAuth.\n *\n * Throws on:\n * - Wrong transport.\n * - Abort signal.\n * - Browser-side error (user denied, server rejected, etc.).\n * - Code exchange failure.\n * - Post-exchange connect failure.\n *\n * Always closes the loopback callback server before returning, success or\n * failure.\n */\nexport async function loginMcpServer(\n config: McpServerConfig,\n options: LoginMcpServerOptions,\n): Promise<LoginMcpServerResult> {\n if (config.transport !== 'sse' && config.transport !== 'streamable-http')\n throw new Error(`MCP OAuth: cannot login for transport \"${config.transport}\" — only sse / streamable-http are supported`)\n if (!config.url)\n throw new Error(`MCP OAuth: server \"${config.name}\" is missing a url`)\n\n const hooks = options.hooks\n\n let callback: OAuthCallbackHandle | undefined\n try {\n callback = await startOAuthCallback({\n signal: options.signal,\n path: options.callbackPath,\n })\n\n const handle = callback\n const provider = new McpOAuthProvider({\n name: config.name,\n store: options.store,\n redirectUri: handle.redirectUri,\n clientName: options.clientName,\n scope: options.scope,\n onAuthorizationUrl: async (url) => {\n await hooks?.callHook('mcp:auth:url', { name: config.name, url: url.toString() })\n await options.onAuthorizationUrl?.(url)\n },\n })\n\n const transport = await createInteractiveTransport(config, provider)\n const client = await createTolerantClient({ name: 'zidane', version: '1.0.0' })\n\n // First connect → SDK has no tokens, so it calls\n // `provider.redirectToAuthorization(url)` (which routes to our\n // `onAuthorizationUrl`), then throws `UnauthorizedError`. This is the\n // expected path — we catch and wait on the loopback for the code.\n let needsAuth = false\n try {\n await client.connect(transport)\n }\n catch (err) {\n if (!isUnauthorizedError(err)) {\n await client.close().catch(() => {})\n throw err\n }\n needsAuth = true\n }\n\n if (needsAuth) {\n const { code } = await raceLoginCallback(handle, options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS, options.signal)\n // SDK exchanges code → tokens; provider.saveTokens persists them.\n await (transport as { finishAuth: (code: string) => Promise<void> }).finishAuth(code)\n // Reconnect with a FRESH transport. The previous one is already in\n // the \"started\" state from the failed connect — reusing it throws\n // \"StreamableHTTPClientTransport already started!\" inside\n // `transport.start()`. The SDK's canonical example\n // (`simpleOAuthClient.js`) uses the same pattern: recurse with a\n // new transport, reuse the client. Close the stale transport\n // first so its abort controllers + sockets don't leak.\n await transport.close().catch(() => {})\n const freshTransport = await createInteractiveTransport(config, provider)\n await client.connect(freshTransport)\n }\n\n const { tools } = await client.listTools()\n await client.close()\n\n const tokens = provider.tokens()\n if (!tokens)\n throw new Error(`MCP OAuth: login for \"${config.name}\" returned no tokens (server may have rejected the exchange silently)`)\n\n await hooks?.callHook('mcp:auth:success', { name: config.name })\n return { tokens, tools }\n }\n catch (err) {\n const error = err instanceof Error ? err : new Error(String(err))\n await hooks?.callHook('mcp:auth:error', { name: config.name, error })\n throw error\n }\n finally {\n await callback?.close()\n }\n}\n\n/**\n * Build an `sse` / `streamable-http` transport pre-wired with `authProvider`.\n * Mirrors the bootstrap-side `createTransport` shape but inlined here so the\n * login flow doesn't depend on the bootstrap module's private export.\n */\nasync function createInteractiveTransport(config: McpServerConfig, authProvider: OAuthClientProvider) {\n if (config.transport === 'sse') {\n const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js')\n return new SSEClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n authProvider,\n })\n }\n // streamable-http\n const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js')\n return new StreamableHTTPClientTransport(new URL(config.url!), {\n requestInit: config.headers ? { headers: config.headers } : undefined,\n fetch: sseToJsonFetchIfNeeded(),\n authProvider,\n })\n}\n\nfunction isUnauthorizedError(err: unknown): boolean {\n if (!err || typeof err !== 'object')\n return false\n const e = err as { name?: string, message?: string, constructor?: { name?: string } }\n if (e.name === 'UnauthorizedError' || e.constructor?.name === 'UnauthorizedError')\n return true\n return typeof e.message === 'string' && e.message.toLowerCase().startsWith('unauthorized')\n}\n\n/**\n * Wait on the callback handle, with a hard timeout AND honor the external\n * abort signal. Unlike `callback.promise` alone, this provides a deterministic\n * failure mode for \"user opened the browser then walked away\" — the modal\n * doesn't hang forever waiting for a redirect that may never come.\n */\nasync function raceLoginCallback(\n handle: OAuthCallbackHandle,\n timeoutMs: number,\n signal: AbortSignal | undefined,\n): Promise<{ code: string, state?: string }> {\n if (signal?.aborted)\n throw new Error('OAuth login aborted')\n let timer: ReturnType<typeof setTimeout> | undefined\n let onAbort: (() => void) | undefined\n try {\n return await new Promise<{ code: string, state?: string }>((resolve, reject) => {\n timer = setTimeout(\n () => reject(new Error(`OAuth login timed out after ${timeoutMs}ms`)),\n timeoutMs,\n )\n if (signal) {\n onAbort = () => reject(new Error('OAuth login aborted'))\n signal.addEventListener('abort', onAbort, { once: true })\n }\n handle.promise.then(resolve, reject)\n })\n }\n finally {\n if (timer)\n clearTimeout(timer)\n if (signal && onAbort)\n signal.removeEventListener('abort', onAbort)\n }\n}\n"],"mappings":"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"}
|