zidane 5.6.12 → 5.6.13

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.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/dist/{agent-NwJU7c_W.d.ts → agent-ClkpElCZ.d.ts} +179 -39
  3. package/dist/agent-ClkpElCZ.d.ts.map +1 -0
  4. package/dist/chat.d.ts +19 -5
  5. package/dist/chat.d.ts.map +1 -1
  6. package/dist/chat.js +2 -2
  7. package/dist/{index-Bd0kbj4D.d.ts → index-CTDMMdIy.d.ts} +2 -2
  8. package/dist/{index-Bd0kbj4D.d.ts.map → index-CTDMMdIy.d.ts.map} +1 -1
  9. package/dist/{index-CZIOuAw7.d.ts → index-v3Tzobqr.d.ts} +2 -2
  10. package/dist/{index-CZIOuAw7.d.ts.map → index-v3Tzobqr.d.ts.map} +1 -1
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +7 -7
  13. package/dist/{login-BmPfubSA.js → login-DS3sf6b5.js} +3 -3
  14. package/dist/{login-BmPfubSA.js.map → login-DS3sf6b5.js.map} +1 -1
  15. package/dist/mcp.d.ts +1 -1
  16. package/dist/{messages-BRrEqWzH.js → messages-Dym8S_YH.js} +45 -4
  17. package/dist/messages-Dym8S_YH.js.map +1 -0
  18. package/dist/{presets-B0JQGmDK.js → presets-CZXS_87d.js} +2 -2
  19. package/dist/{presets-B0JQGmDK.js.map → presets-CZXS_87d.js.map} +1 -1
  20. package/dist/presets.d.ts +2 -2
  21. package/dist/presets.js +1 -1
  22. package/dist/{providers-f-2jAcUL.js → providers-beXyD9W9.js} +11 -7
  23. package/dist/providers-beXyD9W9.js.map +1 -0
  24. package/dist/providers.d.ts +1 -1
  25. package/dist/providers.js +2 -2
  26. package/dist/restate.d.ts +1 -1
  27. package/dist/session/sqlite.d.ts +1 -1
  28. package/dist/{session-CPZP-ZWO.js → session-BRIsmBSY.js} +5 -2
  29. package/dist/session-BRIsmBSY.js.map +1 -0
  30. package/dist/session.d.ts +2 -2
  31. package/dist/session.js +3 -3
  32. package/dist/skills.d.ts +2 -2
  33. package/dist/{tools-CQuDw7Sw.js → tools-DE9pR_NG.js} +4 -2
  34. package/dist/tools-DE9pR_NG.js.map +1 -0
  35. package/dist/tools.d.ts +2 -2
  36. package/dist/tools.js +1 -1
  37. package/dist/{transcript-anchors-awwdnicP.d.ts → transcript-anchors-D0TR6djV.d.ts} +4 -4
  38. package/dist/transcript-anchors-D0TR6djV.d.ts.map +1 -0
  39. package/dist/tui.d.ts +2 -2
  40. package/dist/tui.js +5 -5
  41. package/dist/{turn-operations-Dz_vp9En.js → turn-operations-6Yls2HuG.js} +645 -8
  42. package/dist/turn-operations-6Yls2HuG.js.map +1 -0
  43. package/dist/types.d.ts +2 -2
  44. package/docs/ARCHITECTURE.md +5 -3
  45. package/package.json +2 -2
  46. package/dist/agent-NwJU7c_W.d.ts.map +0 -1
  47. package/dist/messages-BRrEqWzH.js.map +0 -1
  48. package/dist/providers-f-2jAcUL.js.map +0 -1
  49. package/dist/session-CPZP-ZWO.js.map +0 -1
  50. package/dist/tools-CQuDw7Sw.js.map +0 -1
  51. package/dist/transcript-anchors-awwdnicP.d.ts.map +0 -1
  52. package/dist/turn-operations-Dz_vp9En.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-operations-6Yls2HuG.js","names":["findGitRoot","findGitRoot","CLIENT_ID","AUTHORIZE_URL","TOKEN_URL","CALLBACK_PORT","CALLBACK_PATH","PROVIDER_NAME","parseAuthorizationInput","exchangeAuthorizationCode","FILE_MODE","Buffer","findGitRoot","findGitRoot"],"sources":["../src/chat/agent-prompt.ts","../src/chat/todos.ts","../src/chat/agents.ts","../src/chat/project-root.ts","../src/chat/agents-md.ts","../src/chat/oauth-page/pkce.ts","../src/chat/oauth-page/server.ts","../src/chat/oauth-page/anthropic.ts","../src/chat/oauth-page/openai-codex.ts","../src/chat/oauth-page/render.ts","../src/chat/oauth-page/index.ts","../src/chat/providers.ts","../src/chat/credentials.ts","../src/chat/auth.ts","../src/chat/auto-compact.ts","../src/chat/auto-update.ts","../src/chat/auto-update-hook.ts","../src/chat/boot-profiler.ts","../src/chat/browser.ts","../src/chat/color-gradient.ts","../src/chat/completion.ts","../src/chat/completion-files.ts","../src/chat/completion-skills.ts","../src/chat/keybindings.ts","../src/chat/edit-approval.ts","../src/chat/edit-diff.ts","../src/chat/store.ts","../src/chat/user-config.ts","../src/chat/xdg.ts","../src/chat/config.ts","../src/chat/config-context.tsx","../src/chat/discovery-context.tsx","../src/chat/discovery-slot.ts","../src/chat/themes/catppuccin.ts","../src/chat/themes/crush.ts","../src/chat/themes/gruvbox.ts","../src/chat/themes/light.ts","../src/chat/themes/vaporwave.ts","../src/chat/theme.ts","../src/chat/settings-context.tsx","../src/chat/enabled-toggle-set.ts","../src/chat/files-discovery.ts","../src/chat/footer-hints.ts","../src/chat/generate-title.ts","../src/chat/hints.ts","../src/chat/interactions.tsx","../src/chat/markdown-segments.ts","../src/chat/mcp-auth-state.ts","../src/chat/mcp-auth-context.tsx","../src/chat/mcp-credentials.ts","../src/chat/mcp-rows.ts","../src/chat/mcp-tool-toggle.ts","../src/chat/mcp-tools-cache.ts","../src/chat/project-user-paths.ts","../src/chat/mcps-discovery.ts","../src/chat/model-catalog.ts","../src/chat/oauth.ts","../src/chat/oauth-redirect.ts","../src/chat/path-display.ts","../src/chat/prompt-segments.ts","../src/chat/shell-parse.ts","../src/chat/safe-mode.ts","../src/chat/safe-mode-context.tsx","../src/chat/session-export.ts","../src/chat/skills-discovery.ts","../src/chat/streaming.ts","../src/chat/theme-context.tsx","../src/chat/tool-formatters.ts","../src/chat/transcript-anchors.ts","../src/chat/turn-operations.ts"],"sourcesContent":["/**\n * Agent system-prompt fragments — composable doctrine for built-in profiles.\n *\n * Each `const` below is one self-contained section of a system prompt. They\n * exist as exports so SDK consumers can pick and choose: build a custom\n * profile by spreading them in {@link buildBuildSystem} order, drop a fragment\n * for a stricter / looser agent, or compose with project-specific prose.\n *\n * The fragments are deliberately byte-stable across sessions — every doctrine\n * piece reads the same on every turn so the system-prompt cache breakpoint\n * (anthropic ephemeral, `src/providers/anthropic.ts`) keeps hitting cache.\n *\n * Layout uses {@link SYSTEM_PROMPT_BOUNDARY} from `../system-prompt` to split\n * the rendered prompt into:\n *\n * ┌──────────────┐ cache_control: ephemeral\n * │ STATIC half │ identity + doctrine + guidance + user instructions\n * ├──────────────┤ ← __ZIDANE_SYSTEM_PROMPT_BOUNDARY__\n * │ DYNAMIC half │ envSection(cwd / date / platform)\n * └──────────────┘ (no cache_control)\n *\n * Per-turn cwd / date / platform churn no longer invalidates the doctrine\n * cache above. The TUI's `system:transform` hook rewrites only the dynamic\n * half via `replaceDynamicSection`; the static prefix stays byte-identical\n * for the lifetime of the run.\n */\n\nimport { joinSystemPrompt } from '../system-prompt'\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\n/**\n * Lead line — short, version-stable, written in the same key as Claude Code's\n * identity prefix (`constants/system.ts:10` in claude-code). Models trained\n * on similar prefixes anchor faster; keep this immutable across releases.\n */\nexport const IDENTITY_PREFIX\n = 'You are zidane, a terminal-based coding agent that helps the user with software-engineering tasks.'\n\n// ---------------------------------------------------------------------------\n// Environment — cwd + date + platform\n// ---------------------------------------------------------------------------\n\nexport interface EnvSectionOptions {\n /**\n * The agent's actual working directory — what `process.cwd()` reports\n * and what its tool calls resolve relative paths against. Distinct\n * from {@link EnvSectionOptions.projectRoot}, which carries the\n * project anchor when the user spawned from a subdirectory.\n */\n cwd: string\n /**\n * Project root (typically the git root). When set AND different from\n * `cwd`, the rendered env block discloses both AND adds a path-\n * convention note — file paths coming from the user's `@`-completion\n * are project-root-relative, NOT cwd-relative, so the model needs to\n * know there's a wider anchor to resolve them against. When omitted\n * or equal to `cwd`, the block stays compact (no project-root line,\n * no note) — same wording as before the subdirectory disclosure\n * landed, so users running from a project's root see no extra text.\n */\n projectRoot?: string\n /** ISO date (YYYY-MM-DD). Defaults to today. Pinned for tests. */\n date?: string\n /** Platform string. Defaults to `process.platform`. Pinned for tests. */\n platform?: string\n}\n\n/**\n * Render the env anchor block. Plain text — XML tags only because models\n * lean on them as structural cues, not because we parse them anywhere.\n *\n * Kept narrow on purpose: cwd / projectRoot / date / platform are the\n * knobs the model can't infer from the conversation. Anything else\n * (git branch, project name) belongs in CLAUDE.md / project-level\n * prompts where the user opts in.\n *\n * Cache-stability: outputs are byte-stable per `(cwd, projectRoot,\n * date, platform)` tuple. Repeated session activations against the\n * same workspace produce the same prefix → provider's prompt cache\n * hits across turns + across sessions in the same host process.\n */\nexport function envSection(opts: EnvSectionOptions): string {\n const date = opts.date ?? new Date().toISOString().slice(0, 10)\n const platform = opts.platform ?? process.platform\n const lines = ['<env>', `Cwd: ${opts.cwd}`]\n if (opts.projectRoot && opts.projectRoot !== opts.cwd) {\n lines.push(`Project root: ${opts.projectRoot}`)\n // Path-resolution note — load-bearing for `@`-completion: the user\n // sees files listed relative to the project root (so any file in\n // the repo can be referenced from any subdirectory), but the\n // agent's tools resolve relative to cwd. Without this note the\n // model treats `@subdir/file.ts` as `<cwd>/subdir/file.ts` and\n // misses it. With the note, the model knows to either rebase the\n // path against `Project root` or compute the relative offset.\n lines.push(\n 'Note: file paths referenced by the user via `@`-completion are '\n + 'relative to `Project root`, not `Cwd`. Resolve them from the '\n + 'project root (use absolute paths when invoking tools to avoid '\n + 'ambiguity).',\n )\n }\n lines.push(`Date: ${date}`, `Platform: ${platform}`, '</env>')\n return lines.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// Doctrine fragments — the core behavior shapers\n// ---------------------------------------------------------------------------\n\n/**\n * Software-engineering doctrine. Lifted in spirit from claude-code's\n * `# Doing tasks` (`constants/prompts.ts:199-253`). Key levers:\n *\n * - Stick to scope — prevents speculative refactors that double output tokens.\n * - No narration comments — prevents inflating diffs with `// Import the module` lines.\n * - Verify before claiming done — prevents false-positive reports that force a follow-up turn.\n */\nexport const DOING_TASKS_DOCTRINE = `# Doing tasks\n- Stick to the scope you were asked for. Don't add features, refactor unrelated code, or \"improve\" things beyond the request. A bug fix doesn't need surrounding cleanup; a small feature doesn't need extra configurability.\n- Don't add comments, docstrings, or type annotations to code you didn't change. Only comment where the WHY is non-obvious (hidden constraint, subtle invariant, workaround). Never narrate WHAT the code does — well-named identifiers already do that.\n- Trust internal code and framework guarantees. Validate at system boundaries (user input, external APIs), not everywhere. Skip backwards-compat shims unless asked.\n- Don't create helpers, utilities, or abstractions for one-time operations. Three similar lines beat a premature abstraction. The right amount of complexity is what the task actually requires.\n- Before reporting a task complete, verify it actually works: run the test, execute the script, check the output. Report outcomes faithfully — if tests fail, say so with the relevant output. Never claim \"all tests pass\" when output shows failures.\n- If you spot a bug adjacent to what was asked, or notice the request is based on a misconception, say so. You are a collaborator, not just an executor.`\n\n/**\n * zidane-specific token-economy doctrine. No claude-code analog — this is\n * the prose half of the `behavior.{compactStrategy, elideStaleReads,\n * toolDisclosure, toolOutputBudget}` defaults. The behavior flags do the\n * heavy lifting at the wire level; this prompt nudges the model away from\n * the access patterns that defeat them.\n */\nexport const TOKEN_DISCIPLINE_DOCTRINE = `# Working efficiently\n- Read a file once. Re-reading the same file in the same session wastes context — the prior \\`read_file\\` result is still in your view (and stale reads after an edit are elided automatically).\n- Narrow before you read. \\`grep\\` / \\`glob\\` to locate, then \\`read_file\\` on the specific file. Don't open a 2000-line file when you only need one function.\n- Prefer \\`edit\\` / \\`multi_edit\\` over \\`write_file\\` for surgical changes — full overwrites are larger payloads and easier to get wrong.\n- Spawn a subagent (\\`spawn\\`) for self-contained research dives or codegen passes that don't need the parent's context. The child runs in its own context window — you only get its final report back.\n- Don't grep for things already shown to you, list directories you already saw, or re-execute commands whose output is still above. Tool budgets nudge against runaway exploration but the cheaper fix is to not start the loop.\n\nTool results may be elided, persisted, or replaced as the conversation grows: stale \\`read_file\\` outputs become short stubs after the file is edited, older tool results may be tail-compacted, and large outputs are written to disk and replaced inline with a \\`<persisted-output tool=\"…\" bytes=\"…\" path=\"…\">\\` block carrying a 2 KiB head preview plus the filesystem path. Copy load-bearing data into your own response text before you'd need to recover it later — and when you see a \\`<persisted-output>\\` block whose preview isn't enough, call \\`read_file\\` on the persisted path to retrieve the full payload.`\n\n/**\n * User-facing communication style. Lifted from claude-code's\n * `# Communicating with the user` + `# Tone and style` sections (~404-442).\n * The \"no colon before tool calls\" rule matters because models often\n * generate \"Let me check this file:\" right before a tool call that the\n * user never sees rendered.\n */\nexport const COMMUNICATION_DOCTRINE = `# Communicating with the user\n- You're writing for a person, not logging to a console. Tool calls and reasoning are not visible — only your text output is. Before your first tool call, briefly state what you're about to do.\n- Give short updates at load-bearing moments: root cause found, direction change, real progress. Skip play-by-play.\n- Write in flowing prose. Avoid excessive em dashes, fragments, symbols, or notation. Tables when they help, not by default.\n- Don't use a colon before tool calls. \"Let me read the file:\" + tool call reads as broken — write \"Let me read the file.\" with a period.\n- Reference code as \\`file:line\\` so the user can navigate (e.g. \\`src/agent.ts:1213\\`).\n- No emojis unless the user explicitly asks.`\n\n/**\n * Reversibility / blast-radius doctrine. Mirrors claude-code's `# Executing\n * actions with care` (255-267). The TUI's safe-mode already gates risky\n * tool calls behind explicit approval, but this prompt teaches the model\n * to anticipate the gate rather than be surprised by it — and to ask\n * before reaching for destructive shortcuts.\n */\nexport const ACTIONS_WITH_CARE_DOCTRINE = `# Executing actions with care\nReversibility scales the required caution. Local edits, tests, and reads are free to take. Destructive or shared-state actions need explicit user confirmation — \\`rm -rf\\`, dropping tables, force-pushing, modifying CI, sending messages, posting to external services. A user approving an action once does NOT mean they approve it in all contexts.\n\nWhen you hit an obstacle, do not use destructive actions as a shortcut to make it go away. Measure twice, cut once.`\n\n/**\n * How to brief a subagent. Spawning a child agent is the highest-leverage\n * token move in zidane — a 5k-token research dive becomes a 200-token\n * summary in the parent's context. But shallow prompts produce shallow\n * work, so the doctrine is mostly about quality of the briefing.\n *\n * Lifted from claude-code's `## Writing the prompt` tool-prompt\n * (`tools/AgentTool/prompt.ts:99-113`).\n */\nexport const SUBAGENT_GUIDANCE = `# When spawning subagents\nBrief the child agent like a smart colleague who just walked in — it has not seen this conversation and doesn't know what you've tried or why this matters.\n- Explain what you're trying to accomplish and why.\n- Describe what you've already learned or ruled out.\n- Give enough context that the child can make judgment calls, not just follow narrow steps.\n- For investigations, hand over the question. For lookups, hand over the exact command.\n- Never delegate understanding (\"based on your findings, fix the bug\"). Synthesize yourself — include file paths, line numbers, and the specific change you want.\n\nTerse command-style prompts produce shallow, generic work.`\n\n/**\n * Plan-mode framing. Replaces the previous one-liner with explicit phases\n * (explore → propose → submit), drawing on claude-code's iterative-plan\n * mode (`utils/messages.ts:3323-3383`). Always paired with the read-only\n * tool set in {@link PLAN_AGENT}.\n */\nexport const PLAN_MODE_DOCTRINE = `# Plan mode — read-only\nYou are in PLAN mode. You can read files, list directories, and search the codebase — but you CANNOT modify files, run shell commands, or spawn subagents. Use this mode to investigate, understand, and design a proposal.\n\n## Loop\n1. **Explore** — Read code. Look for existing functions, utilities, and patterns to reuse.\n2. **Ask** — When you hit an ambiguity only the user can resolve (requirements, preferences, tradeoffs), call \\`ask_user\\` with the full batch of questions.\n3. **Propose** — When the plan is ready, submit it via \\`present_plan\\` with a concrete title, the markdown body, and optional structured steps. The user approves, rejects, or asks for revisions.\n\n## Good plans\n- Cover what to change, which files to modify, what existing code to reuse (with file paths), and how to verify the change end-to-end.\n- Reference real symbols and line numbers from your exploration.\n- Surface only your recommended approach, not every alternative.\n\nThe user will switch to Build mode to execute an approved plan.`\n\n/**\n * How to use `present_plan` / `ask_user`. Kept short — the tool descriptions\n * carry the schema details; this prompt covers the cross-cutting *when*.\n *\n * Previously lived inline in `src/chat/agents.ts` as `INTERACTION_GUIDANCE`.\n * Moved here so a custom profile can pull it in independently.\n */\nexport const INTERACTION_GUIDANCE = `# Interacting with the user\n- Call \\`present_plan\\` when you have a concrete proposal that materially changes the workspace and want explicit confirmation before executing.\n- Call \\`ask_user\\` when clarifying answers materially change the next steps. Batch related questions into one call — every call pauses the conversation.\n- Use both sparingly. Don't ask what you could find out by reading the code.`\n\n/**\n * Variant of {@link INTERACTION_GUIDANCE} for the no-interactive-prompts\n * configuration (`Settings.allowInteraction = false`). The two tools are\n * stripped from the agent's tool set; this section tells the model to\n * operate autonomously and surface assumptions / open questions in its\n * final prose message rather than via tool calls.\n */\nexport const INTERACTION_GUIDANCE_NO_PROMPTS = `# No interactive prompts\nYou don't have \\`ask_user\\` or \\`present_plan\\` available — interactive prompts are disabled. When you hit ambiguity, state your assumption in plain prose and proceed. Surface decisions, tradeoffs, and open questions in your final message rather than pausing mid-task.`\n\n/**\n * Variant of {@link PLAN_MODE_DOCTRINE} for the no-interactive-prompts\n * configuration. The \"Ask\" step is removed; \"Propose\" no longer calls\n * \\`present_plan\\` and instead instructs the model to write the plan\n * directly in its final message.\n */\nexport const PLAN_MODE_DOCTRINE_NO_PROMPTS = `# Plan mode — read-only\nYou are in PLAN mode. You can read files, list directories, and search the codebase — but you CANNOT modify files, run shell commands, or spawn subagents. Use this mode to investigate, understand, and design a proposal.\n\n## Loop\n1. **Explore** — Read code. Look for existing functions, utilities, and patterns to reuse.\n2. **Propose** — When the plan is ready, write it directly in your final response. Cover what to change, which files to modify, what existing code to reuse (with file paths), and how to verify the change end-to-end.\n\n## Good plans\n- Cover what to change, which files to modify, what existing code to reuse (with file paths), and how to verify the change end-to-end.\n- Reference real symbols and line numbers from your exploration.\n- Surface only your recommended approach, not every alternative.\n- Don't pause for clarification — interactive prompts are disabled. When in doubt, state your assumption and proceed.\n\nThe user will switch to Build mode to execute the plan.`\n\n// ---------------------------------------------------------------------------\n// Composers — what the built-in profiles assemble\n// ---------------------------------------------------------------------------\n\nexport interface BuildSystemOptions {\n /** Actual working directory. Omitted ⇒ no env section is rendered. */\n cwd?: string\n /**\n * Project root (typically the git root), when different from `cwd`.\n * When the user spawned the agent from a subdirectory of a larger\n * project, this enables the env block's two-line disclosure + path-\n * resolution note (see {@link envSection}). Same-as-`cwd` or\n * omitted ⇒ the env block stays compact.\n */\n projectRoot?: string\n /** Override date for tests. */\n date?: string\n /** Override platform for tests. */\n platform?: string\n /**\n * Whether the agent has access to interactive tools (`ask_user`,\n * `present_plan`). Default `true`. When `false`, both interactive\n * sections are swapped for their no-prompts variants:\n * - {@link INTERACTION_GUIDANCE} → {@link INTERACTION_GUIDANCE_NO_PROMPTS}\n * - {@link PLAN_MODE_DOCTRINE} → {@link PLAN_MODE_DOCTRINE_NO_PROMPTS}\n *\n * Must agree with the host's actual tool wiring — telling the model a\n * tool exists that the runtime refuses to invoke produces a tool-not-\n * found loop on first ambiguity. Built-in TUI honors this via\n * `Settings.allowInteraction`.\n */\n allowInteraction?: boolean\n /**\n * Pre-rendered user-instructions block from one or more `AGENTS.md`\n * files. Appended verbatim AFTER the built-in doctrine + guidance so\n * user-authored preferences sit closest to the conversation. Omitted\n * or empty ⇒ nothing is appended.\n *\n * Built by {@link discoverAgentsMd} in `./agents-md`; the TUI calls\n * the discovery at session activation and threads the result here.\n */\n userInstructions?: string | null\n}\n\nfunction buildEnvSectionFromOpts(opts: BuildSystemOptions): string | null {\n if (!opts.cwd)\n return null\n return envSection({\n cwd: opts.cwd,\n ...(opts.projectRoot !== undefined ? { projectRoot: opts.projectRoot } : {}),\n ...(opts.date !== undefined ? { date: opts.date } : {}),\n ...(opts.platform !== undefined ? { platform: opts.platform } : {}),\n })\n}\n\n/**\n * Compose the Build-mode system prompt.\n *\n * Layout (top to bottom):\n *\n * identity → doctrine → guidance → user instructions\n * ─── SYSTEM_PROMPT_BOUNDARY ───\n * env (cwd / project root / date / platform)\n *\n * Everything ABOVE the boundary is byte-stable for the lifetime of the run\n * and rides the prompt cache across turns + sessions. The env block sits\n * BELOW so a moved cwd or rolled-over date doesn't invalidate the doctrine.\n *\n * The fragments are joined with blank lines so the model sees clean section\n * breaks. Don't reorder lightly — every reorder costs one cache miss across\n * every active session.\n */\nexport function buildBuildSystem(opts: BuildSystemOptions = {}): string {\n const interactionGuidance = opts.allowInteraction === false\n ? INTERACTION_GUIDANCE_NO_PROMPTS\n : INTERACTION_GUIDANCE\n const env = buildEnvSectionFromOpts(opts)\n return composeWithBoundary({\n static: [\n IDENTITY_PREFIX,\n DOING_TASKS_DOCTRINE,\n ACTIONS_WITH_CARE_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE,\n SUBAGENT_GUIDANCE,\n COMMUNICATION_DOCTRINE,\n interactionGuidance,\n opts.userInstructions || null,\n ],\n dynamic: [env],\n })\n}\n\n/**\n * Compose the Plan-mode system prompt. Subset of Build: drops the\n * subagent + actions-with-care fragments (no shell, no spawn in plan\n * mode) and uses `PLAN_MODE_DOCTRINE` instead of `DOING_TASKS_DOCTRINE`.\n */\nexport function buildPlanSystem(opts: BuildSystemOptions = {}): string {\n const noPrompts = opts.allowInteraction === false\n const env = buildEnvSectionFromOpts(opts)\n return composeWithBoundary({\n static: [\n IDENTITY_PREFIX,\n noPrompts ? PLAN_MODE_DOCTRINE_NO_PROMPTS : PLAN_MODE_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE,\n COMMUNICATION_DOCTRINE,\n noPrompts ? INTERACTION_GUIDANCE_NO_PROMPTS : INTERACTION_GUIDANCE,\n opts.userInstructions || null,\n ],\n dynamic: [env],\n })\n}\n\n/**\n * Glue helper — compose a system prompt with the boundary marker between the\n * static and dynamic halves. Returns a marker-free string when the dynamic\n * half is empty (e.g. SDK consumers building the prompt without an\n * `envSection`), so the historical single-block cache path is preserved\n * end-to-end.\n */\nfunction composeWithBoundary(parts: {\n static: readonly (string | null | undefined)[]\n dynamic: readonly (string | null | undefined)[]\n}): string {\n const staticPart = joinPrompt(parts.static)\n const dynamicPart = joinPrompt(parts.dynamic)\n return joinSystemPrompt(staticPart, dynamicPart)\n}\n\nfunction joinPrompt(parts: readonly (string | null | undefined)[]): string {\n return parts.filter((p): p is string => typeof p === 'string' && p.length > 0).join('\\n\\n')\n}\n","/**\n * Todos — `todowrite` / `todoread` tools for task checkpointing.\n *\n * The model uses these to plan multi-step work and stream progress as it\n * executes. The list is monolithic-replacement-by-design: every `todowrite`\n * overwrites the active list in full, mirroring Anthropic's reference\n * `TodoWrite` semantics.\n *\n * Persistence model — `session.metadata.todos: TodosBag`:\n *\n * ```ts\n * { session?: TodoItem[], byRun?: Record<runId, TodoItem[]> }\n * ```\n *\n * - Top-level runs (no `parentRunId`) **share** the `session` slot. A list\n * written in one prompt survives across the next prompt — the user\n * aborts a run with `in_progress` items, sends a follow-up, and the\n * model's `todoread` returns the same list to continue from. Matches\n * Claude Code's TodoWrite v1 keying (`agentId ?? sessionId`).\n * - Subagent runs (`parentRunId` set) get their own slot under `byRun`\n * keyed by the child's runId. Subagent state never bleeds into the\n * parent's session list — parallel children stay isolated from each\n * other and from the parent.\n * - When every item is `completed`, the slot is **auto-cleared** to `[]`\n * on write — prevents stale \"all done\" lists from shadowing the next\n * prompt's context. Same rationale as Claude Code's `TodoWriteTool.ts:70`.\n * - **Archive sidecar** (`bag.archive`): every non-empty write also\n * stashes the list under `archive.session` / `archive.byRun`. Empty\n * writes (auto-clear or explicit reset) preserve the archive. UI\n * surfaces fall back to it when the live slot is empty so the modal\n * keeps showing \"what was just completed\" — invisible to the model\n * (`todoread` strictly reads the live slot).\n * - Resume is transparent: the persisted `tool_result` block in\n * `session.turns` already carries the latest snapshot, and\n * `session.metadata.todos` restores on reload via the session store.\n *\n * Policy is layered:\n * - Identical-payload dedup: handled **inside the tool body**, against the\n * current slot (session-shared for top-level, per-run for subagents).\n * Does NOT plumb through `behavior.dedupTools` — that cache is keyed\n * per session and would conflate top-level + subagent re-writes.\n * - Soft nudge (\"you've checkpointed N times, slow down\"): rides on the\n * tool's own `tool_result` string. The model sees the nudge in the\n * result it just received — no `system:transform` plumbing. Count is\n * scoped the same way the data is (session-shared for top-level,\n * per-run for subagents) so the nudge fires on cumulative over-use.\n * - Per-run write cap **(opt-in)**: available via\n * `createTodoTools({ maxWritesPerRun: N })`. Off by default —\n * `todowrite` is a state-transition tool, capping it punishes the\n * legitimate \"finish the N-item plan\" path (which needs ≥ N+1\n * calls). Hosts that explicitly want a hard ceiling can opt in.\n *\n * No OpenTUI imports — a GUI consumer reuses this module verbatim.\n */\nimport type { Preset } from '../presets'\nimport type { Session } from '../session'\nimport type { ToolContext, ToolDef } from '../tools/types'\nimport { definePreset } from '../presets'\n\n// ---------------------------------------------------------------------------\n// Tool identities — canonical names persisted in `session.turns`. Stable\n// across the lifetime of the project; renaming would break resume.\n// ---------------------------------------------------------------------------\n\nexport const TODOWRITE_TOOL = 'todowrite'\nexport const TODOREAD_TOOL = 'todoread'\n\n/** True when `name` is one of the todo tool canonical names. */\nexport function isTodoTool(name: string): boolean {\n return name === TODOWRITE_TOOL || name === TODOREAD_TOOL\n}\n\n// ---------------------------------------------------------------------------\n// Session metadata keys — exported so consumers (UI rehydrate, export\n// pipelines, fork helpers) read/write the same shape we do. Keep them\n// alongside the tool to make the contract obvious.\n// ---------------------------------------------------------------------------\n\n/** `session.metadata[TODOS_METADATA_KEY]: TodosBag` — see {@link TodosBag}. */\nexport const TODOS_METADATA_KEY = 'todos'\n/** `session.metadata[TODO_WRITE_COUNTS_METADATA_KEY]: CountsBag` — mirrors `TodosBag` shape. */\nexport const TODO_WRITE_COUNTS_METADATA_KEY = 'todoWriteCounts'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'\n\nexport interface TodoItem {\n /** Stable id — the model picks it. Reusing an id replaces that item. */\n id: string\n /** One-line task summary (Markdown allowed; renderers may sanitize). */\n content: string\n status: TodoStatus\n}\n\n/**\n * On-disk shape at `session.metadata[TODOS_METADATA_KEY]`.\n *\n * Sub-fields are absent (not empty) when they carry no data — keeps the\n * persisted metadata clean and lets `JSON.stringify` skip empty bags\n * entirely on session save.\n */\nexport interface TodosBag {\n /** Top-level slot — shared across runs whose `parentRunId` is unset. */\n session?: TodoItem[]\n /** Per-subagent-run slots, keyed by the child run's id. */\n byRun?: Record<string, TodoItem[]>\n /**\n * Most-recent non-empty list per slot — sidecar for UI surfaces, NOT\n * visible to `todoread`. Updated on every non-empty write; **preserved**\n * across the auto-clear path. Lets the modal keep showing \"what was just\n * completed\" after a fully-done batch wipes the live slot. Empty writes\n * never touch the archive — clearing the live list shouldn't erase the\n * audit trail.\n */\n archive?: {\n session?: TodoItem[]\n byRun?: Record<string, TodoItem[]>\n }\n}\n\n/** Mirror of {@link TodosBag} for the write-counter — same scope rules. */\ninterface CountsBag {\n session?: number\n byRun?: Record<string, number>\n}\n\nconst TODO_STATUS_VALUES: readonly TodoStatus[] = ['pending', 'in_progress', 'completed', 'cancelled']\n\n// ---------------------------------------------------------------------------\n// Status glyphs — single source of truth for every UI surface that\n// renders a todo. Both the modal and the inline indicator import these\n// so the two read as the same visual language. Glyphs are intentionally\n// monochrome / single-cell so they line up cleanly in a column.\n//\n// pending ☐ empty box — \"queued\"\n// in_progress ◐ half-filled circle — \"currently working\"\n// completed ☑ checked box — \"done\"\n// cancelled ☒ crossed box — \"no longer relevant\"\n//\n// The inline indicator uses the `in_progress` glyph as its prefix so a\n// quick glance at the bar matches the modal's row marker. Renderer-\n// agnostic by design — a GUI shell can reuse the same map verbatim.\n// ---------------------------------------------------------------------------\nexport const TODO_STATUS_GLYPHS: Readonly<Record<TodoStatus, string>> = {\n pending: '☐',\n in_progress: '◐',\n completed: '☑',\n cancelled: '☒',\n}\n\nexport interface CreateTodoToolsOptions {\n /**\n * Maximum item count per `todowrite`. Excess items are truncated by the\n * recursive validator and reported back to the model in the tool_result.\n *\n * Default: `100`. The default is generous; a planning agent that exceeds\n * 100 items per call is usually flailing.\n */\n maxItems?: number\n /**\n * Per-run cap on `todowrite` calls, plumbed into `behavior.toolBudgets`.\n * `onMaxWrites` chooses the reaction (steer / block) — see\n * {@link AgentBehavior.toolBudgets} for the contract.\n *\n * **Default: `0` (no cap).** `todowrite` is a state-transition tool —\n * a legitimate run of an N-item list needs at least `N + 1` calls\n * (initial plan + one transition per item) to complete, so a hard\n * cap punishes the legitimate path. The nudges that DO apply are the\n * `remindAfter` reminder (soft, in-band) and the identical-payload\n * dedup check (silent, structural). Hosts that want a true hard cap\n * can opt in by setting `maxWritesPerRun: N`.\n *\n * **Counting semantics** (when opted in): the budget counts every\n * dispatched call, including dedup substitutes (zidane spec). The\n * {@link remindAfter} counter counts execute-only dispatches —\n * dedup hits replay the prior result and never reach the tool body.\n * Use this budget (not the reminder) as the hard limit.\n */\n maxWritesPerRun?: number\n onMaxWrites?: 'steer' | 'block'\n /**\n * Append a \"you've checkpointed N times — only call this at significant\n * task transitions\" nudge to the tool_result once `todowrite` has fired\n * this many times in the current run. The nudge rides on the result\n * string, so the model sees it on its next turn without `system:transform`\n * plumbing.\n *\n * Counts only executed dispatches (dedup hits skip the tool body and so\n * skip the increment). For a hard cap that also covers dedup hits, use\n * {@link maxWritesPerRun}.\n *\n * Set to `0` to opt out. Default: `3`.\n */\n remindAfter?: number\n /**\n * Custom reminder text. Receives the post-call count and the items that\n * were just written. Return `''` (or `undefined`) to suppress the reminder\n * for this specific call. Default text nudges towards \"back to executing,\n * not re-planning.\"\n */\n reminderText?: (count: number, items: readonly TodoItem[]) => string | undefined\n /**\n * Short-circuit the tool body when the incoming payload is identical to\n * the **current slot** (session-shared for top-level runs, per-run for\n * subagents). The model gets a \"No change — N items already tracked.\"\n * acknowledgment instead of a fresh \"Updated N items.\" line.\n * Default: `true`.\n *\n * Read `session.metadata.todos` via {@link getTodosForRun} for the\n * comparison — so the resolution naturally follows the keying rule\n * (top-level reads the session slot, subagent reads its own). Does\n * NOT plumb through `behavior.dedupTools`: that cache lives at the\n * gate, is keyed per session, and would conflate top-level + subagent\n * re-writes of structurally identical payloads.\n *\n * Counter advances on identical re-writes regardless — the model\n * called the tool, that's a call. Lets the reminder threshold catch\n * spam. Set `false` to skip the check entirely (every call rewrites\n * the slot, useful when state-shape comparisons are pointless for\n * your usage).\n */\n dedupIdentical?: boolean\n /**\n * Override the JSON-schema-level descriptions surfaced to the model.\n * Useful for telemetry experiments. Leave unset to inherit the built-in\n * doctrine prose.\n */\n writeDescription?: string\n readDescription?: string\n}\n\n// ---------------------------------------------------------------------------\n// Built-in doctrine prose\n// ---------------------------------------------------------------------------\n\nconst WRITE_DESCRIPTION\n = 'Replace the active task list. Pass the **full** list of items every '\n + 'call — there is no partial update. The list persists across user '\n + 'prompts in the same session, so an aborted run with `in_progress` '\n + 'items reads back on the next `todoread` and you can pick up where '\n + 'you left off. When every item is `completed`, the list auto-clears '\n + 'so the next prompt starts fresh.\\n\\n'\n + 'Use the `status` field to track progress: `pending` (queued), '\n + '`in_progress` (currently working), `completed` (done), `cancelled` '\n + '(no longer relevant).\\n\\n'\n + 'Only checkpoint at significant transitions:\\n'\n + ' 1. When the user gives you a multi-step task — write the initial '\n + 'plan with everything `pending`.\\n'\n + ' 2. When you transition between steps — mark the previous one '\n + '`completed` and the next one `in_progress`.\\n'\n + ' 3. When the user asks for the current status — re-emit the list '\n + 'unchanged so they can see it.\\n\\n'\n + 'Do NOT call this on every action. The list is for the user\\'s '\n + 'situational awareness, not for self-narrating.'\n\nconst READ_DESCRIPTION\n = 'Return the current active task list. Returns an empty list if nothing '\n + 'has been written yet, or if the previous batch was auto-cleared '\n + 'after all items completed. Use sparingly — you already see the '\n + 'latest list in your own `todowrite` tool_result above.'\n\nfunction defaultReminder(count: number): string {\n return `(You've called todowrite ${count} times. Make sure each `\n + `checkpoint reflects real progress; avoid re-planning every step.)`\n}\n\n// ---------------------------------------------------------------------------\n// Session metadata accessors — single source of truth for the `TodosBag`\n// shape. Tests, fork helpers, UI rehydrate, and the tool bodies all go\n// through these so the keying contract (top-level vs subagent) stays in\n// one place.\n// ---------------------------------------------------------------------------\n\n/**\n * `true` when the run with `runId` is a subagent (has a `parentRunId`).\n * Top-level runs return `false`; an unknown / missing run also returns\n * `false` (defensive — should never happen in practice, but treating an\n * orphan as top-level is the right fallback since the session slot is\n * the more general bucket).\n */\nfunction isSubagentRun(session: Session, runId: string): boolean {\n const run = session.runs.find(r => r.id === runId)\n return !!run?.parentRunId\n}\n\n/**\n * Read the active list for `runId`. Top-level runs see the session-shared\n * slot; subagent runs see their own. Returns a fresh `[]` (not stored in\n * metadata) when no slot exists — the caller can mutate the result\n * without affecting state.\n */\nexport function getTodosForRun(session: Session, runId: string): TodoItem[] {\n const bag = readTodosBag(session)\n if (isSubagentRun(session, runId)) {\n const items = bag.byRun?.[runId]\n return Array.isArray(items) ? [...items] : []\n }\n return Array.isArray(bag.session) ? [...bag.session] : []\n}\n\n/**\n * Replace the active list for `runId`. Routes to the session-shared slot\n * for top-level runs and to the per-run slot for subagents. Empty arrays\n * delete the slot entirely so persisted metadata stays clean.\n *\n * Side effect: a **non-empty** write also stashes the same list into\n * `bag.archive` (same routing). Empty writes never touch the archive —\n * clearing the live list (whether explicitly or via the tool's auto-\n * clear) preserves the \"last shown\" snapshot so UI surfaces can keep\n * rendering what was just completed. See {@link getArchivedTodosForRun}.\n */\nexport function setTodosForRun(session: Session, runId: string, items: readonly TodoItem[]): void {\n const bag = readTodosBag(session)\n const normalized = items.map(normalizeItem)\n const subagent = isSubagentRun(session, runId)\n\n // Active slot.\n if (subagent) {\n const byRun = { ...(bag.byRun ?? {}) }\n if (normalized.length === 0)\n delete byRun[runId]\n else\n byRun[runId] = normalized\n bag.byRun = byRun\n }\n else if (normalized.length === 0) {\n delete bag.session\n }\n else {\n bag.session = normalized\n }\n\n // Archive — only on non-empty writes. Empty writes preserve the prior\n // archive so the modal keeps showing \"what was just done\" after the\n // live slot wipes.\n if (normalized.length > 0) {\n const archive = { ...(bag.archive ?? {}) }\n if (subagent) {\n const archiveByRun = { ...(archive.byRun ?? {}) }\n archiveByRun[runId] = normalized\n archive.byRun = archiveByRun\n }\n else {\n archive.session = normalized\n }\n bag.archive = archive\n }\n\n writeTodosBag(session, bag)\n}\n\n/**\n * Read the archived (most-recent non-empty) list for `runId`. Same slot\n * routing as {@link getTodosForRun}, but reads `bag.archive` instead of\n * the live slot. Returns a fresh `[]` when no archive exists.\n *\n * UI surfaces use this to keep showing \"what was just completed\" after\n * the live list auto-clears. The model never sees the archive — it's a\n * read-only sidecar for human-facing rendering.\n */\nexport function getArchivedTodosForRun(session: Session, runId: string): TodoItem[] {\n const bag = readTodosBag(session)\n const archive = bag.archive\n if (!archive)\n return []\n if (isSubagentRun(session, runId)) {\n const items = archive.byRun?.[runId]\n return Array.isArray(items) ? [...items] : []\n }\n return Array.isArray(archive.session) ? [...archive.session] : []\n}\n\n/**\n * Reconcile `session.metadata.todos.byRun` (live + archive) against\n * `session.runs`. Drops subagent slots whose runId isn't in the run\n * list. The top-level `session` slot is unaffected — it's session-\n * scoped, not per-run, so there's no orphan to GC.\n *\n * Useful after `session.setRuns()` (fork / restore) or to GC stale\n * metadata mutated by an external caller. Also prunes the parallel\n * counter bag so the two don't drift.\n *\n * `dropped` reports the live-byRun orphans only (archive-only orphans\n * are silently swept). The live half is the contract callers actually\n * observe.\n */\nexport function pruneTodosByRun(session: Session): { dropped: readonly string[] } {\n const validRunIds = new Set(session.runs.map(r => r.id))\n const dropped: string[] = []\n\n const bag = readTodosBag(session)\n let bagChanged = false\n if (bag.byRun) {\n const nextByRun: Record<string, TodoItem[]> = {}\n for (const [runId, items] of Object.entries(bag.byRun)) {\n if (validRunIds.has(runId)) {\n nextByRun[runId] = items\n }\n else {\n dropped.push(runId)\n bagChanged = true\n }\n }\n if (Object.keys(nextByRun).length !== Object.keys(bag.byRun).length)\n bag.byRun = nextByRun\n }\n if (bag.archive?.byRun) {\n const nextByRun: Record<string, TodoItem[]> = {}\n let archiveChanged = false\n for (const [runId, items] of Object.entries(bag.archive.byRun)) {\n if (validRunIds.has(runId))\n nextByRun[runId] = items\n else\n archiveChanged = true\n }\n if (archiveChanged) {\n bag.archive = { ...bag.archive, byRun: nextByRun }\n bagChanged = true\n }\n }\n if (bagChanged)\n writeTodosBag(session, bag)\n\n const counts = readCountsBag(session)\n if (counts.byRun) {\n const nextByRun: Record<string, number> = {}\n let changed = false\n for (const [runId, n] of Object.entries(counts.byRun)) {\n if (validRunIds.has(runId))\n nextByRun[runId] = n\n else\n changed = true\n }\n if (changed) {\n counts.byRun = nextByRun\n writeCountsBag(session, counts)\n }\n }\n\n return { dropped }\n}\n\n// ---------------------------------------------------------------------------\n// Tool factory\n// ---------------------------------------------------------------------------\n\n/**\n * Build a `Preset` carrying the `{ todowrite, todoread }` tool pair.\n * Identical-payload dedup is handled inside the tool body — the\n * comparison resolves against the active slot (session-shared for\n * top-level runs, per-run for subagents) and intentionally does NOT\n * plumb through `behavior.dedupTools`. See the `dedupIdentical` option\n * doc for the rationale.\n *\n * By default the preset adds **no** `toolBudgets` entry — `todowrite`\n * is a state-transition tool, so a hard cap is the wrong abstraction\n * (it punishes the legitimate \"finish the N-item plan\" path). Hosts\n * that want a ceiling pass `maxWritesPerRun: N` explicitly; the\n * factory then plumbs a `behavior.toolBudgets.todowrite` entry\n * through.\n *\n * Returning a `Preset` (not a bare tool map) lets the result flow\n * through {@link composePresets} unchanged — todos compose with any\n * other preset the same way every other preset does. `toolBudgets` is\n * a tool-name-keyed record that `composePresets` deep-merges, so a\n * caller's custom budget entries for other tools survive the\n * layering, and a caller's override for `todowrite` itself wins by\n * being placed later in the chain.\n *\n * ```ts\n * import { basic, composePresets } from 'zidane/presets'\n * import { createTodoTools } from 'zidane/chat'\n *\n * // Default: no budget — relies on the in-band reminder + dedup.\n * createAgent({ ...composePresets(basic, createTodoTools()), provider })\n *\n * // Opt-in hard cap: refuse > 20 writes per run.\n * createAgent({\n * ...composePresets(basic, createTodoTools({ maxWritesPerRun: 20 })),\n * provider,\n * })\n * ```\n *\n * For the trivial \"just add the tools to an existing config\" case, plain\n * spread is also fine since the returned `Preset` only sets `tools`\n * (and `behavior` when a budget is opted in):\n *\n * ```ts\n * createAgent({ ...basic, ...createTodoTools(), provider })\n * ```\n */\nexport function createTodoTools(options: CreateTodoToolsOptions = {}): Preset {\n const maxItems = options.maxItems ?? 100\n const remindAfter = options.remindAfter ?? 3\n const reminderText = options.reminderText ?? ((count, _items) => defaultReminder(count))\n const dedupIdentical = options.dedupIdentical ?? true\n // Default opt-out — `todowrite` is a state-transition tool, capping\n // it punishes legitimate \"finish the 5-item plan\" paths (which need\n // ≥6 calls). The `remindAfter` reminder + `dedupIdentical` check\n // handle spam in-band; a host that wants a hard ceiling can opt in\n // explicitly.\n const maxWritesPerRun = options.maxWritesPerRun ?? 0\n const onMaxWrites = options.onMaxWrites ?? 'steer'\n\n const tools: Record<string, ToolDef> = {\n [TODOWRITE_TOOL]: createTodoWriteTool({\n maxItems,\n remindAfter,\n reminderText,\n dedupIdentical,\n description: options.writeDescription ?? WRITE_DESCRIPTION,\n }),\n [TODOREAD_TOOL]: createTodoReadTool({\n description: options.readDescription ?? READ_DESCRIPTION,\n }),\n }\n\n // Only `toolBudgets` plumbs through the framework's behavior surface — the\n // budget needs to fire at the `tool:gate` layer to enforce a hard cap.\n // Dedup deliberately lives in the tool body instead of `behavior.dedupTools`\n // because the framework's dedup cache is session-scoped and would silently\n // drop cross-run re-writes of an identical list (see `dedupIdentical` JSDoc).\n const behavior: NonNullable<Preset['behavior']> = {}\n if (maxWritesPerRun > 0)\n behavior.toolBudgets = { [TODOWRITE_TOOL]: { max: maxWritesPerRun, onExceed: onMaxWrites } }\n\n return definePreset(\n Object.keys(behavior).length > 0\n ? { tools, behavior }\n : { tools },\n )\n}\n\n// ---------------------------------------------------------------------------\n// Tool implementations\n// ---------------------------------------------------------------------------\n\ninterface CreateTodoWriteOptions {\n maxItems: number\n remindAfter: number\n reminderText: (count: number, items: readonly TodoItem[]) => string | undefined\n dedupIdentical: boolean\n description: string\n}\n\nfunction createTodoWriteTool(opts: CreateTodoWriteOptions): ToolDef {\n return {\n spec: {\n name: TODOWRITE_TOOL,\n description: opts.description,\n inputSchema: {\n type: 'object',\n properties: {\n todos: {\n type: 'array',\n description: 'The complete task list. Replaces the prior list in full.',\n maxItems: opts.maxItems,\n items: {\n type: 'object',\n properties: {\n id: { type: 'string', description: 'Stable identifier (the model picks).' },\n content: { type: 'string', description: 'One-line task summary.' },\n status: {\n type: 'string',\n enum: [...TODO_STATUS_VALUES],\n description: '`pending`, `in_progress`, `completed`, or `cancelled`.',\n },\n },\n required: ['id', 'content', 'status'],\n },\n },\n },\n required: ['todos'],\n },\n },\n async execute(input, ctx) {\n const { session, runId } = requireSessionAndRun(ctx, TODOWRITE_TOOL)\n\n const rawItems = Array.isArray(input.todos) ? input.todos : []\n const items = sanitizeItems(rawItems, opts.maxItems)\n const dropped = rawItems.length - items.length\n\n // ID dedup — last-wins. Two items in one payload sharing an id is\n // a model mistake; quietly normalize rather than error.\n const byId = new Map<string, TodoItem>()\n for (const item of items)\n byId.set(item.id, item)\n const normalized = [...byId.values()]\n\n // Identical-payload short-circuit — compares against the slot the\n // active run resolves to (session-shared for top-level, per-run\n // for subagents). See `dedupIdentical` JSDoc.\n const current = getTodosForRun(session, runId)\n const unchanged = opts.dedupIdentical && todosEqual(current, normalized)\n\n // Counter advances on every call — including identical re-writes —\n // so the reminder threshold catches \"model keeps re-checkpointing\n // the same list\" spam. Scope mirrors the data (session-shared for\n // top-level, per-run for subagents) so the count reflects\n // cumulative use of the *active* slot, not noisy per-prompt resets.\n const count = incrementCount(session, runId)\n\n // Auto-clear when every item is `completed`. Matches Claude Code's\n // TodoWrite v1 behavior — once a batch is fully done, the next\n // prompt should start clean rather than re-read a wall of\n // checkmarks. The model's reply still summarizes the completion;\n // only the persisted slot is wiped.\n const allCompleted = normalized.length > 0 && normalized.every(t => t.status === 'completed')\n\n if (!unchanged) {\n if (allCompleted) {\n // Two-phase auto-clear: first persist the all-completed list\n // so the archive captures it (setTodosForRun copies non-empty\n // writes into the archive sidecar), then wipe the live slot.\n // The empty write preserves the archive, so the modal can\n // keep showing the close-out batch until the model writes\n // something new.\n setTodosForRun(session, runId, normalized)\n setTodosForRun(session, runId, [])\n }\n else {\n setTodosForRun(session, runId, normalized)\n }\n }\n\n return formatWriteResult({\n items: normalized,\n dropped,\n count,\n unchanged,\n // Only report the auto-clear when it actually fired (a no-op\n // re-write of an already-cleared list shouldn't shout\n // \"marked complete\" again).\n cleared: allCompleted && !unchanged,\n opts,\n })\n },\n }\n}\n\ninterface FormatWriteResultInput {\n items: readonly TodoItem[]\n dropped: number\n count: number\n unchanged: boolean\n cleared: boolean\n opts: CreateTodoWriteOptions\n}\n\nfunction formatWriteResult(input: FormatWriteResultInput): string {\n const { items, dropped, count, unchanged, cleared, opts } = input\n const lines: string[] = []\n const n = items.length\n const suffix = n === 1 ? '' : 's'\n if (cleared) {\n lines.push(`Marked ${n} item${suffix} complete — list cleared.`)\n }\n else if (unchanged) {\n lines.push(`No change — ${n} todo item${suffix} already tracked.`)\n }\n else {\n lines.push(`Updated ${n} todo item${suffix}.`)\n }\n // Skip the tally when the list was cleared — the headline already\n // captures \"all done\" and the tally would read as a redundant\n // restatement.\n if (!cleared) {\n const tally = summarizeStatuses(items)\n if (tally)\n lines.push(tally)\n }\n if (dropped > 0)\n lines.push(`Dropped ${dropped} malformed item${dropped === 1 ? '' : 's'}.`)\n const reminder = opts.remindAfter > 0 && count >= opts.remindAfter\n ? opts.reminderText(count, items)\n : undefined\n if (reminder && reminder.length > 0)\n lines.push(reminder)\n return lines.join('\\n')\n}\n\nfunction createTodoReadTool(opts: { description: string }): ToolDef {\n return {\n // Reads `session.metadata.todos` without mutation — concurrency-safe.\n isConcurrencySafe: true,\n spec: {\n name: TODOREAD_TOOL,\n description: opts.description,\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n async execute(_input, ctx) {\n const { session, runId } = requireSessionAndRun(ctx, TODOREAD_TOOL)\n\n const items = getTodosForRun(session, runId)\n if (items.length === 0)\n return 'No todos yet — call todowrite to start tracking tasks.'\n return JSON.stringify({ todos: items })\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Both tool bodies need a `Session` + `runId`. Centralize the guards so the\n * error messages stay symmetric and one fix lands in both places.\n */\nfunction requireSessionAndRun(\n ctx: ToolContext,\n toolName: string,\n): { session: Session, runId: string } {\n if (!ctx.session)\n throw new Error(`${toolName}: no session on tool context — todos require a session via createSession().`)\n if (!ctx.runId)\n throw new Error(`${toolName}: no runId on tool context.`)\n return { session: ctx.session, runId: ctx.runId }\n}\n\n/** Read the todos bag from session metadata, normalizing the live shape. */\nfunction readTodosBag(session: Session): TodosBag {\n const raw = session.metadata[TODOS_METADATA_KEY]\n if (!isPlainObject(raw))\n return {}\n const archive = isPlainObject(raw.archive)\n ? {\n session: Array.isArray(raw.archive.session) ? raw.archive.session as TodoItem[] : undefined,\n byRun: isPlainObject(raw.archive.byRun) ? raw.archive.byRun as Record<string, TodoItem[]> : undefined,\n }\n : undefined\n return {\n session: Array.isArray(raw.session) ? raw.session as TodoItem[] : undefined,\n byRun: isPlainObject(raw.byRun) ? raw.byRun as Record<string, TodoItem[]> : undefined,\n archive,\n }\n}\n\nfunction writeTodosBag(session: Session, bag: TodosBag): void {\n // Strip empty sub-fields so the persisted shape stays minimal.\n const clean: TodosBag = {}\n if (bag.session && bag.session.length > 0)\n clean.session = bag.session\n if (bag.byRun && Object.keys(bag.byRun).length > 0)\n clean.byRun = bag.byRun\n if (bag.archive) {\n const archive: NonNullable<TodosBag['archive']> = {}\n if (bag.archive.session && bag.archive.session.length > 0)\n archive.session = bag.archive.session\n if (bag.archive.byRun && Object.keys(bag.archive.byRun).length > 0)\n archive.byRun = bag.archive.byRun\n if (archive.session || archive.byRun)\n clean.archive = archive\n }\n session.setMeta(TODOS_METADATA_KEY, clean)\n}\n\nfunction readCountsBag(session: Session): CountsBag {\n const raw = session.metadata[TODO_WRITE_COUNTS_METADATA_KEY]\n if (!isPlainObject(raw))\n return {}\n return {\n session: typeof raw.session === 'number' ? raw.session : undefined,\n byRun: isPlainObject(raw.byRun) ? raw.byRun as Record<string, number> : undefined,\n }\n}\n\nfunction writeCountsBag(session: Session, bag: CountsBag): void {\n const clean: CountsBag = {}\n if (typeof bag.session === 'number' && bag.session > 0)\n clean.session = bag.session\n if (bag.byRun && Object.keys(bag.byRun).length > 0)\n clean.byRun = bag.byRun\n session.setMeta(TODO_WRITE_COUNTS_METADATA_KEY, clean)\n}\n\n/**\n * Bump the call counter for the active slot and return the new value.\n * Top-level runs increment `bag.session` (cumulative across prompts);\n * subagent runs increment their own entry under `bag.byRun`. Same\n * keying as {@link setTodosForRun} so the counter and the data move\n * together — a freshly seeded run's count starts at 1 on its first\n * write, a continuing top-level run's count picks up from where the\n * prior prompt left off.\n */\nfunction incrementCount(session: Session, runId: string): number {\n const bag = readCountsBag(session)\n let next: number\n if (isSubagentRun(session, runId)) {\n const byRun = { ...(bag.byRun ?? {}) }\n next = (byRun[runId] ?? 0) + 1\n byRun[runId] = next\n bag.byRun = byRun\n }\n else {\n next = (bag.session ?? 0) + 1\n bag.session = next\n }\n writeCountsBag(session, bag)\n return next\n}\n\n/** Narrow guard — `Record<string, unknown>` excluding arrays / null. */\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return !!v && typeof v === 'object' && !Array.isArray(v)\n}\n\nfunction normalizeItem(item: TodoItem): TodoItem {\n return { id: item.id, content: item.content, status: item.status }\n}\n\nfunction sanitizeItems(raw: unknown[], cap: number): TodoItem[] {\n const out: TodoItem[] = []\n for (const item of raw) {\n if (!item || typeof item !== 'object' || Array.isArray(item))\n continue\n const obj = item as Record<string, unknown>\n const id = typeof obj.id === 'string' ? obj.id : undefined\n const content = typeof obj.content === 'string' ? obj.content : undefined\n const status = typeof obj.status === 'string' && (TODO_STATUS_VALUES as readonly string[]).includes(obj.status)\n ? obj.status as TodoStatus\n : undefined\n if (!id || !content || !status)\n continue\n out.push({ id, content, status })\n if (out.length >= cap)\n break\n }\n return out\n}\n\nfunction summarizeStatuses(items: readonly TodoItem[]): string | undefined {\n if (items.length === 0)\n return undefined\n const counts: Record<TodoStatus, number> = {\n pending: 0,\n in_progress: 0,\n completed: 0,\n cancelled: 0,\n }\n for (const item of items)\n counts[item.status] += 1\n const parts: string[] = []\n if (counts.completed)\n parts.push(`${counts.completed} completed`)\n if (counts.in_progress)\n parts.push(`${counts.in_progress} in progress`)\n if (counts.pending)\n parts.push(`${counts.pending} pending`)\n if (counts.cancelled)\n parts.push(`${counts.cancelled} cancelled`)\n return parts.length > 0 ? parts.join(' · ') : undefined\n}\n\n/**\n * Order-sensitive structural equality on todo lists. Used by the\n * `dedupIdentical` short-circuit to decide whether an incoming payload\n * matches the current run's stored slot.\n *\n * Order-sensitive on purpose: re-ordering the list is a meaningful state\n * change (\"the model reprioritized\") and should NOT collapse to a no-op.\n * Three-field comparison is exhaustive for `TodoItem` — `normalizeItem`\n * strips any extra fields before they reach the slot, so we never need to\n * compare beyond `{ id, content, status }`.\n */\nfunction todosEqual(a: readonly TodoItem[], b: readonly TodoItem[]): boolean {\n if (a.length !== b.length)\n return false\n for (let i = 0; i < a.length; i++) {\n const x = a[i]\n const y = b[i]\n if (x.id !== y.id || x.status !== y.status || x.content !== y.content)\n return false\n }\n return true\n}\n\n// ---------------------------------------------------------------------------\n// UI-facing derivation — active-run slice + tally + in-progress pick.\n//\n// Renderer-agnostic. The TUI's inline indicator + modal both consume\n// {@link useActiveTodos}; a future GUI shell reuses the same hook.\n// ---------------------------------------------------------------------------\n\nexport interface TodoTally {\n pending: number\n in_progress: number\n completed: number\n cancelled: number\n}\n\nexport interface ActiveTodosState {\n /** Run the slice came from. `null` ⇒ no session / no runs / no active run. */\n runId: string | null\n /**\n * Live list — what `todoread` would return. Empty when the slot was\n * cleared (e.g. auto-clear after every item completed) or never\n * written. Source of truth for the model's view.\n */\n todos: readonly TodoItem[]\n /**\n * Most-recent non-empty list for the active slot, preserved across\n * auto-clear. Equals `todos` when the live list is non-empty;\n * otherwise the last batch that was shown (commonly the all-`completed`\n * close-out). UI surfaces fall back to this when `todos` is empty so\n * the user can still glance back at \"what was just finished\" — until\n * the model writes a new list. Empty array when no list was ever\n * written for this slot.\n */\n archive: readonly TodoItem[]\n /**\n * The first `in_progress` item from `todos` (the **live** list), when\n * one exists. Drives the inline indicator above the prompt. Always\n * `null` when the live list is empty — even if the archive carries\n * an in-progress item, that item is by definition stale.\n */\n inProgress: TodoItem | null\n /**\n * Per-status counts. Computed over `todos` when non-empty, else over\n * `archive`. Zeros only when both are empty. Lets the modal header\n * show a meaningful tally for the archived \"last batch\" view.\n */\n tally: TodoTally\n}\n\nconst EMPTY_TALLY: TodoTally = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 }\nconst EMPTY_ACTIVE: ActiveTodosState = {\n runId: null,\n todos: [],\n archive: [],\n inProgress: null,\n tally: EMPTY_TALLY,\n}\n\n/**\n * Pick the \"active\" run for UI surfaces that show one slot at a time\n * (todos indicator, todos modal, footer ctx indicator). The resolved\n * runId then routes through {@link getTodosForRun} — top-level runs\n * land on the session-shared slot, subagent runs on their own.\n *\n * - A currently-running run wins (latest-started one if several).\n * - Otherwise the most recently appended run wins.\n * - `null` when the session has no runs.\n *\n * Mirrors the \"most recent thing that mattered\" policy that drives\n * `lastContextSizeFromTurns` — but walks `session.runs` rather than\n * `session.turns` because the active todo slot is a function of the\n * run, not of message history.\n */\nexport function pickActiveRunId(session: Session | null | undefined): string | null {\n if (!session)\n return null\n const runs = session.runs\n if (!runs || runs.length === 0)\n return null\n // Scan from the tail — latest-running wins, falling back to latest of any\n // status. Subagent runs are eligible like top-level runs: a child spawned\n // off the parent has its own runId / its own todo slot, and surfacing it\n // here matches the per-run isolation the data layer already guarantees.\n let latestRunning: string | null = null\n for (let i = runs.length - 1; i >= 0; i--) {\n if (runs[i].status === 'running') {\n latestRunning = runs[i].id\n break\n }\n }\n if (latestRunning)\n return latestRunning\n return runs[runs.length - 1].id\n}\n\n/**\n * Pure selector — derive the active-todos state from a session snapshot.\n * Used by {@link useActiveTodos} and directly testable without React.\n *\n * Resolves both `todos` (live, model-facing) and `archive` (last\n * non-empty snapshot for UI fallback). The tally folds over whichever\n * list is non-empty; `inProgress` is sourced strictly from `todos`\n * because an archived in-progress item is by definition stale.\n */\nexport function selectActiveTodos(session: Session | null | undefined): ActiveTodosState {\n const runId = pickActiveRunId(session)\n if (!session || !runId)\n return EMPTY_ACTIVE\n const todos = getTodosForRun(session, runId)\n const archive = getArchivedTodosForRun(session, runId)\n // Whichever list the modal would show. `todos` wins when live,\n // `archive` fills in after auto-clear, empty when neither exists.\n const display = todos.length > 0 ? todos : archive\n if (display.length === 0)\n return { runId, todos, archive, inProgress: null, tally: EMPTY_TALLY }\n const tally: TodoTally = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 }\n let inProgress: TodoItem | null = null\n // Tally folds over `display`; `inProgress` only over `todos` so we\n // don't surface an item that's no longer being worked on.\n for (const t of display)\n tally[t.status] += 1\n if (todos.length > 0) {\n for (const t of todos) {\n if (t.status === 'in_progress') {\n inProgress = t\n break\n }\n }\n }\n return { runId, todos, archive, inProgress, tally }\n}\n\n/**\n * React hook — active-todos state for the supplied session. Recomputes\n * on every parent re-render; the work is O(runs + todos), both bounded\n * by small constants in practice.\n *\n * Why no memoization: `Session.runs` is mutated in place by `completeRun`\n * / `abortRun` (status flips without changing array length or item\n * identity), so any memo keyed off length / bag identity would silently\n * return stale state when e.g. a child subagent run completes and the\n * \"active\" run should reflow to the parent. The selector is cheap\n * enough that running it every parent render is the simpler, correct\n * answer — see `selectActiveTodos` for the O(n) cost.\n *\n * The hook lives in `chat/` so a GUI consumer can mount it verbatim.\n * Renderer-agnostic — no `@opentui/*` imports.\n */\nexport function useActiveTodos(session: Session | null | undefined): ActiveTodosState {\n return selectActiveTodos(session)\n}\n","/**\n * Agent profiles — named variants of the chat agent (Build, Plan, …).\n *\n * Each profile bundles a {@link Preset} with display metadata (label,\n * description, accent color) so the TUI can render a picker, persist the\n * user's choice, and rebuild the active agent on switch.\n *\n * The host's {@link ChatOptions.preset} is the single-agent legacy\n * shorthand; for multi-profile setups, pass `agents` (an {@link AgentRegistry})\n * directly. Built-ins ship as {@link BUILTIN_AGENTS} = `{ build, plan }`.\n *\n * No OpenTUI dependency — this module is pure data so a GUI host can\n * consume the same registry.\n */\nimport type { Preset } from '../presets'\nimport { composePresets, definePreset } from '../presets'\nimport { edit, glob, grep, listFiles, multiEdit, readFile, shell, writeFile } from '../tools'\nimport { createSpawnTool } from '../tools/spawn'\nimport { buildBuildSystem, buildPlanSystem } from './agent-prompt'\nimport { createTodoTools } from './todos'\n\n/**\n * Theme color token used to accent the active profile in the UI (footer\n * badge, picker highlight). Resolved against `ThemeColors` via\n * `useColors()`; falls back to `accent` when omitted.\n */\nexport type AgentAccent = 'brand' | 'accent' | 'warn' | 'model'\n\n/**\n * Resolve a profile's `accent` token to a concrete hex color via the\n * caller's color palette. Renderer-agnostic — accepts the four-role\n * subset of `ThemeColors` that's relevant to accent surfaces, so this\n * helper composes against either `useColors()` (TUI) or a CSS-variable\n * lookup (GUI). Omitted / unknown tokens fall back to `accent`.\n */\nexport function accentColor(\n accent: AgentAccent | undefined,\n COLOR: { brand: string, accent: string, warn: string, model: string },\n): string {\n switch (accent) {\n case 'brand': return COLOR.brand\n case 'warn': return COLOR.warn\n case 'model': return COLOR.model\n case 'accent':\n default:\n return COLOR.accent\n }\n}\n\nexport interface AgentProfile {\n /** Stable identifier persisted in `TuiState.lastAgent` and shown in keybindings. */\n id: string\n /** Human-readable label rendered in the picker and footer badge. */\n label: string\n /** One-line description shown next to the label in the picker. */\n description: string\n /**\n * Preset applied to `createAgent()` when this profile is active. Profiles\n * are self-contained: the host's chat-level `preset` (if any) is NOT\n * merged underneath. Hosts that want a shared base across profiles\n * should compose it explicitly in each profile's preset.\n */\n preset: Preset\n /** Theme token used by the picker / footer badge. Defaults to `'accent'`. */\n accent?: AgentAccent\n}\n\nexport type AgentRegistry = Readonly<Record<string, AgentProfile>>\n\n/** Read-only tool slice shared by the Plan profile and any host-built read-only variant. */\nconst READ_ONLY_TOOLS = { readFile, listFiles, glob, grep }\n\n/** Full build-mode tool slice — read + write + shell + spawn. */\nconst BUILD_TOOLS = { shell, readFile, writeFile, listFiles, edit, multiEdit, glob, grep }\n\n/**\n * Tools the chat layer never persists to disk, regardless of output size.\n *\n * Rationale per entry:\n * - `read_file` — our reader is already paginated (`offset` + `limit`).\n * Persisting the slice the model asked for and pointing it at the\n * persisted path is circular; if the model wants more, it pages.\n * - `tool_search` — short metadata envelope. Never large.\n * - `skills_use` / `skills_read` — the `<skill_content>` body is\n * intentionally part of the prompt; persisting it defeats the purpose.\n * - `present_plan` / `ask_user` — short JSON envelopes (decision +\n * answers). Never large.\n * - `spawn` — the subagent's final report IS the value of the call;\n * stubbing it out would defeat the orchestration pattern.\n *\n * Exported so SDK consumers building custom profiles can extend the list\n * (e.g. host-defined `screenshot` tool whose base64 payload should stay\n * inline). Set `behavior.persistExcludeTools` to your own list to fully\n * override.\n */\nexport const DEFAULT_PERSIST_EXCLUDE_TOOLS: readonly string[] = [\n 'read_file',\n 'tool_search',\n 'skills_use',\n 'skills_read',\n 'present_plan',\n 'ask_user',\n 'spawn',\n // Todos are short JSON envelopes (≤100 items) and the latest snapshot\n // is the only state of interest — persisting them to disk is circular.\n 'todowrite',\n 'todoread',\n]\n\n/**\n * Tools whose output is exempt from the per-turn `toolOutputBudget` accounting.\n *\n * These tools exist to LOAD context into the conversation — their bytes ARE\n * the value of the call. Counting them against the budget steers the model\n * away from the very tool it needs to make progress (e.g. a 78 KiB\n * `tool_search` response for a 20-tool Notion MCP server would trigger the\n * \"summarize before calling more tools\" nudge on the very turn that\n * unlocked those tools, making them effectively unusable).\n *\n * Note this list is intentionally narrower than {@link DEFAULT_PERSIST_EXCLUDE_TOOLS}:\n * `read_file` is NOT exempt — a 200 KiB file load is exactly the case the\n * budget should steer against. Same for `spawn` (a sprawling subagent\n * report should still nudge a consolidation pass).\n *\n * Exported so SDK consumers building custom profiles can extend the list\n * for host-defined context-loading tools.\n */\nexport const DEFAULT_BUDGET_EXCLUDE_TOOLS: readonly string[] = [\n 'tool_search',\n 'skills_use',\n 'skills_read',\n]\n\n/**\n * Token-saving `AgentBehavior` defaults shared by the built-in profiles.\n *\n * These flip on at the chat layer, not at the SDK core (`src/agent.ts`) —\n * external `createAgent()` consumers keep the conservative defaults.\n * Rationale per knob:\n *\n * - `compactStrategy: 'tail'` — when the persisted history's tool-output\n * bytes exceed `compactThreshold`, stub older `tool_result` blocks at\n * the wire level (session storage is untouched). Without this, a single\n * long-lived chat keeps re-sending every read/grep result every turn.\n * - `elideStaleReads: true` — after a successful `edit` / `write_file`,\n * replace earlier `read_file` results on the same path with a short\n * stub. Eliminates the pre-edit file body riding along forever.\n * - `toolDisclosure: 'lazy'` — MCP tools advertise as name+description in\n * the system prompt and load their `inputSchema` on demand via\n * `tool_search`. No-op without MCPs (gated on `lazyEntries.length > 0`\n * in `src/agent.ts:1287`); substantial savings with a 50-tool server.\n * - `toolOutputBudget: 64 KiB` + `toolOutputBudgetExcludeTools` — soft\n * per-turn cap that injects a summarize-before-continuing nudge when\n * tool output explodes. Context-loading tools (`tool_search`,\n * `skills_use`, `skills_read`) are exempt so the model isn't steered\n * away from the very call it made to load schemas / skill content.\n * - `persistThreshold: 8 KiB` + `persistExcludeTools` — tool results\n * above the threshold are written to disk and replaced inline with a\n * `<persisted-output>` stub (preview + path). `persistDir` is wired by\n * the TUI at session activation; without a dir, persistence is off.\n * The user can disable persistence entirely via\n * `Settings.persistToolResults` (sets the threshold to 0 at agent build\n * time).\n */\nconst SHARED_BEHAVIOR = {\n compactStrategy: 'tail',\n compactThreshold: 128 * 1024,\n compactKeepTurns: 4,\n elideStaleReads: true,\n toolDisclosure: 'lazy',\n toolOutputBudget: 64 * 1024,\n toolOutputBudgetExcludeTools: DEFAULT_BUDGET_EXCLUDE_TOOLS,\n persistThreshold: 8 * 1024,\n persistExcludeTools: DEFAULT_PERSIST_EXCLUDE_TOOLS,\n} as const\n\n/**\n * Build agent — the default profile. Full read/write/shell access plus\n * subagent spawning. Mirrors the legacy `basic` preset so existing TUI\n * behavior is preserved when `agents` is omitted.\n *\n * The system prompt is the cwd-less variant of {@link buildBuildSystem};\n * the TUI overrides it per-session with an env-anchored composition so\n * the prompt carries the current project root + date. SDK consumers\n * calling `createAgent({ ...BUILD_AGENT.preset })` directly get the\n * cwd-less prompt and can wrap their own env section.\n */\nexport const BUILD_AGENT: AgentProfile = {\n id: 'build',\n label: 'Build',\n description: 'full tool access — read, write, edit, shell, and spawn subagents',\n accent: 'accent',\n // `composePresets` layers `createTodoTools()` on top so the model gets\n // `todowrite` / `todoread` plus their per-run write budget. Top-level\n // runs share a persistent session slot (continuity across prompts);\n // subagent runs get their own isolated slot. Auto-clears when every\n // item is `completed`, so a fresh prompt after a successful batch\n // starts clean. See `src/chat/todos.ts` for the keying / hygiene\n // contract.\n //\n // The composition is per-tool-name deep-merge, so a downstream host\n // re-composing this profile with its own per-tool overrides gets the\n // expected last-wins-on-collision behavior.\n //\n // Plan mode deliberately omits todos — a read-only exploration session\n // has nothing to checkpoint.\n preset: composePresets(\n definePreset({\n name: 'build',\n system: buildBuildSystem(),\n behavior: { ...SHARED_BEHAVIOR },\n // `persist: true` shares the parent's session with every child agent so a\n // reloaded TUI can reconstruct the subagent tree from `session.turns`.\n tools: { ...BUILD_TOOLS, spawn: createSpawnTool({ persist: true }) },\n }),\n createTodoTools(),\n ),\n}\n\n/**\n * Plan agent — read-only exploration mode. Locked down to file reading and\n * codebase search; no shell, no edits, no spawning. The system prompt\n * frames the conversation as planning rather than execution so the model\n * outputs proposals instead of attempting mutations.\n */\nexport const PLAN_AGENT: AgentProfile = {\n id: 'plan',\n label: 'Plan',\n description: 'read-only — explore, analyze, and propose without modifying anything',\n accent: 'model',\n preset: definePreset({\n name: 'plan',\n system: buildPlanSystem(),\n behavior: { ...SHARED_BEHAVIOR },\n tools: READ_ONLY_TOOLS,\n }),\n}\n\n/**\n * Default registry shipped with `zidane/tui`. Insertion order = picker order.\n * Hosts that want only one profile can pass `{ agents: { build: BUILD_AGENT } }`,\n * or a fully custom registry of their own profiles.\n */\nexport const BUILTIN_AGENTS: AgentRegistry = {\n build: BUILD_AGENT,\n plan: PLAN_AGENT,\n}\n\n/** Id of the profile activated on first launch when nothing is persisted. */\nexport const DEFAULT_AGENT_ID = 'build'\n\n/**\n * Resolve an agent id against a registry with sensible fallbacks: the\n * requested id wins when present, then `defaultId`, then the first key.\n * Returns `null` when the registry is empty (host misconfiguration —\n * the caller should surface this rather than silently failing).\n */\nexport function resolveAgentId(\n registry: AgentRegistry,\n requestedId: string | undefined,\n defaultId: string | undefined,\n): string | null {\n if (requestedId && registry[requestedId])\n return requestedId\n if (defaultId && registry[defaultId])\n return defaultId\n const first = Object.keys(registry)[0]\n return first ?? null\n}\n\n/**\n * Wrap a legacy single `Preset` into a single-profile registry. Used by\n * `resolveConfig` when the host passed `preset` without `agents`, so older\n * call sites keep working — they get a one-entry registry whose picker is\n * a no-op (only one option).\n */\nexport function singleAgentRegistry(preset: Preset): AgentRegistry {\n return {\n default: {\n id: 'default',\n label: typeof preset.name === 'string' && preset.name.length > 0 ? preset.name : 'Default',\n description: 'host-provided preset',\n preset,\n accent: 'accent',\n },\n }\n}\n","/**\n * Git root detection — walks parents from `cwd` looking for a `.git`\n * entry, returning the absolute path of the repo root or `null` when\n * the search reaches the filesystem root.\n *\n * Why parent-walk over `git rev-parse --show-toplevel`:\n *\n * - Zero dependencies (no shell-out, no git binary requirement,\n * works even when `git` is missing from `PATH`).\n * - Pure-sync, deterministic, easy to test against tmp dirs.\n * - Equally fast in practice — most lookups are a single `existsSync`.\n *\n * Recognizes `.git` as either:\n *\n * - A directory (standard repo layout).\n * - A file containing `gitdir: …` (worktrees + submodules).\n *\n * Bare repos (no working tree) are intentionally NOT detected here —\n * the TUI's data dir contract is \"project working tree\", and a bare\n * repo has no working files to scope sessions against.\n */\n\nimport { existsSync, statSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * Walk parents of `cwd` looking for a `.git` entry. Returns the\n * absolute path of the directory containing `.git`, or `null` when no\n * git repo is found above `cwd`.\n *\n * Stops at the filesystem root (`/`) — no infinite loop on `dirname`'s\n * idempotent root case (`dirname('/')` returns `/`).\n */\nexport function findGitRoot(cwd: string = process.cwd()): string | null {\n let dir = resolve(cwd)\n // Guard against pathological inputs (junction loops, mount cycles)\n // by bounding the walk. 64 levels is well past anything sane and\n // avoids a pathological infinite loop on misconfigured volumes.\n for (let depth = 0; depth < 64; depth++) {\n if (hasGitMarker(dir))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n return null // reached filesystem root\n dir = parent\n }\n return null\n}\n\n/**\n * `.git` exists as a directory (standard repo) OR as a file containing\n * a `gitdir:` pointer (worktree / submodule). Tolerant on stat failures\n * — a permission error on a parent directory shouldn't crash the walk.\n */\nfunction hasGitMarker(dir: string): boolean {\n const candidate = resolve(dir, '.git')\n try {\n if (!existsSync(candidate))\n return false\n const stat = statSync(candidate)\n return stat.isDirectory() || stat.isFile()\n }\n catch {\n return false\n }\n}\n","/**\n * Personal instructions discovery — load user + project `AGENTS.md` /\n * `CLAUDE.md` files.\n *\n * Every file that exists is loaded — no shadowing within a scope.\n * Users routinely split doctrine across files (\"`~/.agents/AGENTS.md`\n * for cross-tool portable rules, `~/.zidane/AGENTS.md` for\n * zidane-specific tweaks\") and silently dropping a file they committed\n * is surprising. Empty or whitespace-only files are skipped.\n *\n * Two conventions are supported in each scope:\n * - `AGENTS.md` — Codex / Cursor / agentic-tools convention.\n * - `CLAUDE.md` — Claude Code convention.\n *\n * Search order (broadest scope first; same order in the rendered\n * block, so narrower scopes land closer to the conversation and\n * override broader ones in the model's eyes):\n *\n * user scope:\n * 1. `~/.agents/AGENTS.md` — agnostic\n * 2. `~/.{prefix}/AGENTS.md` — zidane (`.zidane` default)\n * 3. `~/.claude/CLAUDE.md` — Claude Code\n *\n * project scope (anchored at git root, else cwd):\n * 4. `<root>/AGENTS.md` — Codex convention\n * 5. `<root>/.agents/AGENTS.md` — agnostic\n * 6. `<root>/.{prefix}/AGENTS.md` — zidane\n * 7. `<root>/CLAUDE.md` — Claude Code\n *\n * The {@link DiscoverAgentsMdOptions.scope} option restricts which\n * scopes contribute. Defaults to `'both'` (the historical behavior).\n *\n * Each file is rendered as a section with its absolute path as the\n * header so the model can cite the source verbatim when it asks the\n * user about something that came from there.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { findGitRoot } from './project-root'\n\n/** A single loaded AGENTS.md file. */\nexport interface AgentsMdFile {\n path: string\n source: 'project' | 'user'\n contents: string\n}\n\n/** Result of {@link discoverAgentsMd}. `files` is empty when none were found. */\nexport interface AgentsMdResult {\n /** All loaded files in render order (user first, project last). */\n files: readonly AgentsMdFile[]\n /**\n * Pre-rendered prompt block ready to append to the system prompt, or\n * `null` when no files were loaded. Header + per-file sections with\n * absolute paths.\n */\n block: string | null\n}\n\n/** Sane upper bound per file so a runaway log dumped as AGENTS.md doesn't blow the prompt. */\nconst MAX_BYTES_PER_FILE = 64 * 1024\n\n/**\n * Which scope(s) of personal instructions to load.\n * - `'none'` — skip discovery entirely; no files load. Useful for\n * reproducible reviews, demos, or debugging the base\n * system prompt without any user doctrine bleeding in.\n * - `'user'` — `~/.agents/AGENTS.md`, `~/.{prefix}/AGENTS.md`, `~/.claude/CLAUDE.md`\n * - `'project'` — `<root>/AGENTS.md`, `<root>/.agents/AGENTS.md`, `<root>/.{prefix}/AGENTS.md`, `<root>/CLAUDE.md`\n * - `'both'` — union of user + project (default)\n *\n * Exposed as a user-facing setting so users can scope their instructions\n * to e.g. only the project (when running zidane against an unfamiliar\n * repo and they don't want their global preferences leaking in).\n */\nexport type AgentsMdScope = 'both' | 'none' | 'project' | 'user'\n\nexport interface DiscoverAgentsMdOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n /** Which scopes to load. Default: `'both'`. */\n scope?: AgentsMdScope\n}\n\n/**\n * Walk the candidate paths and load every `AGENTS.md` that exists.\n *\n * Project paths anchor at the git root when one exists above `cwd`,\n * else at `cwd` itself — same anchor logic as `projectUserPaths` so the\n * three discovery surfaces (skills, mcps, AGENTS.md) stay in lock-step.\n *\n * All matching files are loaded (no shadowing within a scope). Empty\n * or whitespace-only files are skipped. Render order matches the\n * search order — broader scopes first, narrower scopes last so the\n * project-specific file lands closest to the conversation.\n *\n * Tolerant on read failures: an unreadable file is skipped silently\n * (logged under `ZIDANE_DEBUG`). The loader never throws — bootstrap\n * paths can't afford to crash on a permission glitch.\n *\n * Defensive dedup: when `<git-root> === ~` (the user launched zidane\n * directly from `$HOME` and `$HOME` happens to be a git repo) the\n * project + user candidate sets would otherwise overlap. We dedup by\n * absolute path so the same file never lands in the block twice.\n */\nexport function discoverAgentsMd(opts: DiscoverAgentsMdOptions = {}): AgentsMdResult {\n const scope: AgentsMdScope = opts.scope ?? 'both'\n // Short-circuit on `'none'` BEFORE touching the filesystem — no\n // existsSync probes, no homedir() / git-root walk. Keeps the\n // explicit opt-out genuinely free.\n if (scope === 'none')\n return { files: [], block: null }\n\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n const projectRoot = findGitRoot(cwd) ?? cwd\n\n const userCandidates: { path: string, source: 'user' }[] = [\n { path: resolve(home, `.agents/AGENTS.md`), source: 'user' },\n { path: resolve(home, `.${prefix}/AGENTS.md`), source: 'user' },\n { path: resolve(home, `.claude/CLAUDE.md`), source: 'user' },\n ]\n const projectCandidates: { path: string, source: 'project' }[] = [\n { path: resolve(projectRoot, 'AGENTS.md'), source: 'project' },\n { path: resolve(projectRoot, `.agents/AGENTS.md`), source: 'project' },\n { path: resolve(projectRoot, `.${prefix}/AGENTS.md`), source: 'project' },\n { path: resolve(projectRoot, 'CLAUDE.md'), source: 'project' },\n ]\n\n const ordered: ({ path: string, source: 'project' | 'user' })[] = []\n if (scope !== 'project')\n ordered.push(...userCandidates)\n if (scope !== 'user')\n ordered.push(...projectCandidates)\n\n const files: AgentsMdFile[] = []\n const seen = new Set<string>()\n for (const c of ordered) {\n if (seen.has(c.path))\n continue\n const file = readIfPresent(c.path, c.source)\n if (file) {\n seen.add(c.path)\n files.push(file)\n }\n }\n\n if (files.length === 0)\n return { files, block: null }\n\n return { files, block: renderAgentsMdBlock(files) }\n}\n\n/**\n * Render one or more `AgentsMdFile` into a system-prompt block.\n *\n * Top header tells the model what's coming + that it's user-authored\n * (so it's treated as guidance, not as a system constraint that can't\n * be questioned). Each file becomes a sub-section with its absolute\n * path so the model can cite it back when asking follow-up questions.\n *\n * Exported separately so callers building custom profiles can inject\n * their own discovery list (e.g. an SDK consumer pulling AGENTS.md\n * from a remote source) without re-implementing the rendering.\n */\nexport function renderAgentsMdBlock(files: readonly AgentsMdFile[]): string {\n if (files.length === 0)\n return ''\n const sections = files.map((f) => {\n const scope = f.source === 'project' ? 'project' : 'user'\n return `## ${f.path} (${scope})\\n\\n${f.contents.trim()}`\n })\n return [\n '# User instructions',\n 'The following sections come from `AGENTS.md` files the user authored. Treat them as durable preferences and context that apply for this session — they extend, and where they conflict take precedence over, the doctrine above.',\n ...sections,\n ].join('\\n\\n')\n}\n\nfunction readIfPresent(path: string, source: 'project' | 'user'): AgentsMdFile | null {\n // Returns null when the file is absent, empty, or unreadable.\n // The caller (discoverAgentsMd) iterates candidates and pushes\n // every non-null hit — so multiple files per scope concatenate\n // naturally and a stub doesn't shadow a real file underneath.\n if (!existsSync(path))\n return null\n let raw: string\n try {\n raw = readFileSync(path, 'utf8')\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] agents-md: failed to read \"${path}\": ${errorMessage(err)}\\n`)\n return null\n }\n // Truncate gigantic files defensively — the model still sees the\n // path, so the user can pare the file down themselves.\n const contents = raw.length > MAX_BYTES_PER_FILE\n ? `${raw.slice(0, MAX_BYTES_PER_FILE)}\\n\\n[... truncated; ${raw.length - MAX_BYTES_PER_FILE} more bytes in source file ...]`\n : raw\n if (contents.trim().length === 0)\n return null\n return { path, source, contents }\n}\n","/**\n * PKCE helper. Inlined here (rather than imported from pi-ai) because\n * `@earendil-works/pi-ai/oauth` does not re-export it, and we don't want\n * to reach into `node_modules/.../utils/oauth/pkce.js` — that's an\n * implementation-detail path that breaks on minor upstream rearrangements.\n *\n * Web Crypto API only. Works under Bun + Node 22+ without any node:crypto\n * import (matches pi-ai's own behavior). Output is base64url per RFC 7636.\n */\n\nfunction base64UrlEncode(bytes: Uint8Array): string {\n let binary = ''\n for (const byte of bytes)\n binary += String.fromCharCode(byte)\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '')\n}\n\nexport interface PkcePair {\n /** Random verifier used in the token-exchange step. */\n verifier: string\n /** SHA-256(verifier) sent on the authorize URL. */\n challenge: string\n}\n\nexport async function generatePkce(): Promise<PkcePair> {\n const verifierBytes = new Uint8Array(32)\n crypto.getRandomValues(verifierBytes)\n const verifier = base64UrlEncode(verifierBytes)\n\n const data = new TextEncoder().encode(verifier)\n const hashBuffer = await crypto.subtle.digest('SHA-256', data)\n const challenge = base64UrlEncode(new Uint8Array(hashBuffer))\n\n return { verifier, challenge }\n}\n","/**\n * Local HTTP callback server primitive shared by the Anthropic + Codex\n * login flows. Owns three concerns:\n *\n * 1. Bind a loopback listener (configurable port + host via\n * `PI_OAUTH_CALLBACK_HOST`, matching pi-ai's semantics).\n * 2. Parse the redirect query string into `{ code, state }` or surface\n * a structured error.\n * 3. Render the result page through the caller-supplied renderer.\n *\n * Each provider clones the lifecycle wrapper (`waitForCode` / `cancelWait`\n * / `close`) so the higher-level login code reads the same on either\n * side. The only thing the server itself decides is the HTML.\n */\n\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createServer } from 'node:http'\n\nconst CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || '127.0.0.1'\n\nexport interface CallbackServerOptions {\n /** Loopback port. Must match the `redirect_uri` registered with the provider. */\n port: number\n /** Path component of the redirect URI (e.g. `/callback`, `/auth/callback`). */\n path: string\n /**\n * Expected `state` value. The server rejects any callback whose `state`\n * doesn't match, exactly. For Anthropic this is the PKCE verifier; for\n * Codex it's a random 16-byte hex string.\n */\n expectedState: string\n /** Display name passed through to {@link OAuthCallbackPageRenderer}. */\n providerName: string\n /** HTML renderer for both success + error pages. */\n renderPage: OAuthCallbackPageRenderer\n /** Success-page body shown after a clean redirect. */\n successMessage: string\n /**\n * How to react when `server.listen` itself fails (typically `EADDRINUSE`\n * when another instance is mid-flow). `\"reject\"` propagates the error to\n * the awaiting `start` caller; `\"resolveWithStub\"` returns a degraded\n * handle whose `waitForCode` immediately resolves to `null`, so the\n * caller can fall back to manual paste. Anthropic uses `\"reject\"`,\n * Codex uses `\"resolveWithStub\"` to match pi-ai's per-provider behavior.\n */\n onListenError?: 'reject' | 'resolveWithStub'\n}\n\nexport interface CallbackServerHandle {\n /** Fully-qualified redirect URI to advertise to the provider. */\n redirectUri: string\n /**\n * Resolves when the redirect lands (with `{ code, state? }`) or when\n * {@link cancelWait} is invoked (with `null`). Never throws.\n */\n waitForCode: () => Promise<{ code: string, state?: string } | null>\n /** Unblock `waitForCode` early (e.g. manual paste won the race). */\n cancelWait: () => void\n /** Close the listener. Idempotent. */\n close: () => void\n}\n\ninterface PendingResolution {\n settle: (value: { code: string, state?: string } | null) => void\n}\n\nfunction buildRequestHandler(opts: CallbackServerOptions, pending: PendingResolution) {\n return (req: IncomingMessage, res: ServerResponse) => {\n try {\n const url = new URL(req.url || '', 'http://localhost')\n\n if (url.pathname !== opts.path) {\n respondError(res, 404, opts, 'Callback route not found.')\n return\n }\n\n const error = url.searchParams.get('error')\n if (error) {\n respondError(res, 400, opts, `${opts.providerName} authentication did not complete.`, `Error: ${error}`)\n return\n }\n\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (!code || !state) {\n respondError(res, 400, opts, 'Missing code or state parameter.')\n return\n }\n\n if (state !== opts.expectedState) {\n respondError(res, 400, opts, 'State mismatch.')\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(opts.renderPage({\n kind: 'success',\n provider: opts.providerName,\n message: opts.successMessage,\n }))\n pending.settle({ code, state })\n }\n catch {\n // We intentionally swallow the actual error here: surfacing a stack\n // to the user's browser would leak internals, and the login flow\n // is about to fall back to manual paste anyway.\n respondError(res, 500, opts, 'Internal error while processing the callback.')\n }\n }\n}\n\nfunction respondError(\n res: ServerResponse,\n status: number,\n opts: CallbackServerOptions,\n message: string,\n details?: string,\n) {\n res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(opts.renderPage({\n kind: 'error',\n provider: opts.providerName,\n message,\n details,\n }))\n}\n\nfunction buildStubHandle(redirectUri: string, server: Server | null): CallbackServerHandle {\n return {\n redirectUri,\n waitForCode: async () => null,\n cancelWait: () => {},\n close: () => {\n try { server?.close() }\n catch { /* already closed */ }\n },\n }\n}\n\n/**\n * Start the loopback HTTP callback server. The returned handle is the\n * caller's contract — they `await waitForCode()`, race it against manual\n * paste, then `close()` in a `finally`.\n */\nexport async function startCallbackServer(opts: CallbackServerOptions): Promise<CallbackServerHandle> {\n const onListenError = opts.onListenError ?? 'reject'\n const redirectUri = `http://localhost:${opts.port}${opts.path}`\n\n return new Promise<CallbackServerHandle>((resolve, reject) => {\n let settled = false\n const pending: PendingResolution = {\n settle: () => {},\n }\n const waitPromise = new Promise<{ code: string, state?: string } | null>((resolveWait) => {\n pending.settle = (value) => {\n if (settled)\n return\n settled = true\n resolveWait(value)\n }\n })\n\n const server = createServer(buildRequestHandler(opts, pending))\n\n server.on('error', (err) => {\n if (onListenError === 'resolveWithStub') {\n pending.settle(null)\n resolve(buildStubHandle(redirectUri, server))\n return\n }\n reject(err)\n })\n\n server.listen(opts.port, CALLBACK_HOST, () => {\n resolve({\n redirectUri,\n waitForCode: () => waitPromise,\n cancelWait: () => pending.settle(null),\n close: () => {\n try { server.close() }\n catch { /* already closed */ }\n },\n })\n })\n })\n}\n","/**\n * Anthropic OAuth flow with a customisable callback page.\n *\n * Mirrors `loginAnthropic` from `@earendil-works/pi-ai/oauth` 1:1 except:\n *\n * 1. The HTTP callback server's response HTML routes through\n * {@link OAuthCallbackPageRenderer} instead of pi-ai's baked-in page.\n * 2. The lifecycle is delegated to {@link startCallbackServer} so the\n * Codex sibling and any future loopback provider share one HTTP impl.\n *\n * Refresh + `getApiKey` delegate straight to pi-ai's exports — those don't\n * touch HTML and there's no upside to forking them.\n *\n * Endpoints / client id / port / scopes match pi-ai `0.76.0`. Bump in\n * lockstep on the next `@earendil-works/pi-ai` upgrade, or delete this\n * file once pi-ai exposes a `renderCallbackPage` callback upstream.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { refreshAnthropicToken } from '@earendil-works/pi-ai/oauth'\nimport { generatePkce } from './pkce'\nimport { startCallbackServer } from './server'\n\n// Pulled verbatim from pi-ai 0.76.0 — `dist/utils/oauth/anthropic.js`.\nconst CLIENT_ID = atob('OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl')\nconst AUTHORIZE_URL = 'https://claude.ai/oauth/authorize'\nconst TOKEN_URL = 'https://platform.claude.com/v1/oauth/token'\nconst CALLBACK_PORT = 53692\nconst CALLBACK_PATH = '/callback'\nconst SCOPES = 'org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload'\nconst PROVIDER_NAME = 'Anthropic'\n\n/**\n * Parse what the user pasted into the manual-code prompt. Accepts:\n * - the bare authorization code\n * - the full `redirect_uri?code=...&state=...` URL\n * - the `code#state` shorthand Anthropic surfaces on the page\n * - a raw query string with `code=...&state=...`\n *\n * Matches pi-ai's parse table so an existing user's muscle memory still works.\n */\nfunction parseAuthorizationInput(input: string): { code?: string, state?: string } {\n const value = input.trim()\n if (!value)\n return {}\n\n try {\n const url = new URL(value)\n return {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n }\n }\n catch {\n // fall through to non-URL parsing below\n }\n\n if (value.includes('#')) {\n const [code, state] = value.split('#', 2)\n return { code, state }\n }\n if (value.includes('code=')) {\n const params = new URLSearchParams(value)\n return {\n code: params.get('code') ?? undefined,\n state: params.get('state') ?? undefined,\n }\n }\n return { code: value }\n}\n\nasync function postJson(url: string, body: Record<string, unknown>): Promise<string> {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(30_000),\n })\n const responseBody = await response.text()\n if (!response.ok)\n throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`)\n return responseBody\n}\n\nasync function exchangeAuthorizationCode(\n code: string,\n state: string,\n verifier: string,\n redirectUri: string,\n): Promise<OAuthCredentials> {\n const responseBody = await postJson(TOKEN_URL, {\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n state,\n redirect_uri: redirectUri,\n code_verifier: verifier,\n })\n\n const tokenData = JSON.parse(responseBody) as {\n refresh_token: string\n access_token: string\n expires_in: number\n }\n\n return {\n refresh: tokenData.refresh_token,\n access: tokenData.access_token,\n // -5min so a refresh fires before the upstream expiry; matches pi-ai.\n expires: Date.now() + tokenData.expires_in * 1000 - 5 * 60 * 1000,\n }\n}\n\nexport interface LoginAnthropicWithPageOptions extends Pick<\n OAuthLoginCallbacks,\n 'onAuth' | 'onPrompt' | 'onProgress' | 'onManualCodeInput'\n> {\n renderPage: OAuthCallbackPageRenderer\n}\n\nexport async function loginAnthropicWithCustomPage(\n options: LoginAnthropicWithPageOptions,\n): Promise<OAuthCredentials> {\n const { verifier, challenge } = await generatePkce()\n const server = await startCallbackServer({\n port: CALLBACK_PORT,\n path: CALLBACK_PATH,\n expectedState: verifier,\n providerName: PROVIDER_NAME,\n renderPage: options.renderPage,\n successMessage: 'Anthropic authentication completed. You can close this window.',\n onListenError: 'reject',\n })\n\n let code: string | undefined\n let state: string | undefined\n\n try {\n const authParams = new URLSearchParams({\n code: 'true',\n client_id: CLIENT_ID,\n response_type: 'code',\n redirect_uri: server.redirectUri,\n scope: SCOPES,\n code_challenge: challenge,\n code_challenge_method: 'S256',\n state: verifier,\n })\n\n options.onAuth({\n url: `${AUTHORIZE_URL}?${authParams.toString()}`,\n instructions: 'Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.',\n })\n\n if (options.onManualCodeInput) {\n let manualInput: string | undefined\n let manualError: Error | undefined\n const manualPromise = options\n .onManualCodeInput()\n .then((input) => {\n manualInput = input\n server.cancelWait()\n })\n .catch((err) => {\n manualError = err instanceof Error ? err : new Error(String(err))\n server.cancelWait()\n })\n\n const result = await server.waitForCode()\n if (manualError)\n throw manualError\n\n if (result?.code) {\n code = result.code\n state = result.state\n }\n else if (manualInput) {\n const parsed = parseAuthorizationInput(manualInput)\n if (parsed.state && parsed.state !== verifier)\n throw new Error('OAuth state mismatch')\n code = parsed.code\n state = parsed.state ?? verifier\n }\n\n if (!code) {\n await manualPromise\n if (manualError)\n throw manualError\n if (manualInput) {\n const parsed = parseAuthorizationInput(manualInput)\n if (parsed.state && parsed.state !== verifier)\n throw new Error('OAuth state mismatch')\n code = parsed.code\n state = parsed.state ?? verifier\n }\n }\n }\n else {\n const result = await server.waitForCode()\n if (result?.code) {\n code = result.code\n state = result.state\n }\n }\n\n if (!code) {\n const input = await options.onPrompt({\n message: 'Paste the authorization code or full redirect URL:',\n placeholder: server.redirectUri,\n })\n const parsed = parseAuthorizationInput(input)\n if (parsed.state && parsed.state !== verifier)\n throw new Error('OAuth state mismatch')\n code = parsed.code\n state = parsed.state ?? verifier\n }\n\n if (!code)\n throw new Error('Missing authorization code')\n if (!state)\n throw new Error('Missing OAuth state')\n\n options.onProgress?.('Exchanging authorization code for tokens...')\n return await exchangeAuthorizationCode(code, state, verifier, server.redirectUri)\n }\n finally {\n server.close()\n }\n}\n\n/**\n * Build an `OAuthProviderInterface` that behaves identically to pi-ai's\n * `anthropicOAuthProvider` except for the callback page HTML. Drop this\n * onto `ProviderDescriptor.oauthProvider` to override.\n */\nexport function createAnthropicOAuthProviderWithCustomPage(\n renderPage: OAuthCallbackPageRenderer,\n): OAuthProviderInterface {\n return {\n id: 'anthropic',\n name: 'Anthropic (Claude Pro/Max)',\n usesCallbackServer: true,\n async login(callbacks: OAuthLoginCallbacks) {\n return loginAnthropicWithCustomPage({\n renderPage,\n onAuth: callbacks.onAuth,\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n onManualCodeInput: callbacks.onManualCodeInput,\n })\n },\n async refreshToken(credentials: OAuthCredentials) {\n return refreshAnthropicToken(credentials.refresh)\n },\n getApiKey(credentials: OAuthCredentials) {\n return credentials.access\n },\n }\n}\n","/**\n * OpenAI Codex (ChatGPT Plus/Pro) OAuth flow with a customisable callback\n * page. 1:1 mirror of pi-ai `0.76.0`'s `loginOpenAICodex` except the HTTP\n * callback server routes through {@link OAuthCallbackPageRenderer}.\n *\n * Refresh delegates to pi-ai's exported `refreshOpenAICodexToken` —\n * Codex's refresh path involves JWT decoding for `accountId`, and we don't\n * want to duplicate it. The HTML override is purely about the landing page.\n */\n\nimport type {\n OAuthCredentials,\n OAuthLoginCallbacks,\n OAuthProviderInterface,\n} from '@earendil-works/pi-ai/oauth'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { randomBytes } from 'node:crypto'\nimport { refreshOpenAICodexToken } from '@earendil-works/pi-ai/oauth'\nimport { generatePkce } from './pkce'\nimport { startCallbackServer } from './server'\n\n// Pulled verbatim from pi-ai 0.76.0 — `dist/utils/oauth/openai-codex.js`.\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann'\nconst AUTHORIZE_URL = 'https://auth.openai.com/oauth/authorize'\nconst TOKEN_URL = 'https://auth.openai.com/oauth/token'\nconst CALLBACK_PORT = 1455\nconst CALLBACK_PATH = '/auth/callback'\nconst SCOPE = 'openid profile email offline_access'\nconst JWT_CLAIM_PATH = 'https://api.openai.com/auth'\nconst PROVIDER_NAME = 'OpenAI Codex'\n\nfunction createState(): string {\n return randomBytes(16).toString('hex')\n}\n\nfunction parseAuthorizationInput(input: string): { code?: string, state?: string } {\n const value = input.trim()\n if (!value)\n return {}\n\n try {\n const url = new URL(value)\n return {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n }\n }\n catch {\n // fall through\n }\n\n if (value.includes('#')) {\n const [code, state] = value.split('#', 2)\n return { code, state }\n }\n if (value.includes('code=')) {\n const params = new URLSearchParams(value)\n return {\n code: params.get('code') ?? undefined,\n state: params.get('state') ?? undefined,\n }\n }\n return { code: value }\n}\n\ninterface JwtPayload {\n [key: string]: unknown\n [JWT_CLAIM_PATH]?: { chatgpt_account_id?: string }\n}\n\nfunction decodeJwt(token: string): JwtPayload | null {\n try {\n const parts = token.split('.')\n if (parts.length !== 3)\n return null\n const payload = parts[1] ?? ''\n const decoded = atob(payload)\n return JSON.parse(decoded) as JwtPayload\n }\n catch {\n return null\n }\n}\n\nfunction getAccountId(accessToken: string): string | null {\n const payload = decodeJwt(accessToken)\n const claim = payload?.[JWT_CLAIM_PATH] as { chatgpt_account_id?: string } | undefined\n const accountId = claim?.chatgpt_account_id\n return typeof accountId === 'string' && accountId.length > 0 ? accountId : null\n}\n\ninterface TokenExchangeSuccess {\n type: 'success'\n access: string\n refresh: string\n expires: number\n}\n\ninterface TokenExchangeFailure {\n type: 'failed'\n message: string\n}\n\nasync function exchangeAuthorizationCode(\n code: string,\n verifier: string,\n redirectUri: string,\n): Promise<TokenExchangeSuccess | TokenExchangeFailure> {\n const response = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n }),\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => '')\n return {\n type: 'failed',\n message: `OpenAI Codex token exchange failed (${response.status}): ${text || response.statusText}`,\n }\n }\n\n const json = await response.json() as {\n access_token?: string\n refresh_token?: string\n expires_in?: number\n }\n if (!json.access_token || !json.refresh_token || typeof json.expires_in !== 'number') {\n return {\n type: 'failed',\n message: `OpenAI Codex token exchange response missing fields: ${JSON.stringify(json)}`,\n }\n }\n return {\n type: 'success',\n access: json.access_token,\n refresh: json.refresh_token,\n expires: Date.now() + json.expires_in * 1000,\n }\n}\n\nexport interface LoginOpenAICodexWithPageOptions extends Pick<\n OAuthLoginCallbacks,\n 'onAuth' | 'onPrompt' | 'onProgress' | 'onManualCodeInput'\n> {\n renderPage: OAuthCallbackPageRenderer\n /** OAuth `originator` parameter. Defaults to `\"pi\"` to stay observable upstream. */\n originator?: string\n}\n\nexport async function loginOpenAICodexWithCustomPage(\n options: LoginOpenAICodexWithPageOptions,\n): Promise<OAuthCredentials> {\n const { verifier, challenge } = await generatePkce()\n const state = createState()\n const originator = options.originator ?? 'pi'\n\n const server = await startCallbackServer({\n port: CALLBACK_PORT,\n path: CALLBACK_PATH,\n expectedState: state,\n providerName: PROVIDER_NAME,\n renderPage: options.renderPage,\n successMessage: 'OpenAI authentication completed. You can close this window.',\n // Matches pi-ai: Codex falls back to manual paste on port collision.\n onListenError: 'resolveWithStub',\n })\n\n const authUrl = new URL(AUTHORIZE_URL)\n authUrl.searchParams.set('response_type', 'code')\n authUrl.searchParams.set('client_id', CLIENT_ID)\n authUrl.searchParams.set('redirect_uri', server.redirectUri)\n authUrl.searchParams.set('scope', SCOPE)\n authUrl.searchParams.set('code_challenge', challenge)\n authUrl.searchParams.set('code_challenge_method', 'S256')\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('id_token_add_organizations', 'true')\n authUrl.searchParams.set('codex_cli_simplified_flow', 'true')\n authUrl.searchParams.set('originator', originator)\n\n options.onAuth({\n url: authUrl.toString(),\n instructions: 'A browser window should open. Complete login to finish.',\n })\n\n let code: string | undefined\n\n try {\n if (options.onManualCodeInput) {\n let manualCode: string | undefined\n let manualError: Error | undefined\n const manualPromise = options\n .onManualCodeInput()\n .then((input) => {\n manualCode = input\n server.cancelWait()\n })\n .catch((err) => {\n manualError = err instanceof Error ? err : new Error(String(err))\n server.cancelWait()\n })\n\n const result = await server.waitForCode()\n if (manualError)\n throw manualError\n\n if (result?.code) {\n code = result.code\n }\n else if (manualCode) {\n const parsed = parseAuthorizationInput(manualCode)\n if (parsed.state && parsed.state !== state)\n throw new Error('State mismatch')\n code = parsed.code\n }\n\n if (!code) {\n await manualPromise\n if (manualError)\n throw manualError\n if (manualCode) {\n const parsed = parseAuthorizationInput(manualCode)\n if (parsed.state && parsed.state !== state)\n throw new Error('State mismatch')\n code = parsed.code\n }\n }\n }\n else {\n const result = await server.waitForCode()\n if (result?.code)\n code = result.code\n }\n\n if (!code) {\n const input = await options.onPrompt({\n message: 'Paste the authorization code (or full redirect URL):',\n })\n const parsed = parseAuthorizationInput(input)\n if (parsed.state && parsed.state !== state)\n throw new Error('State mismatch')\n code = parsed.code\n }\n\n if (!code)\n throw new Error('Missing authorization code')\n\n const tokenResult = await exchangeAuthorizationCode(code, verifier, server.redirectUri)\n if (tokenResult.type !== 'success')\n throw new Error(tokenResult.message)\n\n const accountId = getAccountId(tokenResult.access)\n if (!accountId)\n throw new Error('Failed to extract accountId from token')\n\n return {\n access: tokenResult.access,\n refresh: tokenResult.refresh,\n expires: tokenResult.expires,\n accountId,\n }\n }\n finally {\n server.close()\n }\n}\n\n/**\n * Build an `OAuthProviderInterface` that behaves identically to pi-ai's\n * `openaiCodexOAuthProvider` except for the callback page HTML.\n */\nexport function createOpenAICodexOAuthProviderWithCustomPage(\n renderPage: OAuthCallbackPageRenderer,\n): OAuthProviderInterface {\n return {\n id: 'openai-codex',\n name: 'ChatGPT Plus/Pro (Codex Subscription)',\n usesCallbackServer: true,\n async login(callbacks: OAuthLoginCallbacks) {\n return loginOpenAICodexWithCustomPage({\n renderPage,\n onAuth: callbacks.onAuth,\n onPrompt: callbacks.onPrompt,\n onProgress: callbacks.onProgress,\n onManualCodeInput: callbacks.onManualCodeInput,\n })\n },\n async refreshToken(credentials: OAuthCredentials) {\n return refreshOpenAICodexToken(credentials.refresh)\n },\n getApiKey(credentials: OAuthCredentials) {\n return credentials.access\n },\n }\n}\n","/**\n * HTML rendered by the local OAuth callback server when the browser hits\n * `redirect_uri`. This is the page the user sees the moment authentication\n * finishes — by default pi-ai bakes in its own dark page; this module lets\n * a host swap it for something branded without forking the package.\n *\n * Renderer is a single pure function — `(page) => string` — so a host can\n * inline a literal template, pull from a theme registry, or proxy a static\n * asset built at compile time. Anything that returns a `string`.\n */\n\n/**\n * Outcome rendered on the callback page. `kind` drives status code on the\n * underlying HTTP response (`200` for success, `4xx` for error); the rest\n * is content. `provider` is the human-readable name of the OAuth provider\n * (e.g. `\"Anthropic\"`, `\"OpenAI Codex\"`) so a renderer can swap copy or\n * logos per provider.\n */\nexport interface OAuthCallbackPage {\n kind: 'success' | 'error'\n /** Headline message — usually one sentence. Safe to include user-visible details. */\n message: string\n /** Optional secondary details (e.g. provider error code). Rendered in mono. */\n details?: string\n /** Provider display name passed through from the login function. */\n provider: string\n}\n\nexport type OAuthCallbackPageRenderer = (page: OAuthCallbackPage) => string\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll('\\'', '&#39;')\n}\n\n/**\n * Default zidane-themed page. Visually neutral but distinguishable from\n * pi-ai's stock page — dark background, mono headings, no logo (host can\n * pass a custom renderer to add one).\n */\nexport const renderDefaultCallbackPage: OAuthCallbackPageRenderer = (page) => {\n const title = page.kind === 'success'\n ? `Signed in to ${page.provider}`\n : `Could not sign in to ${page.provider}`\n const heading = escapeHtml(title)\n const message = escapeHtml(page.message)\n const details = page.details ? escapeHtml(page.details) : undefined\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${heading}</title>\n <style>\n :root {\n --text: #f4f4f5;\n --text-dim: #a1a1aa;\n --accent: ${page.kind === 'success' ? '#22d3ee' : '#f87171'};\n --page-bg: #0a0a0a;\n --panel-bg: #131316;\n --border: #27272a;\n --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n }\n * { box-sizing: border-box; }\n html { color-scheme: dark; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: var(--page-bg);\n color: var(--text);\n font-family: var(--font-sans);\n }\n main {\n width: 100%;\n max-width: 520px;\n padding: 32px;\n background: var(--panel-bg);\n border: 1px solid var(--border);\n border-radius: 12px;\n }\n .label {\n font-family: var(--font-mono);\n font-size: 12px;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--accent);\n margin-bottom: 12px;\n }\n h1 {\n margin: 0 0 12px;\n font-size: 22px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--text);\n }\n p {\n margin: 0;\n line-height: 1.6;\n color: var(--text-dim);\n font-size: 14px;\n }\n .details {\n margin-top: 18px;\n padding: 12px 14px;\n background: var(--page-bg);\n border: 1px solid var(--border);\n border-radius: 8px;\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text-dim);\n white-space: pre-wrap;\n word-break: break-word;\n }\n </style>\n</head>\n<body>\n <main>\n <div class=\"label\">zidane · OAuth · ${escapeHtml(page.provider)}</div>\n <h1>${heading}</h1>\n <p>${message}</p>\n ${details ? `<div class=\"details\">${details}</div>` : ''}\n </main>\n</body>\n</html>`\n}\n\n/**\n * Tiny helper kept private to the module so renderer authors can reuse\n * the same escaping rules as the default theme. Re-exported in case a\n * host wants to embed user-supplied messages safely.\n */\nexport const escapeHtmlForCallbackPage: (value: string) => string = escapeHtml\n","/**\n * Custom OAuth callback page — drop-in replacements for pi-ai's built-in\n * Anthropic + OpenAI Codex providers that route the post-redirect HTML\n * through your own renderer.\n *\n * Why this module exists\n * ----------------------\n * `@earendil-works/pi-ai` (as of `0.76.0`) bakes its callback page into\n * `loginAnthropic` / `loginOpenAICodex` with no override. The upstream\n * fix is a tiny `renderCallbackPage` hook on `OAuthLoginCallbacks`; until\n * that lands this module forks the two flows in one self-contained place\n * — everything else (token refresh, `getApiKey`, env handling) delegates\n * back to pi-ai unchanged.\n *\n * How to use\n * ----------\n *\n * ```ts\n * import { createCustomCallbackOAuthProviders, renderDefaultCallbackPage } from './chat/oauth-page'\n * import { BUILTIN_PROVIDERS } from './chat/providers'\n *\n * const { anthropic, openaiCodex } = createCustomCallbackOAuthProviders(renderDefaultCallbackPage)\n *\n * const providers = {\n * ...BUILTIN_PROVIDERS,\n * anthropic: { ...BUILTIN_PROVIDERS.anthropic, oauthProvider: anthropic },\n * openai: { ...BUILTIN_PROVIDERS.openai, oauthProvider: openaiCodex },\n * }\n * ```\n *\n * Or override per provider with `createAnthropicOAuthProviderWithCustomPage`\n * / `createOpenAICodexOAuthProviderWithCustomPage` directly.\n *\n * Deleting this module\n * --------------------\n * When pi-ai exposes `renderCallbackPage` on `OAuthLoginCallbacks`:\n *\n * 1. Delete this folder.\n * 2. Surface the upstream callback through `OAuthFlowOptions` in\n * `src/chat/oauth.ts` (one optional pass-through field).\n * 3. Re-point any descriptors that imported from here to pi-ai's\n * `anthropicOAuthProvider` / `openaiCodexOAuthProvider` directly.\n *\n * Pinned to pi-ai `0.76.0`. Re-verify endpoints + ports on every pi-ai\n * bump (auth URLs, client IDs, callback ports are duplicated verbatim\n * here; see `anthropic.ts` and `openai-codex.ts` for the source-of-truth\n * pointers).\n */\n\nimport type { OAuthProviderInterface } from '@earendil-works/pi-ai/oauth'\nimport type { OAuthCallbackPageRenderer } from './render'\nimport { createAnthropicOAuthProviderWithCustomPage } from './anthropic'\nimport { createOpenAICodexOAuthProviderWithCustomPage } from './openai-codex'\n\nexport type {\n LoginAnthropicWithPageOptions,\n} from './anthropic'\nexport {\n createAnthropicOAuthProviderWithCustomPage,\n loginAnthropicWithCustomPage,\n} from './anthropic'\nexport type {\n LoginOpenAICodexWithPageOptions,\n} from './openai-codex'\nexport {\n createOpenAICodexOAuthProviderWithCustomPage,\n loginOpenAICodexWithCustomPage,\n} from './openai-codex'\nexport type {\n OAuthCallbackPage,\n OAuthCallbackPageRenderer,\n} from './render'\nexport {\n escapeHtmlForCallbackPage,\n renderDefaultCallbackPage,\n} from './render'\n\nexport interface CustomCallbackOAuthProviders {\n anthropic: OAuthProviderInterface\n openaiCodex: OAuthProviderInterface\n}\n\n/**\n * Bundle helper — returns both Anthropic + OpenAI Codex providers wired\n * with the same renderer. Hosts that want different renderers per provider\n * should call the individual `create…WithCustomPage` factories instead.\n */\nexport function createCustomCallbackOAuthProviders(\n renderPage: OAuthCallbackPageRenderer,\n): CustomCallbackOAuthProviders {\n return {\n anthropic: createAnthropicOAuthProviderWithCustomPage(renderPage),\n openaiCodex: createOpenAICodexOAuthProviderWithCustomPage(renderPage),\n }\n}\n","/**\n * Provider registry — the customization seam for the TUI.\n *\n * A {@link ProviderDescriptor} carries everything the TUI needs to know about\n * a provider in one place: how to instantiate it, what to show in the UI,\n * how to detect credentials, how to OAuth (if applicable), and where to look\n * up its models. Hosts can ship their own descriptors instead of the built-in\n * four — see {@link BUILTIN_PROVIDERS}.\n */\n\nimport type { OAuthProviderInterface } from '@earendil-works/pi-ai/oauth'\nimport type { Provider } from '../providers'\nimport { getModel, getModels } from '@earendil-works/pi-ai'\nimport { anthropic, cerebras, openai, openrouter } from '../providers'\nimport { createCustomCallbackOAuthProviders, renderDefaultCallbackPage } from './oauth-page'\n\n/**\n * pi-ai's stock Anthropic + Codex OAuth providers bake their own callback\n * HTML; we route both through `renderDefaultCallbackPage` so the post-redirect\n * page matches zidane's theme. The override lives in `src/chat/oauth-page/`\n * — see that folder's `index.ts` for the deletion path once pi-ai exposes\n * a `renderCallbackPage` hook upstream.\n */\nconst { anthropic: anthropicOAuthProvider, openaiCodex: openaiCodexOAuthProvider }\n = createCustomCallbackOAuthProviders(renderDefaultCallbackPage)\n\n/**\n * Structural model metadata — compatible with pi-ai's `Model` interface but\n * not coupled to it, so hosts can pass either pi-ai-shaped objects or a\n * custom registry of their own.\n *\n * Deliberately a structural duplicate of pi-ai's `Model` rather than a\n * re-export: keeps the public API stable when pi-ai bumps shape, and lets\n * hosts implement their own registry without depending on pi-ai's types.\n *\n * Lives here (rather than `./config`) because `ProviderDescriptor.models`\n * needs it — pushing it to `config.tsx` created an import cycle.\n */\nexport interface ModelInfo {\n id: string\n name?: string\n contextWindow: number\n maxTokens?: number\n reasoning?: boolean\n input?: readonly ('text' | 'image')[]\n cost?: { input: number, output: number, cacheRead?: number, cacheWrite?: number }\n provider?: string\n}\n\nexport interface ProviderDescriptor {\n /**\n * Unique identifier. Used as the registry key, persisted in `state.json`\n * as the resumed provider, and (by default) as the credential-file key.\n */\n key: string\n /** Display name shown in the TUI's provider picker and labels. */\n label: string\n /**\n * Factory that builds a fresh `Provider` instance. Called on every session\n * activation. Some factories (e.g. zidane's built-in `anthropic`) eagerly\n * resolve credentials at construction time and throw when none are\n * configured — set {@link defaultModel} on the descriptor to avoid the TUI\n * calling the factory before the user has picked + authed a provider.\n */\n factory: () => Provider\n /**\n * Default model id to seed the picker with. When omitted, the TUI falls\n * back to `descriptor.factory().meta.defaultModel`, which constructs the\n * provider — fine for lazy-credential factories, but breaks for factories\n * that throw on missing credentials before the wizard runs. Setting this\n * eagerly lets the TUI render the auth wizard without ever instantiating\n * the provider.\n */\n defaultModel?: string\n /**\n * Env var checked when detecting whether the user has an API key\n * configured for this provider. Optional — set to `undefined` when the\n * provider only supports OAuth (or only credentials via a custom path).\n */\n envKey?: string\n /**\n * Key under which credentials live in `credentials.json`. Defaults to\n * `key`. The only built-in that overrides this is OpenAI Codex\n * (`openai` → `openai-codex`) to stay compatible with the harness\n * provider's existing lookup.\n */\n credentialFileKey?: string\n /** Placeholder shown in the wizard's API-key input. */\n apiKeyPlaceholder?: string\n /**\n * pi-ai (or compat) OAuth provider. When present, the wizard offers an\n * OAuth option in addition to API key.\n */\n oauthProvider?: OAuthProviderInterface\n /**\n * Optional copy appended to the wizard's \"OAuth\" method description.\n * Use to communicate why a user might pick OAuth (e.g. subscription tier,\n * org-wide SSO). Shown after `browser-based sign-in`. Skip the leading\n * space — the wizard adds it. Example: `'Claude Pro/Max subscription'`.\n */\n oauthHint?: string\n /**\n * pi-ai provider id used to look up models in pi-ai's built-in registry.\n * Defaults to `key`. Only Codex differs (`openai` → `openai-codex`).\n */\n piProviderId?: string\n /**\n * Override the model list returned for this provider's picker. When set,\n * skips pi-ai's registry entirely. Useful for hosts maintaining their\n * own model catalogue or for custom providers pi-ai doesn't know about.\n */\n models?: readonly ModelInfo[]\n}\n\n/** Convenience accessor — returns `credentialFileKey ?? key`. */\nexport function credKeyOf(desc: ProviderDescriptor): string {\n return desc.credentialFileKey ?? desc.key\n}\n\n/** Convenience accessor — returns `piProviderId ?? key`. */\nexport function piIdOf(desc: ProviderDescriptor): string {\n return desc.piProviderId ?? desc.key\n}\n\n// ---------------------------------------------------------------------------\n// Built-in descriptors\n// ---------------------------------------------------------------------------\n\nexport const anthropicDescriptor: ProviderDescriptor = {\n key: 'anthropic',\n label: 'Anthropic',\n factory: anthropic,\n defaultModel: 'claude-opus-4-7',\n envKey: 'ANTHROPIC_API_KEY',\n apiKeyPlaceholder: 'sk-ant-…',\n oauthProvider: anthropicOAuthProvider,\n oauthHint: 'Claude Pro/Max subscription',\n}\n\nexport const openaiDescriptor: ProviderDescriptor = {\n key: 'openai',\n label: 'OpenAI Codex',\n factory: openai,\n defaultModel: 'gpt-5.4',\n envKey: 'OPENAI_CODEX_API_KEY',\n credentialFileKey: 'openai-codex',\n piProviderId: 'openai-codex',\n apiKeyPlaceholder: 'sk-… or eyJ… (Codex)',\n oauthProvider: openaiCodexOAuthProvider,\n}\n\nexport const openrouterDescriptor: ProviderDescriptor = {\n key: 'openrouter',\n label: 'OpenRouter',\n factory: openrouter,\n defaultModel: 'anthropic/claude-sonnet-4-6',\n envKey: 'OPENROUTER_API_KEY',\n apiKeyPlaceholder: 'sk-or-…',\n}\n\nexport const cerebrasDescriptor: ProviderDescriptor = {\n key: 'cerebras',\n label: 'Cerebras',\n factory: cerebras,\n defaultModel: 'zai-glm-4.7',\n envKey: 'CEREBRAS_API_KEY',\n apiKeyPlaceholder: 'csk-…',\n}\n\n/**\n * Default provider registry. Passed verbatim when `runTui` is invoked without\n * an explicit `providers` option. Hosts that want to override per-provider\n * metadata can spread this and replace specific entries:\n *\n * ```ts\n * runTui({ providers: { ...BUILTIN_PROVIDERS, anthropic: myOwnAnthropicDescriptor } })\n * ```\n */\nexport const BUILTIN_PROVIDERS: Readonly<Record<string, ProviderDescriptor>> = {\n anthropic: anthropicDescriptor,\n openai: openaiDescriptor,\n openrouter: openrouterDescriptor,\n cerebras: cerebrasDescriptor,\n}\n\n// ---------------------------------------------------------------------------\n// Model registry helper\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the model list for a given provider. Honors `descriptor.models`\n * when set; otherwise queries pi-ai via `descriptor.piProviderId`. Returns\n * `[]` for descriptors with no known mapping (custom providers without a\n * model list) — callers should hide the model picker in that case.\n */\nexport function modelsForDescriptor(descriptor: ProviderDescriptor): readonly ModelInfo[] {\n if (descriptor.models)\n return descriptor.models\n try {\n return getModels(piIdOf(descriptor) as never) as readonly ModelInfo[]\n }\n catch {\n return []\n }\n}\n\n/**\n * Resolve a single model's metadata via the descriptor's model source.\n * Mirrors {@link modelsForDescriptor} routing: descriptor's own list wins,\n * pi-ai's registry is the fallback. Returns `null` when the model isn't\n * known.\n */\nexport function getModelInfo(descriptor: ProviderDescriptor, modelId: string): ModelInfo | null {\n if (descriptor.models)\n return descriptor.models.find(m => m.id === modelId) ?? null\n try {\n return (getModel(piIdOf(descriptor) as never, modelId as never) as ModelInfo | undefined) ?? null\n }\n catch {\n return null\n }\n}\n\n/**\n * Look up the model's max context window via the descriptor's model source.\n * Returns `null` when the model isn't known (custom slugs, providers without\n * a registry); callers should hide the context indicator in that case.\n */\nexport function getContextWindow(descriptor: ProviderDescriptor, modelId: string): number | null {\n return getModelInfo(descriptor, modelId)?.contextWindow ?? null\n}\n\n/**\n * Reserved output budget subtracted from the raw context window when\n * computing the effective ceiling for compaction / warning thresholds.\n *\n * Aligned with Claude Code's `COMPACT_MAX_OUTPUT_TOKENS = 20_000`\n * (`utils/context.ts:12`) — covers p99.99 of summary + response output.\n * A turn that consumed N input tokens leaves `effectiveWindow - N`\n * headroom for the next prompt's response; once headroom shrinks\n * below the threshold, auto-compaction fires.\n */\nexport const OUTPUT_RESERVE_TOKENS = 20_000\n\n/**\n * Effective context window — what the next user turn can actually pack\n * before the provider reserves space for the assistant's reply. Equals\n * `rawWindow - OUTPUT_RESERVE_TOKENS`, clamped to `>= 1` so a tiny\n * (or absurd) window doesn't yield zero / negative thresholds.\n *\n * Used by both the footer's context indicator and the auto-compact\n * trigger so the two surfaces agree on \"how full are we, really\".\n * Pass `null` through unchanged so callers can pipe `getContextWindow`\n * directly without an intermediate check.\n */\nexport function effectiveContextWindow(rawWindow: number | null): number | null {\n if (rawWindow === null)\n return null\n return Math.max(1, rawWindow - OUTPUT_RESERVE_TOKENS)\n}\n\n/**\n * Whether the given model exposes a reasoning / extended-thinking knob.\n * Drives the TUI's effort picker visibility — the `ctrl+n` shortcut and\n * the bottom-bar `effortName` segment only surface when this is `true`.\n * Returns `false` for unknown models (no registry entry → no advertised\n * capability).\n */\nexport function modelSupportsReasoning(descriptor: ProviderDescriptor, modelId: string): boolean {\n return getModelInfo(descriptor, modelId)?.reasoning === true\n}\n","/**\n * Credential storage for the TUI.\n *\n * Lives at `<dataDir>/credentials.json` (default `~/.zidane/credentials.json`,\n * overridable via `ZIDANE_STORAGE_DIR`). Owner-only (0o600) so refresh tokens\n * and API keys don't leak on shared boxes.\n *\n * Two credential kinds:\n * - `apikey` — a plain API key entered by the user (e.g. via the setup wizard).\n * - `oauth` — tokens obtained via a provider's OAuth flow (refresh-aware).\n *\n * **Schema note.** File keys come from {@link ProviderDescriptor.credentialFileKey}\n * (falling back to `descriptor.key`). They must match what the harness providers\n * look up at runtime — for built-ins this is `anthropic`, `openai-codex`,\n * `openrouter`, `cerebras`. Hosts that ship custom providers control the file\n * key via the descriptor.\n *\n * The legacy `cwd/.credentials.json` (written by `bun run auth` in older\n * versions) is migrated on first read if the new file doesn't exist yet. The\n * legacy file is left in place — we don't delete user data behind their back.\n */\n\nimport type { ProviderDescriptor } from './providers'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { credKeyOf } from './providers'\n\n/** POSIX mode for the credentials file. Ignored on Windows. */\nconst FILE_MODE = 0o600\n\nexport interface ApiKeyCredential {\n kind: 'apikey'\n value: string\n}\nexport interface OAuthCredential {\n kind: 'oauth'\n access: string\n refresh?: string\n expires?: number\n /** Provider-specific extras (e.g. OpenAI Codex `accountId`). */\n [extra: string]: unknown\n}\nexport type ProviderCredential = ApiKeyCredential | OAuthCredential\n\n/** Top-level shape of `credentials.json` — keys are credential-file keys. */\nexport type CredentialsFile = Record<string, ProviderCredential>\n\n/**\n * Resolve the credentials file path given the resolved TUI data directory\n * (typically `~/.zidane`, i.e. `config.paths.dir`).\n *\n * Matches the convention used elsewhere in the TUI (sessions.db, state.json)\n * so a single `ZIDANE_STORAGE_DIR` override moves the entire data root.\n */\nexport function credentialsPath(dataDir: string): string {\n return resolve(dataDir, 'credentials.json')\n}\n\n/**\n * Read credentials from disk.\n *\n * Returns `{}` when the file is missing or corrupt (last-ditch tolerance —\n * a hand-edit gone wrong shouldn't lock the user out of re-authing). On first\n * call with no file present, attempts a migration from `cwd/.credentials.json`\n * (the legacy location used by `bun run auth`).\n */\nexport function readCredentials(dataDir: string): CredentialsFile {\n const path = credentialsPath(dataDir)\n\n if (!existsSync(path)) {\n const migrated = migrateLegacyFile(path)\n if (migrated)\n return migrated\n return {}\n }\n\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n return parsed as CredentialsFile\n }\n catch {\n return {}\n }\n}\n\n/** Read a single provider's credential (translating via the descriptor). */\nexport function readProviderCredential(dataDir: string, descriptor: ProviderDescriptor): ProviderCredential | undefined {\n return readCredentials(dataDir)[credKeyOf(descriptor)]\n}\n\n/**\n * Write credentials atomically (write-then-rename) with mode 0o600.\n *\n * Atomic on the same filesystem — readers either see the previous file or the\n * new one, never a half-written intermediate. Creates the parent dir if needed\n * (first launch on a fresh machine: `~/.zidane/` may not exist yet).\n */\nexport function writeCredentials(dataDir: string, creds: CredentialsFile): void {\n writeFileAtomic(\n credentialsPath(dataDir),\n `${JSON.stringify(creds, null, 2)}\\n`,\n { ensureDir: true, mode: FILE_MODE },\n )\n}\n\nexport function setProviderCredential(\n dataDir: string,\n descriptor: ProviderDescriptor,\n cred: ProviderCredential,\n): void {\n const all = readCredentials(dataDir)\n all[credKeyOf(descriptor)] = cred\n writeCredentials(dataDir, all)\n}\n\nexport function removeProviderCredential(dataDir: string, descriptor: ProviderDescriptor): void {\n const all = readCredentials(dataDir)\n const fileKey = credKeyOf(descriptor)\n if (!(fileKey in all))\n return\n delete all[fileKey]\n writeCredentials(dataDir, all)\n}\n\n/**\n * Reconcile `process.env` with the stored credentials so the harness providers\n * pick up the wizard's output via their existing env-var resolution. Called\n * once at TUI launch after the credentials file has been resolved.\n *\n * **Precedence: stored credential > ambient env.** When the user has run the\n * auth wizard, that's an explicit, recent action — it MUST win over whatever\n * ambient value is already in `process.env`. Otherwise an upgrade reliably\n * breaks for anyone whose cwd happens to contain a stale `.env`: Bun\n * auto-loads `.env` from cwd into `process.env` before our code runs, the\n * legacy `bun run auth` used to upsert tokens into `cwd/.env`, and even a\n * one-off `export ANTHROPIC_API_KEY=…` from months ago in the user's shell rc\n * can outlive its usefulness. None of those should override a key the user\n * just typed into the wizard.\n *\n * Three cases per registered provider with an `envKey`:\n *\n * 1. Stored `kind: 'apikey'` → overwrite env with the stored value. Wizard\n * wins. A debug log fires when this clobbers a different ambient value.\n * 2. Stored `kind: 'oauth'` → DELETE env, so {@link resolveOAuthApiKey}\n * falls through to the file-read + refresh path. A stale OAuth token\n * in env would otherwise short-circuit refresh and get rejected.\n * 3. No stored entry → leave env alone. Env-only setups (no wizard run yet,\n * hosts driving zidane purely from secrets management) keep working.\n *\n * To opt out and force ambient env to win again, the user signs the provider\n * out via the wizard (which deletes the stored entry → case 3 above).\n *\n * Descriptors without an `envKey` (OAuth-only providers, custom providers\n * that bypass env-var resolution) are skipped silently.\n */\nexport function applyApiKeyEnv(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n): void {\n const creds = readCredentials(dataDir)\n for (const descriptor of Object.values(registry)) {\n if (!descriptor.envKey)\n continue\n const envKey = descriptor.envKey\n const ambient = process.env[envKey]\n const cred = creds[credKeyOf(descriptor)]\n\n if (cred?.kind === 'apikey' && cred.value) {\n if (ambient && ambient !== cred.value && process.env.ZIDANE_DEBUG) {\n process.stderr.write(\n `[zidane/chat] applyApiKeyEnv: overriding ambient \\`${envKey}\\` `\n + `with stored API key from credentials.json (provider=${descriptor.key}). `\n + `Sign out via the auth wizard if the ambient value was intended to win.\\n`,\n )\n }\n process.env[envKey] = cred.value\n continue\n }\n\n if (cred?.kind === 'oauth' && cred.access) {\n if (ambient !== undefined && process.env.ZIDANE_DEBUG) {\n process.stderr.write(\n `[zidane/chat] applyApiKeyEnv: clearing ambient \\`${envKey}\\` because `\n + `credentials.json has stored OAuth for ${descriptor.key} — refresh path needs the file.\\n`,\n )\n }\n delete process.env[envKey]\n continue\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Legacy migration\n// ---------------------------------------------------------------------------\n\n/**\n * `bun run auth` (pre-TUI) wrote `cwd/.credentials.json` with an entry per\n * provider mapping directly to an OAuthCredentials payload, e.g.:\n *\n * {\n * \"anthropic\": { \"access\": \"...\", \"refresh\": \"...\", \"expires\": 123 },\n * \"openai-codex\": { \"access\": \"...\", \"refresh\": \"...\", \"expires\": 123, \"accountId\": \"...\" }\n * }\n *\n * We don't delete the legacy file — it might still be used by a host that\n * imports the harness directly. We just copy its contents into the new\n * location under the kind-tagged shape so the TUI picks them up.\n *\n * Migration is provider-agnostic: any top-level entry with an `access` field\n * is preserved verbatim (extras included), under the same key. The TUI's\n * detection then looks them up via the matching descriptor's `credentialFileKey`.\n *\n * Returns the migrated credentials when the migration ran, or `null` when\n * there's no legacy file to migrate.\n */\nfunction migrateLegacyFile(targetPath: string): CredentialsFile | null {\n const legacyPath = resolve(process.cwd(), '.credentials.json')\n if (!existsSync(legacyPath))\n return null\n\n let legacy: Record<string, unknown>\n try {\n legacy = JSON.parse(readFileSync(legacyPath, 'utf-8'))\n }\n catch {\n return null\n }\n if (!legacy || typeof legacy !== 'object' || Array.isArray(legacy))\n return null\n\n const migrated: CredentialsFile = {}\n for (const [fileKey, value] of Object.entries(legacy)) {\n if (!isOAuthLegacy(value))\n continue\n const { access, refresh, expires, ...extras } = value\n migrated[fileKey] = {\n kind: 'oauth',\n access,\n ...(typeof refresh === 'string' ? { refresh } : {}),\n ...(typeof expires === 'number' ? { expires } : {}),\n ...extras,\n }\n }\n\n if (Object.keys(migrated).length === 0)\n return null\n\n // Persist immediately so subsequent reads use the new file directly.\n writeFileAtomic(\n targetPath,\n `${JSON.stringify(migrated, null, 2)}\\n`,\n { ensureDir: true, mode: FILE_MODE },\n )\n\n return migrated\n}\n\nfunction isOAuthLegacy(value: unknown): value is { access: string, refresh?: string, expires?: number, [extra: string]: unknown } {\n return (\n typeof value === 'object'\n && value !== null\n && 'access' in value\n && typeof (value as { access: unknown }).access === 'string'\n )\n}\n","import type { ProviderCredential } from './credentials'\nimport type { ProviderDescriptor } from './providers'\nimport { readCredentials } from './credentials'\nimport { credKeyOf } from './providers'\n\n/**\n * Provider identifier as known to the TUI. Built-in keys are `anthropic`,\n * `openai`, `openrouter`, `cerebras`, but hosts can register additional\n * keys via {@link ProviderDescriptor} — so the type is open.\n */\nexport type ProviderKey = string\n\nexport interface AuthMethod {\n source: 'env' | 'oauth' | 'apikey'\n /** Human-readable detail (env var name, expiry timestamp, …). */\n detail: string\n}\n\nexport interface ProviderAuth {\n key: ProviderKey\n label: string\n /** True when at least one credential source is present. */\n available: boolean\n methods: AuthMethod[]\n}\n\n/**\n * Detect available auth for every registered provider.\n *\n * Resolution order per provider (a method appears in `methods` for each\n * layer that has a credential — the agent itself resolves them in the same\n * order via its provider factories):\n *\n * 1. `kind: 'apikey'` from `credentials.json` (injected into env at TUI launch)\n * 2. explicit env var (descriptor's `envKey`)\n * 3. `kind: 'oauth'` from `credentials.json` (or legacy `cwd/.credentials.json`)\n *\n * Pure read — never refreshes or rewrites the credentials file.\n */\nexport function detectAuth(\n dataDir: string,\n registry: Readonly<Record<string, ProviderDescriptor>>,\n env: Record<string, string | undefined> = process.env,\n): ProviderAuth[] {\n const creds = readCredentials(dataDir)\n\n return Object.values(registry).map((descriptor) => {\n const methods: AuthMethod[] = []\n const fileEntry = creds[credKeyOf(descriptor)] as ProviderCredential | undefined\n\n // 1. Stored API key\n if (fileEntry?.kind === 'apikey' && fileEntry.value)\n methods.push({ source: 'apikey', detail: 'credentials.json' })\n\n // 2. Env var (may be set by `applyApiKeyEnv` from a stored key, or by the\n // user's shell. Either way, treated as the same observable signal.)\n if (descriptor.envKey && env[descriptor.envKey])\n methods.push({ source: 'env', detail: descriptor.envKey })\n\n // 3. Stored OAuth\n if (fileEntry?.kind === 'oauth' && fileEntry.access) {\n const detail = typeof fileEntry.expires === 'number'\n ? `oauth · expires ${new Date(fileEntry.expires).toLocaleString()}`\n : 'oauth · credentials.json'\n methods.push({ source: 'oauth', detail })\n }\n\n return {\n key: descriptor.key,\n label: descriptor.label,\n available: methods.length > 0,\n methods,\n }\n })\n}\n","/**\n * Auto-compaction trigger policy — decide whether the latest turn's usage\n * has crossed the threshold that should fire `compactConversation` against\n * the active session.\n *\n * Pure function, no I/O. Sits next to the chat-layer settings + providers\n * so the same decision is reusable from a non-TUI host (a future GUI shell\n * that wants to surface \"Compact now\" suggestions, a CI runner that wants\n * to auto-compact between batches, …).\n *\n * Wire contract:\n *\n * - Inputs are the bits the TUI already tracks: latest `usage.input` (from\n * `turn:after`), the model's raw context window (`getContextWindow`),\n * and the user's settings (`autoCompact` + `autoCompactThreshold`).\n * - Output is a discriminated outcome — `'fire'` when the trigger should\n * run, `'skip'` with a reason otherwise. The reason is diagnostic only;\n * callers don't branch on it but tests + debug logs need it.\n *\n * No state. Same inputs → same answer.\n */\n\nimport { effectiveContextWindow } from './providers'\n\n/**\n * Default hysteresis floor for {@link shouldAutoCompact}. After a successful\n * compaction lands, the next compaction is suppressed until input usage\n * grows by at least this fraction of the effective context window beyond\n * the post-compact baseline.\n *\n * `0.1` = 10% of the window. Picked so the immediate post-compact bounce\n * (summary turn + restored attachments + re-emitted system prefix) never\n * re-fires the trigger by itself, but a meaningful chunk of new work\n * (one or two large tool reads) does. Tune via the predicate's\n * `minGrowthFraction` input if a host needs different ergonomics.\n */\nexport const AUTO_COMPACT_MIN_GROWTH_FRACTION = 0.1\n\n/**\n * Inputs to {@link shouldAutoCompact}.\n *\n * Kept as a struct so the call site reads as the predicate it is, and so\n * future inputs (per-session opt-out, recent-compaction cooldown, …) land\n * without re-threading every consumer.\n */\nexport interface AutoCompactInput {\n /** User's master toggle. `false` → trigger never fires. */\n enabled: boolean\n /**\n * Threshold expressed as a fraction of the effective context window\n * (e.g. `0.8` = 80%). Values outside the open interval `(0, 1)` are\n * rejected with `'invalid-threshold'` — `<= 0` is meaningless, and\n * `>= 1` would schedule the trigger for \"after we've already\n * overflowed\" which is too late to be useful.\n */\n threshold: number\n /**\n * Latest input-token count from the last `turn:after`. Must be a\n * non-negative finite number; `NaN` / `±Infinity` / negative values\n * are treated as \"no usable signal\" and the trigger skips\n * conservatively (we'd rather defer compaction than fire on garbage).\n */\n inputTokens: number\n /**\n * Model's raw context window from `getContextWindow(descriptor, model)`.\n * `null` for unknown / custom models — the trigger skips since we can't\n * compute a ratio.\n */\n rawContextWindow: number | null\n /**\n * Whether a compaction is already in flight for this session. The TUI\n * tracks this with a ref + Promise; passing it through the predicate\n * lets the test suite assert \"doesn't double-fire\" without spinning up\n * the agent loop.\n */\n alreadyCompacting?: boolean\n /**\n * Input-token count observed immediately AFTER the most recent successful\n * compaction landed. Used together with {@link minGrowthFraction} to\n * suppress thrashing — after compaction lowers the prefix from 85% → 35%,\n * the next few turns drift back up; without hysteresis the trigger fires\n * again the instant the threshold is re-crossed, often before the model\n * has done any new work. Tracking the post-compact baseline lets the\n * predicate require meaningful new growth before re-firing.\n *\n * `undefined` (no compaction yet this session) → hysteresis disabled,\n * predicate behaves as before.\n */\n lastCompactedInputTokens?: number\n /**\n * Minimum fraction of the effective context window that must be CONSUMED\n * by new content (i.e. `inputTokens - lastCompactedInputTokens`) before\n * a follow-up compaction fires. Expressed in the same units as\n * {@link threshold}, e.g. `0.1` = 10% of the window. Defaults to `0`\n * (no hysteresis) when omitted; chat hosts pick a value like `0.1`.\n *\n * Only consulted when {@link lastCompactedInputTokens} is set — first\n * compaction in a session always fires off the absolute threshold.\n */\n minGrowthFraction?: number\n}\n\nexport type AutoCompactDecision\n = | { kind: 'fire', usedFraction: number, effectiveWindow: number }\n | { kind: 'skip', reason: 'disabled' | 'unknown-window' | 'invalid-threshold' | 'invalid-input-tokens' | 'under-threshold' | 'already-compacting' | 'cooldown' }\n\n/**\n * Decide whether auto-compaction should fire for the latest turn.\n *\n * Order of checks is deliberate: cheapest / most common skips first\n * (`disabled` is one boolean read; `unknown-window` is the predictable\n * miss for custom models), invariant violations next, then the actual\n * threshold math. `already-compacting` runs last so the diagnostic\n * surfaces correctly when the predicate is called inside an existing\n * compaction window.\n */\nexport function shouldAutoCompact(input: AutoCompactInput): AutoCompactDecision {\n if (!input.enabled)\n return { kind: 'skip', reason: 'disabled' }\n\n if (input.rawContextWindow === null)\n return { kind: 'skip', reason: 'unknown-window' }\n\n if (!Number.isFinite(input.threshold) || input.threshold <= 0 || input.threshold >= 1)\n return { kind: 'skip', reason: 'invalid-threshold' }\n\n // Guard against `NaN` / `±Infinity` / negative usage. The realistic\n // source for any of these is a provider returning `undefined` (`?? 0`\n // collapses to 0 normally, but a malformed nested object could leak\n // through). We'd rather skip than emit `used NaN%` to the user.\n if (!Number.isFinite(input.inputTokens) || input.inputTokens < 0)\n return { kind: 'skip', reason: 'invalid-input-tokens' }\n\n // `effectiveContextWindow` clamps to `>= 1` and only returns `null`\n // when the raw window is `null` — already short-circuited above. So\n // the result here is guaranteed positive, no further defense needed.\n const effectiveWindow = effectiveContextWindow(input.rawContextWindow)!\n\n const usedFraction = input.inputTokens / effectiveWindow\n if (usedFraction < input.threshold)\n return { kind: 'skip', reason: 'under-threshold' }\n\n // Hysteresis: once a session has compacted, require meaningful new growth\n // before re-firing. Without this, the threshold is crossed again the\n // moment the post-compact summary turn + a re-emitted system prefix push\n // usage back over the line, leading to compact → compact → compact loops\n // that lose more context than they save. Only active when the caller\n // tracks the post-compact baseline AND opts into a non-zero growth floor.\n if (typeof input.lastCompactedInputTokens === 'number'\n && Number.isFinite(input.lastCompactedInputTokens)\n && input.lastCompactedInputTokens >= 0\n && typeof input.minGrowthFraction === 'number'\n && Number.isFinite(input.minGrowthFraction)\n && input.minGrowthFraction > 0) {\n const growthTokens = input.inputTokens - input.lastCompactedInputTokens\n const growthFraction = growthTokens / effectiveWindow\n if (growthFraction < input.minGrowthFraction)\n return { kind: 'skip', reason: 'cooldown' }\n }\n\n if (input.alreadyCompacting)\n return { kind: 'skip', reason: 'already-compacting' }\n\n return { kind: 'fire', usedFraction, effectiveWindow }\n}\n","// ---------------------------------------------------------------------------\n// Auto-update — renderer-agnostic core.\n//\n// Three layers, all reusable by any consumer that wraps `zidane/tui`:\n// 1. `checkForUpdate` — fetches the latest version from an npm-compatible\n// registry, persisted on disk with a TTL so the hot path costs zero\n// network on a warm cache.\n// 2. `performSelfUpdate` — spawns the host package manager (npm, pnpm,\n// yarn, bun, volta) and tells it to re-install the package globally.\n// 3. `performInPlaceSelfUpdate` — downloads the platform-binary tarball\n// from the registry, extracts the binary, atomically swaps it over\n// the live one. Used when no package manager is reachable (offline\n// install path, restricted shells) or when the user wants speed.\n//\n// Pure data + Node-builtin I/O. No React, no OpenTUI, no external deps —\n// hosts replacing the runtime can re-implement just the layer they need.\n// ---------------------------------------------------------------------------\n\nimport type { Buffer } from 'node:buffer'\nimport { spawn } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport {\n chmodSync,\n createReadStream,\n createWriteStream,\n mkdirSync,\n mkdtempSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from 'node:fs'\nimport { homedir, tmpdir } from 'node:os'\nimport { dirname, join, resolve, sep } from 'node:path'\nimport { createGunzip } from 'node:zlib'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface CheckForUpdateOptions {\n /** Full npm package name to query. e.g. `'zidane-tui'`. */\n packageName: string\n /** Current installed version. Inlined at build time from the consumer's `package.json`. */\n currentVersion: string\n /**\n * Registry base URL. Defaults to `https://registry.npmjs.org`. Strip the\n * trailing slash; the helper appends `/${packageName}/${channel}`.\n */\n registry?: string\n /**\n * dist-tag to read. Defaults to `'latest'`. Set to `'next'` (or any\n * custom tag) for prerelease channels — the comparison still goes through\n * `compareSemver`, so `5.6.0-beta.2 > 5.5.0` evaluates correctly.\n */\n channel?: string\n /** Directory used for the on-disk TTL cache. Skipped when omitted. */\n cacheDir?: string\n /** Cache lifetime. Defaults to 24 h. Negative / 0 disables caching. */\n cacheTtlMs?: number\n /** Network timeout. Defaults to 3 000 ms. */\n timeoutMs?: number\n /** Abort signal — wins over the internal timeout. */\n signal?: AbortSignal\n /**\n * When `true`, ignore env-var opt-outs (`CI`, `NO_UPDATE_NOTIFIER`,\n * `ZIDANE_NO_UPDATE`) AND bypass the on-disk TTL cache (always hit\n * the registry). Used by the explicit `zidane upgrade` path where the\n * user typed the command — they expect fresh data, not a 24-hour-old\n * cached value. The cache is still WRITTEN with the fresh result so\n * subsequent background checks benefit; cache reads only feed the\n * etag into the next request so the registry can short-circuit with\n * 304 when nothing changed.\n */\n force?: boolean\n /** Inject for tests. Defaults to `globalThis.fetch`. */\n fetcher?: typeof fetch\n /** Inject for tests. Defaults to `Date.now`. */\n now?: () => number\n}\n\nexport interface UpdateStatus {\n /** Always present — echoes `currentVersion`. */\n current: string\n /** `null` when the lookup failed / was skipped / cache was empty. */\n latest: string | null\n /** `true` when `latest` is strictly greater than `current` by semver. */\n hasUpdate: boolean\n /**\n * - `'fresh'` — fetched from the registry this call.\n * - `'cached'` — served from the on-disk cache.\n * - `'skipped'` — opt-out env var, missing fetch, network refused, etc.\n * - `'error'` — registry returned non-2xx or threw; details on `error`.\n */\n source: 'fresh' | 'cached' | 'skipped' | 'error'\n /** Error message when `source === 'error'`. */\n error?: string\n /** When the registry was last queried (ms since epoch). `null` when never. */\n checkedAt: number | null\n /** dist-tag used for the lookup — handy when surfacing \"v5.6.0 (next)\". */\n channel: string\n}\n\nexport interface PackageManagerCommand {\n /** Stable identifier. `npm` is the fallback when no other signal fires. */\n id: PackageManagerId\n /** Argv that, when execve'd, installs `packageName@channel` globally. */\n argv: string[]\n /**\n * Free-form note for the user — explains *why* this command was chosen\n * (e.g. \"Volta-managed install detected via ~/.volta/bin\").\n */\n note?: string\n}\n\nexport type PackageManagerId = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'volta'\n\nexport interface DetectPackageManagerOptions {\n /** Override the resolved binary path (defaults to `process.execPath`). */\n binaryPath?: string\n /** Override the user-agent string (defaults to `process.env.npm_config_user_agent`). */\n userAgent?: string | undefined\n /** Override env. Used in tests. */\n env?: Readonly<Record<string, string | undefined>>\n /** Override `homedir()`. Used in tests. */\n home?: string\n /** Override `process.platform`. Used in tests. */\n platform?: NodeJS.Platform\n /** When set, the host already knows the package name (avoids the test seam). */\n packageName?: string\n /** dist-tag. Defaults to `'latest'`. */\n channel?: string\n}\n\n// ---------------------------------------------------------------------------\n// Env-var opt-outs\n//\n// `NO_UPDATE_NOTIFIER` is the de-facto npm-ecosystem convention (yeoman,\n// update-notifier). `ZIDANE_NO_UPDATE` is the project-specific knob so\n// hosts wrapping the TUI can wire their own without colliding.\n// `CI=true` is set by every major CI runner; skipping there avoids\n// surprise registry chatter from build pipelines.\n// ---------------------------------------------------------------------------\n\nconst OPT_OUT_VARS = ['ZIDANE_NO_UPDATE', 'NO_UPDATE_NOTIFIER', 'CI'] as const\n\nfunction isOptedOut(env: NodeJS.ProcessEnv = process.env): boolean {\n for (const key of OPT_OUT_VARS) {\n const v = env[key]\n if (v !== undefined && v !== '' && v !== '0' && v.toLowerCase() !== 'false')\n return true\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Semver compare — tiny self-contained implementation.\n//\n// We support the subset of semver the npm registry actually returns:\n// `MAJOR.MINOR.PATCH` plus optional `-prerelease[.N…]` and `+build`. Build\n// metadata is ignored per spec; prereleases compare via the standard rule\n// (numeric vs string, longer chain wins after equal prefix).\n//\n// Inlining this beats pulling in `semver` (~70 KB) just to compare two\n// strings; we already accept the maintenance trade-off for `tail-truncated`\n// shells and other tiny utilities. Tested in `test/chat/auto-update.test.ts`.\n// ---------------------------------------------------------------------------\n\nexport function parseSemver(input: string): { major: number, minor: number, patch: number, prerelease: string[] } | null {\n const m = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\w.-]+))?(?:\\+[\\w.-]+)?$/.exec(input.trim())\n if (!m)\n return null\n return {\n major: Number(m[1]),\n minor: Number(m[2]),\n patch: Number(m[3]),\n prerelease: m[4] ? m[4].split('.') : [],\n }\n}\n\n/** Returns `-1 / 0 / 1`. Unparseable inputs sort as `0` (assume equal). */\nexport function compareSemver(a: string, b: string): -1 | 0 | 1 {\n const A = parseSemver(a)\n const B = parseSemver(b)\n if (!A || !B)\n return 0\n for (const k of ['major', 'minor', 'patch'] as const) {\n if (A[k] !== B[k])\n return A[k] < B[k] ? -1 : 1\n }\n // A release version is greater than its prereleases (1.0.0 > 1.0.0-rc.1).\n if (A.prerelease.length === 0 && B.prerelease.length > 0)\n return 1\n if (A.prerelease.length > 0 && B.prerelease.length === 0)\n return -1\n const len = Math.max(A.prerelease.length, B.prerelease.length)\n for (let i = 0; i < len; i++) {\n const ai = A.prerelease[i]\n const bi = B.prerelease[i]\n if (ai === undefined)\n return -1\n if (bi === undefined)\n return 1\n const an = /^\\d+$/.test(ai) ? Number(ai) : null\n const bn = /^\\d+$/.test(bi) ? Number(bi) : null\n if (an !== null && bn !== null) {\n if (an !== bn)\n return an < bn ? -1 : 1\n }\n else if (an !== null) {\n return -1 // numeric < alphanumeric per spec\n }\n else if (bn !== null) {\n return 1\n }\n else if (ai !== bi) {\n return ai < bi ? -1 : 1\n }\n }\n return 0\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\ninterface UpdateCheckFile {\n packageName: string\n channel: string\n latest: string\n checkedAt: number\n /** Last-seen registry ETag — flows through If-None-Match on the next check. */\n etag?: string\n}\n\nfunction cacheFilePath(cacheDir: string, packageName: string, channel: string): string {\n // Embed package + channel in the filename so a host running multiple\n // checks (e.g. against `latest` and `next`) doesn't trample state.\n const slug = `${packageName.replaceAll('/', '__')}@${channel}`\n return resolve(cacheDir, `update-check-${slug}.json`)\n}\n\nfunction readCache(path: string): UpdateCheckFile | null {\n try {\n const raw = readFileSync(path, 'utf8')\n const parsed = JSON.parse(raw) as UpdateCheckFile\n if (typeof parsed.latest !== 'string' || typeof parsed.checkedAt !== 'number')\n return null\n return parsed\n }\n catch {\n return null\n }\n}\n\nfunction writeCache(path: string, entry: UpdateCheckFile): void {\n try {\n mkdirSync(dirname(path), { recursive: true })\n // Format with a trailing newline so hand-inspection in a terminal\n // doesn't render the prompt glued to the closing brace.\n writeFileSync(path, `${JSON.stringify(entry, null, 2)}\\n`)\n }\n catch {\n // Cache failures are non-fatal — the next call just re-fetches.\n }\n}\n\n// ---------------------------------------------------------------------------\n// Registry fetch\n// ---------------------------------------------------------------------------\n\n/** Strip a trailing slash so URL assembly is uniform. */\nfunction trimSlash(s: string): string {\n return s.endsWith('/') ? s.slice(0, -1) : s\n}\n\ninterface FetchedRelease {\n latest: string\n etag: string | null\n}\n\nasync function fetchLatestRelease(opts: {\n registry: string\n packageName: string\n channel: string\n etag: string | undefined\n fetcher: typeof fetch\n signal: AbortSignal\n}): Promise<FetchedRelease> {\n // Use the `<pkg>/<dist-tag>` endpoint which returns a single packument\n // (the smallest payload). Falls back to the abbreviated metadata mime\n // type so npm proxies (Verdaccio, JFrog) that don't honor it still\n // return JSON.\n const url = `${trimSlash(opts.registry)}/${encodeURIComponent(opts.packageName)}/${encodeURIComponent(opts.channel)}`\n const headers: Record<string, string> = {\n 'accept': 'application/vnd.npm.install-v1+json, application/json',\n 'user-agent': 'zidane-update-check/1',\n }\n if (opts.etag)\n headers['if-none-match'] = opts.etag\n const res = await opts.fetcher(url, { headers, signal: opts.signal, redirect: 'follow' })\n if (res.status === 304) {\n // Caller falls back to the cached entry when this throws — keep the\n // signal explicit instead of overloading the return shape.\n throw new NotModifiedError()\n }\n if (!res.ok)\n throw new Error(`registry returned ${res.status}`)\n const body = await res.json() as { version?: string, name?: string }\n if (!body.version)\n throw new Error('registry payload missing `version`')\n return { latest: body.version, etag: res.headers.get('etag') }\n}\n\nclass NotModifiedError extends Error {\n constructor() { super('not-modified') }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: checkForUpdate\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the latest available version of `packageName`, honoring an\n * on-disk TTL cache so the warm path costs zero network. Never throws —\n * registry failures degrade to `{ source: 'error', latest: null }`.\n *\n * Designed for boot-path usage: short timeout, swallows errors, opt-outs\n * for CI / restricted shells. Consumers driving the explicit `upgrade`\n * subcommand should set `force: true` to ignore env opt-outs.\n */\nexport async function checkForUpdate(options: CheckForUpdateOptions): Promise<UpdateStatus> {\n const channel = options.channel ?? 'latest'\n const now = options.now ?? Date.now\n const fetcher = options.fetcher ?? globalThis.fetch\n const ttl = options.cacheTtlMs ?? 24 * 60 * 60 * 1000\n const timeoutMs = options.timeoutMs ?? 3000\n\n if (!options.force && isOptedOut())\n return skipped(options.currentVersion, channel)\n\n const cachePath = options.cacheDir\n ? cacheFilePath(options.cacheDir, options.packageName, channel)\n : null\n\n // Fast-path the warm cache — but only when the cached payload still\n // matches `packageName + channel`. The slug guards against an\n // older zidane writing a different package's cache file at the same\n // path (host rename scenarios). `force` skips the freshness short-\n // circuit entirely: the user-typed `upgrade` path always wants the\n // truth from the registry, never a 24-hour-old cached \"no update\".\n const cached = cachePath ? readCache(cachePath) : null\n if (!options.force && cached && cached.packageName === options.packageName && cached.channel === channel) {\n const fresh = ttl > 0 && (now() - cached.checkedAt) < ttl\n if (fresh) {\n const hasUpdate = compareSemver(cached.latest, options.currentVersion) > 0\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n }\n\n // Fetch is required for the cold path. Falling back to a stale cache is\n // checked further below, but the contract for `skipped` is \"we didn't\n // even try\" — without a fetcher we can't try.\n if (typeof fetcher !== 'function') {\n if (cached) {\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return skipped(options.currentVersion, channel)\n }\n\n // Tie the user's signal to our timeout so abort propagates. The\n // controller is local to this call — we never leak it to callers.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const release = await fetchLatestRelease({\n registry: options.registry ?? 'https://registry.npmjs.org',\n packageName: options.packageName,\n channel,\n etag: cached?.etag,\n fetcher,\n signal: controller.signal,\n })\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n packageName: options.packageName,\n channel,\n latest: release.latest,\n checkedAt,\n ...(release.etag ? { etag: release.etag } : {}),\n })\n }\n return {\n current: options.currentVersion,\n latest: release.latest,\n hasUpdate: compareSemver(release.latest, options.currentVersion) > 0,\n source: 'fresh',\n checkedAt,\n channel,\n }\n }\n catch (err) {\n if (err instanceof NotModifiedError && cached) {\n // 304 — extend the cache freshness window so we don't re-poll\n // every boot when the registry agrees we're current.\n const checkedAt = now()\n if (cachePath) {\n writeCache(cachePath, {\n ...cached,\n checkedAt,\n })\n }\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt,\n channel,\n }\n }\n if (cached) {\n // Fall back to a stale cache rather than reporting `null` when we\n // have data — the user still sees something useful.\n return {\n current: options.currentVersion,\n latest: cached.latest,\n hasUpdate: compareSemver(cached.latest, options.currentVersion) > 0,\n source: 'cached',\n checkedAt: cached.checkedAt,\n channel,\n }\n }\n return {\n current: options.currentVersion,\n latest: null,\n hasUpdate: false,\n source: 'error',\n error: err instanceof Error ? err.message : String(err),\n checkedAt: null,\n channel,\n }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction skipped(current: string, channel: string): UpdateStatus {\n return { current, latest: null, hasUpdate: false, source: 'skipped', checkedAt: null, channel }\n}\n\n// ---------------------------------------------------------------------------\n// Package-manager detection\n//\n// Layered heuristics — strongest signal wins:\n// 1. Resolved binary path. Volta lives under `~/.volta/bin`, bun under\n// `~/.bun/install/global/node_modules/.bin` (or `$BUN_INSTALL/bin`),\n// pnpm under `~/.local/share/pnpm` or `~/Library/pnpm`, classic yarn\n// under `~/.yarn/bin`, npm anywhere else under a `<prefix>/bin`.\n// 2. `npm_config_user_agent` env var — set during `npm/pnpm/yarn/bun\n// install` runs, so this still helps when the user is mid-install.\n// 3. Default to npm. Universally available; users on more exotic PMs\n// have to pass `--package-manager` (or set up their env so the path\n// heuristic fires).\n//\n// Volta is treated as a first-class PM here even though it wraps another\n// (typically npm) — `volta install` is the only correct way to update a\n// Volta-pinned tool. Writing through the inner PM would leave Volta's\n// catalog stale and re-pin the old version on the next shell.\n// ---------------------------------------------------------------------------\n\nfunction normalizePath(path: string): string {\n // Resolve symlinks so a `/usr/local/bin/zidane` → `/opt/homebrew/...`\n // chain still matches the inner location. `realpathSync` throws on\n // broken links; fall back to the literal path then.\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\nexport function detectPackageManager(opts: DetectPackageManagerOptions = {}): PackageManagerCommand {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n const channel = opts.channel ?? 'latest'\n const packageName = opts.packageName ?? 'zidane-tui'\n const binaryPath = opts.binaryPath ?? process.execPath\n const resolvedBinary = normalizePath(binaryPath)\n const userAgent = opts.userAgent ?? env.npm_config_user_agent\n\n // The argv contract:\n // - `argv[0]` is the binary we exec — needs to be on PATH or absolute.\n // - Remaining tokens are the install command.\n // We rely on PATH resolution so users with shimmed wrappers (volta,\n // mise, asdf) still hit their managed binary.\n const target = `${packageName}@${channel}`\n\n // 1. Path-based heuristics ------------------------------------------------\n //\n // Match path SEGMENTS, not substrings — `/Users/me/.volta-old/bin/x` must\n // NOT match `/Users/me/.volta`. We anchor the comparison to a separator\n // on both sides so the directory name has to be exact.\n\n const containsSegment = (path: string, segment: string) => {\n // `segment` is `<home>/.<name>` (no trailing separator). To match, the\n // path must contain `<segment><sep>` OR end with `<segment>`.\n const tail = `${segment}${sep}`\n return path.includes(tail) || path.endsWith(segment)\n }\n const inPath = (segment: string) =>\n containsSegment(resolvedBinary, segment) || containsSegment(binaryPath, segment)\n const homeSeg = (rest: string) => `${home}${sep}${rest}`\n\n // Binary-path check — strongest signal. A binary living under `~/.volta/`\n // was DEFINITELY installed by Volta; same logic for the others.\n if (inPath(homeSeg('.volta'))) {\n return {\n id: 'volta',\n argv: ['volta', 'install', target],\n note: 'Volta-managed install detected — refreshing via `volta install`.',\n }\n }\n if (inPath(homeSeg('.bun'))) {\n return {\n id: 'bun',\n argv: ['bun', 'add', '--global', target],\n note: 'Bun global install detected.',\n }\n }\n if (\n inPath(homeSeg('.local/share/pnpm'))\n || inPath(homeSeg('Library/pnpm'))\n || resolvedBinary.includes(`${sep}pnpm${sep}`)\n ) {\n return {\n id: 'pnpm',\n argv: ['pnpm', 'add', '--global', target],\n note: 'pnpm global install detected.',\n }\n }\n if (inPath(homeSeg('.yarn'))) {\n return {\n id: 'yarn',\n argv: ['yarn', 'global', 'add', target],\n note: 'Yarn classic global install detected.',\n }\n }\n\n // 2. User-agent heuristic (mid-install or recently-installed shells) ------\n // `npm_config_user_agent` looks like `npm/10.2.3 node/v22.0.0 darwin arm64`\n // or `pnpm/8.15.0 npm/? node/v22.0.0 darwin arm64`. This fires when the\n // binary lives in an unrecognized prefix (corporate npm proxy, custom\n // global root, …) but the calling shell still identifies a PM.\n if (userAgent) {\n const ua = userAgent.toLowerCase()\n if (ua.startsWith('pnpm/'))\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n if (ua.startsWith('yarn/'))\n return { id: 'yarn', argv: ['yarn', 'global', 'add', target] }\n if (ua.startsWith('bun/'))\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (ua.startsWith('npm/'))\n return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n\n // 3. Env-var fallback — only fires when both the path and the user-agent\n // failed to identify a PM. We trust these LESS than the binary path\n // because a user can install zidane outside their main PM (e.g. via\n // npm) while still having $VOLTA_HOME / $BUN_INSTALL set for unrelated\n // tooling.\n if (env.VOLTA_HOME)\n return { id: 'volta', argv: ['volta', 'install', target], note: '$VOLTA_HOME set — using `volta install`. If the install isn\\'t Volta-managed, pass `--package-manager npm`.' }\n if (env.BUN_INSTALL)\n return { id: 'bun', argv: ['bun', 'add', '--global', target] }\n if (env.PNPM_HOME)\n return { id: 'pnpm', argv: ['pnpm', 'add', '--global', target] }\n\n // 4. Default to npm — universally available.\n\n void platform // Reserved for Windows-specific tweaks (none yet).\n return {\n id: 'npm',\n argv: ['npm', 'install', '--global', target],\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public API: performSelfUpdate (PM-spawned)\n// ---------------------------------------------------------------------------\n\nexport interface PerformSelfUpdateOptions {\n packageName: string\n channel?: string\n /** Force a specific PM instead of auto-detect. */\n packageManager?: PackageManagerId | 'auto'\n /** Don't actually spawn — return the command we WOULD run. */\n dryRun?: boolean\n /** When set, write `pm stdout/stderr` to these streams instead of stdio inherit. */\n stdout?: NodeJS.WritableStream\n stderr?: NodeJS.WritableStream\n /** Abort the install. */\n signal?: AbortSignal\n /** Override env (e.g. set `npm_config_loglevel=error`). */\n env?: Readonly<Record<string, string>>\n /** Inject for tests. */\n spawn?: typeof spawn\n /** Inject for tests. */\n detect?: typeof detectPackageManager\n}\n\nexport interface SelfUpdateResult {\n command: PackageManagerCommand\n /** PM exit code. `null` when `dryRun` or the process was killed. */\n exitCode: number | null\n /** Set when the spawn itself failed (`ENOENT`, EACCES). */\n spawnError?: string\n}\n\n/**\n * Re-install the package globally via the host's package manager. Resolves\n * with the spawned command's exit code, or rejects only when the caller's\n * `signal` aborts. PM failures surface as a non-zero `exitCode` — the\n * caller decides whether to retry or fall back to in-place.\n */\nexport async function performSelfUpdate(options: PerformSelfUpdateOptions): Promise<SelfUpdateResult> {\n const detect = options.detect ?? detectPackageManager\n const command = options.packageManager && options.packageManager !== 'auto'\n ? overrideManager(options.packageManager, options.packageName, options.channel ?? 'latest')\n : detect({ packageName: options.packageName, channel: options.channel ?? 'latest' })\n\n if (options.dryRun)\n return { command, exitCode: null }\n\n const useSpawn = options.spawn ?? spawn\n return await new Promise<SelfUpdateResult>((resolveP) => {\n const [bin, ...args] = command.argv\n const child = useSpawn(bin!, args, {\n stdio: options.stdout || options.stderr\n ? ['ignore', 'pipe', 'pipe']\n : 'inherit',\n env: { ...process.env, ...options.env },\n })\n if (options.stdout && child.stdout)\n child.stdout.pipe(options.stdout)\n if (options.stderr && child.stderr)\n child.stderr.pipe(options.stderr)\n child.on('error', (err) => {\n // ENOENT here means the PM binary isn't on PATH — surface a\n // clear message rather than the bare error.\n resolveP({ command, exitCode: null, spawnError: err.message })\n })\n child.on('exit', (code) => {\n resolveP({ command, exitCode: code })\n })\n if (options.signal) {\n const onAbort = () => child.kill()\n if (options.signal.aborted)\n onAbort()\n else\n options.signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n\nfunction overrideManager(id: PackageManagerId, packageName: string, channel: string): PackageManagerCommand {\n const target = `${packageName}@${channel}`\n switch (id) {\n case 'pnpm': return { id, argv: ['pnpm', 'add', '--global', target] }\n case 'yarn': return { id, argv: ['yarn', 'global', 'add', target] }\n case 'bun': return { id, argv: ['bun', 'add', '--global', target] }\n case 'volta': return { id, argv: ['volta', 'install', target] }\n case 'npm':\n default: return { id: 'npm', argv: ['npm', 'install', '--global', target] }\n }\n}\n\n// ---------------------------------------------------------------------------\n// In-place self-update\n//\n// Downloads `<packageName>@<channel>` from the npm registry, untars the\n// `bin/<filename>` entry, codesigns ad-hoc on darwin if needed, and atomic-\n// renames over the live binary. The host needs to know:\n// - the platform-specific package name to fetch (e.g. `zidane-tui-darwin-arm64`)\n// - the path inside that tarball where the binary lives (defaults to\n// `package/bin/<binaryName>`, which matches the project layout under\n// `tui/npm/<key>/bin/`)\n// - the path of the running binary, so we know where to write\n//\n// Volta-managed installs are explicitly refused — Volta's catalog needs\n// the PM path. The caller should detect via `detectPackageManager` and\n// fall through to `performSelfUpdate` instead.\n// ---------------------------------------------------------------------------\n\nexport interface PerformInPlaceUpdateOptions {\n /** Platform-specific tarball package, e.g. `'zidane-tui-darwin-arm64'`. */\n packageName: string\n channel?: string\n /** Resolved path of the running binary. Defaults to `process.execPath`. */\n binaryPath?: string\n /** Inside the tarball — usually `bin/<name>`. Defaults to `bin/<basename(binaryPath)>`. */\n tarballBinaryPath?: string\n registry?: string\n /** Network timeout (per request). Defaults to 30 000 ms — tarballs are MB-scale. */\n timeoutMs?: number\n signal?: AbortSignal\n /** Don't replace — just download + verify. */\n dryRun?: boolean\n fetcher?: typeof fetch\n /** When set, refuse the swap if the resolved binary lives under one of these prefixes. */\n refuseUnderPrefixes?: readonly string[]\n}\n\nexport interface InPlaceUpdateResult {\n /** `'success' | 'refused' | 'failed'`. */\n status: 'success' | 'refused' | 'failed'\n /** Version that was installed (from the tarball's package.json). */\n installedVersion?: string\n /** Why we bailed. Always present on `refused` / `failed`. */\n reason?: string\n /** Resolved binary path after the swap. */\n binaryPath?: string\n}\n\n/**\n * Replace the running binary with the latest tarball-shipped one. Works on\n * macOS + Linux (POSIX unlink-while-running); refuses on Windows. Returns\n * a result object instead of throwing — callers usually want to print\n * `result.reason` and fall back to a PM-driven update.\n */\nexport async function performInPlaceSelfUpdate(options: PerformInPlaceUpdateOptions): Promise<InPlaceUpdateResult> {\n if (process.platform === 'win32')\n return { status: 'refused', reason: 'in-place update not supported on Windows; use `zidane upgrade` instead.' }\n\n const binaryPath = options.binaryPath ?? process.execPath\n const resolved = normalizePath(binaryPath)\n const refused = options.refuseUnderPrefixes\n if (refused?.some(p => resolved.startsWith(p))) {\n return { status: 'refused', reason: `binary at ${resolved} sits under a managed prefix; use \\`zidane upgrade\\` to update via your package manager.` }\n }\n // Volta is the canonical \"managed prefix\" — refuse by default so users\n // don't end up with a stale catalog after the swap.\n if (resolved.includes(`${homedir()}${sep}.volta${sep}`)) {\n return { status: 'refused', reason: 'Volta-managed install detected. Use `zidane upgrade` (runs `volta install`) instead.' }\n }\n\n const fetcher = options.fetcher ?? globalThis.fetch\n if (typeof fetcher !== 'function')\n return { status: 'failed', reason: 'global fetch unavailable; rebuild with Node ≥ 18 or Bun.' }\n\n const channel = options.channel ?? 'latest'\n const registry = options.registry ?? 'https://registry.npmjs.org'\n const timeoutMs = options.timeoutMs ?? 30_000\n\n // 1. Resolve dist-tag → packument entry for the exact version + tarball URL.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(new Error('timeout')), timeoutMs)\n if (options.signal) {\n if (options.signal.aborted)\n controller.abort(options.signal.reason as Error)\n else\n options.signal.addEventListener('abort', () => controller.abort(options.signal!.reason as Error), { once: true })\n }\n\n try {\n const metaUrl = `${trimSlash(registry)}/${encodeURIComponent(options.packageName)}/${encodeURIComponent(channel)}`\n const metaRes = await fetcher(metaUrl, {\n headers: { 'accept': 'application/json', 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!metaRes.ok)\n return { status: 'failed', reason: `registry returned ${metaRes.status} for ${options.packageName}@${channel}` }\n const meta = await metaRes.json() as {\n version?: string\n dist?: { tarball?: string, integrity?: string, shasum?: string }\n }\n if (!meta.version || !meta.dist?.tarball)\n return { status: 'failed', reason: `registry payload missing version or tarball for ${options.packageName}@${channel}` }\n // Defense-in-depth: the npm registry serves over HTTPS, but a user\n // running with `--registry <custom>` might point at a less-trusted\n // mirror. Refuse to overwrite the live binary if the registry didn't\n // ship at least one integrity field for us to verify.\n if (!meta.dist.integrity && !meta.dist.shasum)\n return { status: 'failed', reason: `registry payload missing integrity / shasum for ${options.packageName}@${channel}; refusing to overwrite the binary.` }\n\n // 2. Stage adjacent to the target binary's directory so the final\n // `rename` stays on a single filesystem (avoids EXDEV on Linux where\n // /tmp is often tmpfs). Falls back to the OS tmpdir if the target's\n // parent is non-writable.\n const targetDir = dirname(binaryPath)\n let stagingDir: string\n try {\n stagingDir = mkdtempSync(join(targetDir, '.zidane-update-'))\n }\n catch {\n stagingDir = mkdtempSync(join(tmpdir(), 'zidane-update-'))\n }\n const tarballPath = join(stagingDir, 'package.tgz')\n\n try {\n const tgzRes = await fetcher(meta.dist.tarball, {\n headers: { 'user-agent': 'zidane-update-check/1' },\n signal: controller.signal,\n })\n if (!tgzRes.ok || !tgzRes.body)\n return { status: 'failed', reason: `tarball download failed: ${tgzRes.status}` }\n const digests = await streamToFile(tgzRes.body, tarballPath)\n\n // 3. Verify integrity. Prefer SRI (sha512) when present; fall back\n // to the legacy `shasum` (SHA-1) which every package on the registry\n // still carries. A mismatch is a hard refusal — never overwrite the\n // live binary with content we can't authenticate.\n const integrityCheck = verifyIntegrity(meta.dist, digests)\n if (!integrityCheck.ok)\n return { status: 'failed', reason: `integrity check failed: ${integrityCheck.reason}` }\n\n // 4. Extract just the binary entry.\n const tarballBinaryPath = options.tarballBinaryPath\n ?? `bin/${binaryNameFrom(binaryPath)}`\n const extractedPath = await extractEntryFromTarball(tarballPath, tarballBinaryPath, stagingDir)\n if (!extractedPath)\n return { status: 'failed', reason: `tarball did not contain ${tarballBinaryPath}` }\n\n if (options.dryRun) {\n return { status: 'success', installedVersion: meta.version, binaryPath: extractedPath }\n }\n\n // 5. Mark executable + atomic rename. POSIX permits unlink-while-running.\n chmodSync(extractedPath, 0o755)\n const targetForSwap = binaryPath\n try {\n renameSync(extractedPath, targetForSwap)\n }\n catch (err) {\n return { status: 'failed', reason: `failed to write ${targetForSwap}: ${err instanceof Error ? err.message : String(err)}` }\n }\n return { status: 'success', installedVersion: meta.version, binaryPath: targetForSwap }\n }\n finally {\n try { rmSync(stagingDir, { recursive: true, force: true }) }\n catch { /* best-effort */ }\n }\n }\n catch (err) {\n return { status: 'failed', reason: err instanceof Error ? err.message : String(err) }\n }\n finally {\n clearTimeout(timer)\n }\n}\n\nfunction binaryNameFrom(path: string): string {\n const last = path.split(/[\\\\/]/).at(-1) ?? ''\n return last.endsWith('.exe') ? last.slice(0, -4) : last\n}\n\ninterface StreamDigests {\n sha1: string\n sha512Base64: string\n size: number\n}\n\n/**\n * Stream `body` to `path` while computing integrity digests on the fly —\n * we never load the whole tarball into a Buffer just to hash it. Returns\n * SHA-1 (hex, for the legacy `dist.shasum`) and SHA-512 (base64, for SRI's\n * `sha512-<base64>`).\n */\nasync function streamToFile(body: ReadableStream<Uint8Array>, path: string): Promise<StreamDigests> {\n const file = createWriteStream(path)\n const sha1 = createHash('sha1')\n const sha512 = createHash('sha512')\n let size = 0\n const reader = body.getReader()\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done)\n break\n if (value && value.byteLength > 0) {\n sha1.update(value)\n sha512.update(value)\n size += value.byteLength\n await new Promise<void>((res, rej) => file.write(value, err => err ? rej(err) : res()))\n }\n }\n }\n finally {\n await new Promise<void>(res => file.end(() => res()))\n }\n return {\n sha1: sha1.digest('hex'),\n sha512Base64: sha512.digest('base64'),\n size,\n }\n}\n\n/**\n * Validate the downloaded tarball's digest against the registry's\n * `dist.integrity` (SRI: `sha512-<base64>` / `sha384-…` / `sha256-…`) or\n * the legacy `dist.shasum` (hex SHA-1). Either must match — we don't\n * accept \"neither provided\" because that's what `performInPlaceSelfUpdate`\n * already short-circuited above.\n */\nfunction verifyIntegrity(\n dist: { integrity?: string, shasum?: string },\n digests: StreamDigests,\n): { ok: true } | { ok: false, reason: string } {\n if (dist.integrity) {\n // SRI may carry multiple space-separated hashes — any match wins.\n const entries = dist.integrity.split(/\\s+/).filter(Boolean)\n for (const entry of entries) {\n const m = /^(sha(?:256|384|512))-(.+)$/.exec(entry)\n if (!m)\n continue\n const algo = m[1]!\n const expected = m[2]!\n if (algo === 'sha512' && digests.sha512Base64 === expected)\n return { ok: true }\n // Recompute on demand for the rarer algorithms. We only re-hash if\n // the registry shipped a non-512 SRI, which is unusual but legal.\n // For now, fail closed — refuse rather than skip the check.\n if (algo !== 'sha512')\n return { ok: false, reason: `unsupported SRI algorithm \\`${algo}\\`. Re-run via your package manager.` }\n }\n return { ok: false, reason: 'SRI mismatch: tarball does not match the registry\\'s `dist.integrity`.' }\n }\n if (dist.shasum) {\n if (digests.sha1 === dist.shasum.toLowerCase())\n return { ok: true }\n return { ok: false, reason: 'SHA-1 mismatch: tarball does not match the registry\\'s `dist.shasum`.' }\n }\n return { ok: false, reason: 'no integrity field shipped by the registry.' }\n}\n\n// ---------------------------------------------------------------------------\n// Minimal tar extractor — POSIX ustar format. Only handles the entries\n// produced by `npm pack`: filenames < 100 bytes, no PAX extended headers\n// (npm only emits them for paths > 100 chars; we already root the binary\n// at `bin/<name>` which stays well under that), no GNU long-name records.\n// ---------------------------------------------------------------------------\n\nasync function extractEntryFromTarball(\n tarballPath: string,\n entryRelPath: string,\n outDir: string,\n): Promise<string | null> {\n const matchSuffix = `/${entryRelPath}`\n const out = join(outDir, 'binary.bin')\n\n // Read whole tarball into memory — platform packages clock in around\n // 20-30 MB, well within the budget for a temp buffer.\n const chunks: Uint8Array[] = []\n const gunzip = createGunzip()\n const input = createReadStream(tarballPath)\n await new Promise<void>((res, rej) => {\n gunzip.on('data', (c: Buffer) => chunks.push(c))\n gunzip.on('end', () => res())\n gunzip.on('error', rej)\n input.on('error', rej)\n input.pipe(gunzip)\n })\n const tar = concatChunks(chunks)\n let offset = 0\n while (offset + 512 <= tar.length) {\n const header = tar.subarray(offset, offset + 512)\n if (header.every(b => b === 0))\n break\n const name = readNulString(header, 0, 100)\n const sizeOct = readNulString(header, 124, 12).trim()\n const size = sizeOct === '' ? 0 : Number.parseInt(sizeOct, 8)\n const dataStart = offset + 512\n const dataEnd = dataStart + size\n if (name && (name === entryRelPath || name.endsWith(matchSuffix))) {\n writeFileSync(out, tar.subarray(dataStart, dataEnd))\n return out\n }\n // 512-byte aligned padding.\n offset = dataEnd + ((512 - (size % 512)) % 512)\n }\n return null\n}\n\nfunction readNulString(buf: Uint8Array, start: number, len: number): string {\n const end = start + len\n let stop = end\n for (let i = start; i < end; i++) {\n if (buf[i] === 0) { stop = i; break }\n }\n return new TextDecoder('utf8').decode(buf.subarray(start, stop))\n}\n\nfunction concatChunks(chunks: Uint8Array[]): Uint8Array {\n let total = 0\n for (const c of chunks) total += c.byteLength\n const out = new Uint8Array(total)\n let offset = 0\n for (const c of chunks) {\n out.set(c, offset)\n offset += c.byteLength\n }\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Resolve the platform-specific tarball package\n//\n// Mirrors the resolution table in `tui/bin/zidane.mjs` — `darwin/arm64`,\n// `darwin/x64`, `linux/x64` (glibc), `linux/arm64` (glibc). Returns\n// `null` for unsupported targets so the caller can print a clear message.\n// ---------------------------------------------------------------------------\n\nexport interface PlatformTarballOptions {\n /** Override `process.platform`. */\n platform?: NodeJS.Platform\n /** Override `process.arch`. */\n arch?: NodeJS.Architecture | string\n /** Override the libc check (returned from `detectLibc`). */\n libc?: 'glibc' | 'musl'\n /** Prefix joined to the platform key. Defaults to `'zidane-tui-'`. */\n prefix?: string\n}\n\nexport function resolvePlatformPackage(opts: PlatformTarballOptions = {}): string | null {\n const platform = opts.platform ?? process.platform\n const arch = opts.arch ?? process.arch\n const libc = opts.libc ?? detectLibc()\n const prefix = opts.prefix ?? 'zidane-tui-'\n if (platform === 'darwin') {\n if (arch === 'arm64')\n return `${prefix}darwin-arm64`\n if (arch === 'x64')\n return `${prefix}darwin-x64`\n return null\n }\n if (platform === 'linux') {\n if (libc !== 'glibc')\n return null\n if (arch === 'arm64')\n return `${prefix}linux-arm64`\n if (arch === 'x64')\n return `${prefix}linux-x64`\n return null\n }\n return null\n}\n\n/** Mirrors `tui/bin/zidane.mjs#detectLibc`. */\nexport function detectLibc(): 'glibc' | 'musl' {\n if (process.platform !== 'linux')\n return 'glibc'\n try {\n // eslint-disable-next-line ts/no-require-imports\n const { execSync } = require('node:child_process') as typeof import('node:child_process')\n const out = execSync('ldd --version 2>&1', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) as string\n return out.toLowerCase().includes('musl') ? 'musl' : 'glibc'\n }\n catch {\n return 'musl'\n }\n}\n\n// ---------------------------------------------------------------------------\n// `_` re-exports for tests that don't want to mock through the module\n// boundary. Not part of the public surface; keep names underscored.\n// ---------------------------------------------------------------------------\n\nexport const _internal = {\n cacheFilePath,\n readCache,\n writeCache,\n binaryNameFrom,\n extractEntryFromTarball,\n}\n","// ---------------------------------------------------------------------------\n// `useUpdateCheck` — renderer-agnostic React hook driving the auto-update\n// footer chip + any banner a host wants to wire.\n//\n// Lives in `zidane/chat` (not `zidane/tui`) so a future GUI shell can\n// consume the same status. Pure React + the renderer-agnostic core in\n// `./auto-update`; no OpenTUI imports.\n// ---------------------------------------------------------------------------\n\nimport type { UpdateStatus } from './auto-update'\nimport type { Hint } from './hints'\nimport { useEffect, useRef, useState } from 'react'\nimport { checkForUpdate } from './auto-update'\n\nexport interface UseUpdateCheckOptions {\n /** npm package name. e.g. `'zidane-tui'`. */\n packageName: string\n /** Inlined at build time. Skips the check if empty/falsy. */\n currentVersion: string\n /**\n * Master enable flag — usually `Settings.checkForUpdates`. When `false`\n * the hook short-circuits without scheduling any work AND clears the\n * last-known status so a stale chip doesn't linger after the user\n * disabled the feature.\n */\n enabled: boolean\n /** Directory under which the on-disk cache lives. Usually `paths.userDir`. */\n cacheDir?: string\n registry?: string\n channel?: string\n /**\n * Cache lifetime for the read-through. **Defaults to `0`** (always\n * fetch fresh) — the TUI chip is the user's source of truth for \"what\n * version of zidane is on npm right now\"; we'd rather pay one HTTPS\n * call per session than tell them yesterday's news for up to 24 hours.\n *\n * The on-disk cache is still WRITTEN with each fresh result (so\n * `zidane upgrade` and other tooling reads benefit) and still READ to\n * feed the registry's ETag (so the cold path can short-circuit on 304\n * — meaningful bandwidth win when nothing changed). Pass a positive\n * value (e.g. `3_600_000` for 1 h) to enable read-through caching for\n * a GUI shell that mounts the hook in many places.\n */\n cacheTtlMs?: number\n /**\n * Wall-clock delay before the first check fires. Defaults to 1 500 ms so\n * we never compete with the boot path's first paint. The check itself\n * is async, so this only affects WHEN the chip first appears — never\n * blocks anything else.\n */\n delayMs?: number\n /** Injected for tests. */\n now?: () => number\n /** Injected for tests. */\n fetcher?: typeof fetch\n}\n\n/**\n * Returns the current update status. The check fires:\n * - Once per mount (every TUI launch).\n * - Every time `enabled` flips from `false` → `true` (user re-enabling\n * the setting expects an immediate refresh).\n *\n * The check is async and deferred by `delayMs` so it never competes with\n * first paint. Result is `null` until the first check resolves; flipping\n * `enabled` to `false` resets to `null` so any chip rendered against\n * `hasUpdate` disappears immediately.\n *\n * The hook owns its own AbortController so an unmount cancels any in-flight\n * registry request cleanly.\n */\nexport function useUpdateCheck(options: UseUpdateCheckOptions): UpdateStatus | null {\n const [status, setStatus] = useState<UpdateStatus | null>(null)\n // Ref-mirror so the effect doesn't re-run on every keystroke when the\n // host wraps the result into derived hint state.\n const optsRef = useRef(options)\n optsRef.current = options\n\n useEffect(() => {\n if (!options.enabled || !options.currentVersion) {\n // Disabling the setting must hide a chip that was already painted\n // — otherwise the user sees a stale `↑ vX.Y.Z` linger until the\n // next launch. React bails on identical-state updates so the\n // initial-mount case (status already null) costs nothing.\n setStatus(null)\n return\n }\n const controller = new AbortController()\n const timer = setTimeout(() => {\n void checkForUpdate({\n packageName: optsRef.current.packageName,\n currentVersion: optsRef.current.currentVersion,\n cacheDir: optsRef.current.cacheDir,\n registry: optsRef.current.registry,\n channel: optsRef.current.channel,\n // Default to TTL=0 (always fetch fresh). See `cacheTtlMs` docs.\n cacheTtlMs: optsRef.current.cacheTtlMs ?? 0,\n signal: controller.signal,\n now: optsRef.current.now,\n fetcher: optsRef.current.fetcher,\n }).then((s) => {\n if (!controller.signal.aborted)\n setStatus(s)\n })\n }, options.delayMs ?? 1500)\n return () => {\n clearTimeout(timer)\n controller.abort()\n }\n // Re-run when the master enable / package / version flip. Other\n // option changes stay inside `optsRef.current`; re-running on every\n // option mutation would cause render storms when the host wraps\n // the result into derived hint state.\n }, [options.enabled, options.packageName, options.currentVersion])\n\n return status\n}\n\n// ---------------------------------------------------------------------------\n// Footer chip helper — pure function so any renderer (TUI Footer, future\n// GUI status bar) can compose against the same `Hint`. The chip is\n// suppressed when there's no update; callers spread the result into their\n// hint array unconditionally.\n//\n// Color contract: the chip rides whatever accent the host passes (typically\n// `theme.colors.accent`). Renderers that want a \"fresh check\" badge can\n// inspect `status.source === 'fresh'` themselves — this helper sticks to\n// the one-line discoverability surface.\n// ---------------------------------------------------------------------------\n\nexport interface BuildUpdateHintOptions {\n /** Foreground color for the `↑` glyph + version label. */\n color: string\n /**\n * When true (default), the chip renders the literal version (`↑ v5.6.0`).\n * When false, just the glyph + `update` label — useful when the row is\n * tight and the version would push higher-priority chips off the right.\n */\n showVersion?: boolean\n /** Override the leading glyph. Defaults to `'↑'`. */\n glyph?: string\n}\n\nexport function buildUpdateHint(status: UpdateStatus | null, options: BuildUpdateHintOptions): Hint | null {\n if (!status?.hasUpdate || !status.latest)\n return null\n const label = options.showVersion === false\n ? 'update'\n : `v${stripVPrefix(status.latest)}`\n return {\n key: options.glyph ?? '↑',\n keyColor: options.color,\n label,\n labelColor: options.color,\n }\n}\n\nfunction stripVPrefix(v: string): string {\n return v.startsWith('v') ? v.slice(1) : v\n}\n","// ---------------------------------------------------------------------------\n// Boot-time wall-clock profiler.\n//\n// Sprinkle `bootTick('<label>')` at strategic points along the startup\n// chain (cli.ts → runTui → setupTreeSitter → resolveConfig →\n// createCliRenderer → React mount → discovery effects → session\n// resume). When `ZIDANE_BOOT_PROFILE=1` is set, each call emits one\n// line on stderr with the delta since the previous tick and the total\n// since module load:\n//\n// $ ZIDANE_BOOT_PROFILE=1 zidane\n// [boot] +0.0ms / total 0.0ms / cli:enter\n// [boot] +12.3ms / total 12.3ms / cli:after-bundled-treesitter-setup\n// [boot] +47.8ms / total 60.1ms / runTui:after-setupTreeSitter\n// ...\n//\n// Renderer-agnostic on purpose — lives in `zidane/chat` so a future GUI\n// shell can wire it into its own boot path with the same env var and\n// the same output format. Writes to stderr, which OpenTUI's renderer\n// leaves alone (stdout is the frame buffer surface).\n// ---------------------------------------------------------------------------\n\nconst enabled = !!process.env.ZIDANE_BOOT_PROFILE\n/**\n * High-resolution origin for the running profile. Captured at module\n * load time so the first {@link bootTick} call inside the same process\n * reflects \"time since the boot profiler was first reached\", which for\n * the standard `cli.ts → runTui` path is effectively \"time since the\n * TUI binary started executing user code\".\n */\nconst start = performance.now()\nlet last = start\n\n/**\n * Record a checkpoint. No-op unless `ZIDANE_BOOT_PROFILE` is truthy in\n * the environment.\n *\n * The leading delta is the time since the PREVIOUS tick (so a long\n * delta highlights the immediately-preceding work); the trailing total\n * is the time since the profiler started. Both round to one decimal of\n * a millisecond.\n */\nexport function bootTick(label: string): void {\n if (!enabled)\n return\n const now = performance.now()\n const delta = now - last\n const total = now - start\n last = now\n // 10-char label pad keeps the alignment readable in a terminal even\n // when delta or total widens; longer labels just push the line out\n // slightly without breaking the format.\n process.stderr.write(\n `[boot] +${delta.toFixed(1).padStart(7)}ms / total ${total.toFixed(1).padStart(7)}ms / ${label}\\n`,\n )\n}\n\n/**\n * Returns `true` when `ZIDANE_BOOT_PROFILE` is set. Useful for guarding\n * heavier instrumentation (e.g. wrapping a costly call in a span) that\n * you don't want paying its own cost in the default path.\n */\nexport function bootProfileEnabled(): boolean {\n return enabled\n}\n","/**\n * Best-effort cross-platform browser open.\n *\n * macOS uses `open`, Linux uses `xdg-open`, Windows uses `start`. Failures\n * are swallowed — both callers (AI-provider OAuth + MCP-server OAuth) show\n * the URL on-screen for manual click anyway, and the underlying flows have\n * their own progress/cancel paths.\n *\n * Uses `spawn` (not `exec`) so the URL is passed as an argv element rather\n * than interpolated into a shell command — URLs containing `&`, `?`, `\"`,\n * or other shell metacharacters don't need quoting.\n *\n * Detached + unref so the spawned helper outlives the parent: the user\n * can finish authorizing in the browser even if the TUI is later\n * suspended (Ctrl-Z) or killed.\n */\n\nimport { spawn } from 'node:child_process'\n\nexport function tryOpenBrowser(url: string): void {\n const [cmd, ...args] = (() => {\n if (process.platform === 'darwin')\n return ['open', url]\n if (process.platform === 'win32')\n return ['cmd', '/c', 'start', '', url]\n return ['xdg-open', url]\n })()\n try {\n const child = spawn(cmd, args, { stdio: 'ignore', detached: true })\n child.on('error', () => {}) // ENOENT etc. — silently fall through\n child.unref()\n }\n catch {\n // spawn() itself failed (e.g. invalid binary path). Swallow.\n }\n}\n","// ---------------------------------------------------------------------------\n// HSL color gradient helpers — shared between the Crush throbber (cycling\n// per-frame paint) and the title overlay (static per-character paint).\n//\n// Renderer-agnostic on purpose: a future GUI shell wants the same ramp\n// math against the same hex strings the theme palette already stores, so\n// this lives in `zidane/chat` next to the theme definition rather than\n// inside the TUI's OpenTUI layer.\n//\n// We blend in HSL with shortest-path hue interpolation rather than\n// straight sRGB lerp because saturated palette pairs (e.g. Charple →\n// Dolly = purple → magenta) would muddy through grey when interpolated\n// componentwise in RGB. HSL keeps the path along the hue ring, so the\n// gradient stays vivid.\n// ---------------------------------------------------------------------------\n\n/** Parse `#rrggbb` (case-insensitive) into `[r, g, b]` 0–255 integers. */\nfunction parseHex(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n return [\n Number.parseInt(h.slice(0, 2), 16),\n Number.parseInt(h.slice(2, 4), 16),\n Number.parseInt(h.slice(4, 6), 16),\n ]\n}\n\n/** Convert sRGB 0–255 → HSL 0–1. */\nfunction rgbToHsl(r: number, g: number, b: number): [number, number, number] {\n r /= 255\n g /= 255\n b /= 255\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const l = (max + min) / 2\n if (max === min)\n return [0, 0, l]\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n let h: number\n if (max === r)\n h = (g - b) / d + (g < b ? 6 : 0)\n else if (max === g)\n h = (b - r) / d + 2\n else\n h = (r - g) / d + 4\n return [h / 6, s, l]\n}\n\n/** Convert HSL 0–1 → sRGB 0–255. Standard piecewise formula. */\nfunction hslToRgb(h: number, s: number, l: number): [number, number, number] {\n if (s === 0)\n return [l * 255, l * 255, l * 255]\n const hue2rgb = (p: number, q: number, t: number) => {\n if (t < 0)\n t += 1\n if (t > 1)\n t -= 1\n if (t < 1 / 6)\n return p + (q - p) * 6 * t\n if (t < 1 / 2)\n return q\n if (t < 2 / 3)\n return p + (q - p) * (2 / 3 - t) * 6\n return p\n }\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s\n const p = 2 * l - q\n return [\n hue2rgb(p, q, h + 1 / 3) * 255,\n hue2rgb(p, q, h) * 255,\n hue2rgb(p, q, h - 1 / 3) * 255,\n ]\n}\n\nfunction toHex(rgb: readonly [number, number, number]): string {\n const pad = (v: number) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0')\n return `#${pad(rgb[0])}${pad(rgb[1])}${pad(rgb[2])}`\n}\n\n/**\n * Blend two hex colors in HSL space with shortest-path hue interpolation.\n * `t` ∈ [0, 1]; `t=0` returns `from`, `t=1` returns `to`.\n */\nexport function blendHsl(from: string, to: string, t: number): string {\n const [r1, g1, b1] = parseHex(from)\n const [r2, g2, b2] = parseHex(to)\n const [h1, s1, l1] = rgbToHsl(r1, g1, b1)\n const [h2, s2, l2] = rgbToHsl(r2, g2, b2)\n // Shortest path around the hue wheel — purple→pink shouldn't detour\n // through cyan just because the raw subtraction is positive.\n let dh = h2 - h1\n if (dh > 0.5)\n dh -= 1\n else if (dh < -0.5)\n dh += 1\n const h = (h1 + dh * t + 1) % 1\n const s = s1 + (s2 - s1) * t\n const l = l1 + (l2 - l1) * t\n return toHex(hslToRgb(h, s, l))\n}\n\n/**\n * Static gradient ramp of length `n` going from `from` (index 0) to\n * `to` (index n-1) in HSL space. For the cycling A→B→A→B ramp the\n * throbber uses, see `buildCycleRamp` in `src/tui/crush-throbber.tsx`.\n */\nexport function buildLinearRamp(from: string, to: string, n: number): string[] {\n if (n <= 0)\n return []\n if (n === 1)\n return [blendHsl(from, to, 0.5)]\n const ramp: string[] = []\n for (let i = 0; i < n; i++)\n ramp.push(blendHsl(from, to, i / (n - 1)))\n return ramp\n}\n","/**\n * Prompt autocompletion framework.\n *\n * Renderer-agnostic. Providers plug in by registering a `trigger` character\n * (e.g. `/` for skills, `@` for files) and exposing two operations:\n *\n * 1. `suggest(query)` — return ranked items for the live query.\n * 2. `parseReferences(text)` — find all references to the provider's\n * items in arbitrary text. Used to highlight in-prompt mentions and\n * drive submit-time side effects (activate the skill, attach the\n * file, …).\n *\n * The TUI consumes `useCompletion()` to drive a popover above the textarea.\n * A future GUI consumes the same hook to drive a dropdown. The popup\n * component itself only reads `label` + `description` on each item, so\n * provider-specific typing (`TItem`) stays at the registration boundary\n * and never leaks into the renderer.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * A discoverable, selectable thing — one row in the autocomplete popover.\n * `TItem` is provider-specific; consumers can inspect `data` when they\n * need the originating payload (e.g. the full `SkillConfig` for tooltips).\n */\nexport interface CompletionItem<TItem = unknown> {\n /** Stable identifier within the provider. Used as React key + selection equality. */\n id: string\n /** User-visible primary string (\"research\"). */\n label: string\n /** Optional one-line secondary string (\"In-depth research with citations\"). */\n description?: string\n /**\n * Text that replaces the active span (trigger + query) on commit. Usually\n * `${trigger}${label}` with a trailing space so the cursor lands ready\n * for the next token.\n */\n insertText: string\n /** Original provider-specific payload. */\n data: TItem\n}\n\n/**\n * A reference to a provider item inside arbitrary prompt text. Producers:\n * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,\n * the run flow for \"activate every referenced skill before agent.run()\".\n *\n * Spans are half-open `[start, end)` codepoint offsets into the source\n * string. Overlapping spans from the same or different providers are\n * caller-resolved — the helpers below ship a \"first wins\" merger.\n */\nexport interface CompletionReference<TItem = unknown> {\n providerId: string\n start: number\n end: number\n itemId: string\n data: TItem\n}\n\n/**\n * Provider contract. Implementations decide their own ranking, fuzzy-match\n * rules, async loading behavior, and reference grammar.\n */\nexport interface CompletionProvider<TItem = unknown> {\n /** Stable id used for tagging references + React keys. */\n id: string\n /**\n * Single character that activates this provider. The engine considers a\n * trigger \"active\" when it appears at the start of the buffer or\n * immediately after whitespace, and is not closed by whitespace before\n * the cursor (so a trigger plus query is a contiguous token).\n */\n trigger: string\n /** Human-readable name. Reserved for future multi-provider popover headers. */\n label: string\n /**\n * Returns items for the active `query` (the text between the trigger and\n * the cursor, excluding the trigger itself). Synchronous return is the\n * common case; promises let providers paginate or hit a backend.\n *\n * `signal` is aborted when the query changes or the popover closes —\n * use it to cancel in-flight network calls.\n */\n suggest: (\n query: string,\n ctx: CompletionContext,\n signal: AbortSignal,\n ) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>\n /**\n * Find every reference to this provider's items in `text`. Pure: must not\n * mutate ctx. The TUI calls this on every keystroke for highlighting, so\n * keep it cheap (linear scan, no I/O).\n */\n parseReferences: (\n text: string,\n ctx: CompletionContext,\n ) => CompletionReference<TItem>[]\n}\n\n/**\n * Read-only view of the active prompt buffer + cursor passed to provider\n * callbacks. Kept minimal so providers stay portable across renderers.\n */\nexport interface CompletionContext {\n /** Full prompt text. */\n text: string\n /** Codepoint offset of the cursor (0-based). */\n cursor: number\n}\n\n/**\n * Identified active trigger span. Returned by `findActiveTrigger` so\n * callers can show the popover, query the provider, and on commit replace\n * the span with the selected item's `insertText`.\n */\nexport interface ActiveTrigger<TItem = unknown> {\n provider: CompletionProvider<TItem>\n /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */\n query: string\n /** `[start, end)` — span covered by the trigger + query in the source. */\n span: { start: number, end: number }\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers — exported for unit tests + alternate renderers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the provider trigger active at `cursor`, or `null` when none fits.\n *\n * Rules:\n * - The trigger character must sit at position 0 of the buffer OR be\n * preceded by whitespace. This prevents `http://` from triggering the\n * `/`-bound skills provider mid-URL.\n * - The cursor must be at or past the trigger position.\n * - Nothing between the trigger and the cursor may be whitespace (the\n * query is one contiguous token).\n * - The query length is bounded — `maxQueryLength` defaults to 64 — so\n * a runaway buffer scan can't pin the renderer.\n */\nexport function findActiveTrigger<TItem>(\n text: string,\n cursor: number,\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): ActiveTrigger<TItem> | null {\n if (providers.length === 0)\n return null\n const max = options.maxQueryLength ?? 64\n const safeCursor = Math.max(0, Math.min(cursor, text.length))\n // Whitespace test mirrors regex `\\s` so this surface agrees with the\n // built-in providers' `parseReferences` regex (which uses `\\s`). Without\n // this, pasting `text\\u00a0@README.md` would parse as a ref but the popover\n // wouldn't reopen on retype because NBSP wasn't recognized as a boundary.\n const isWhitespace = (ch: string | undefined) => ch === undefined ? false : /\\s/.test(ch)\n // Walk backward from the cursor to the nearest trigger char. Bail when\n // we hit whitespace (query token closed) or exceed `max`.\n for (let i = safeCursor - 1; i >= 0 && safeCursor - i <= max + 1; i--) {\n const ch = text[i]\n if (isWhitespace(ch))\n return null\n const provider = providers.find(p => p.trigger === ch)\n if (!provider)\n continue\n // Trigger must be at start of buffer or follow whitespace.\n const before = i > 0 ? text[i - 1] : ''\n if (before !== '' && !isWhitespace(before))\n continue\n const query = text.slice(i + 1, safeCursor)\n return {\n provider,\n query,\n span: { start: i, end: safeCursor },\n }\n }\n return null\n}\n\n/**\n * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the\n * mutated text and the new cursor position (end of insertion).\n */\nexport function applyInsert(\n text: string,\n span: { start: number, end: number },\n insertText: string,\n): { text: string, cursor: number } {\n const next = text.slice(0, span.start) + insertText + text.slice(span.end)\n return { text: next, cursor: span.start + insertText.length }\n}\n\n/**\n * Merge reference lists from multiple providers into one ordered list with\n * earlier-start-wins disambiguation when spans overlap. Ties broken by\n * insertion order. Spans are sorted ascending so renderers can walk them\n * sequentially with a cursor through the source string.\n */\nexport function mergeReferences<TItem>(\n refs: readonly CompletionReference<TItem>[],\n): CompletionReference<TItem>[] {\n const sorted = [...refs].sort((a, b) => a.start - b.start)\n const merged: CompletionReference<TItem>[] = []\n let lastEnd = -1\n for (const ref of sorted) {\n if (ref.start < lastEnd)\n continue\n merged.push(ref)\n lastEnd = ref.end\n }\n return merged\n}\n\n/**\n * Collect every provider's references in one pass. Convenience wrapper —\n * the TUI textarea component calls this on every keystroke to highlight\n * in-prompt mentions.\n */\nexport function collectReferences<TItem>(\n text: string,\n providers: readonly CompletionProvider<TItem>[],\n cursor = text.length,\n): CompletionReference<TItem>[] {\n const ctx: CompletionContext = { text, cursor }\n const refs: CompletionReference<TItem>[] = []\n for (const p of providers) {\n for (const ref of p.parseReferences(text, ctx))\n refs.push(ref)\n }\n return mergeReferences(refs)\n}\n\n/**\n * Shared sentinel returned by `useCompletion` when no references are\n * present in the buffer. Stable identity lets consumers' `useEffect`\n * dep arrays bail out on chip-less keystrokes — see `useChipHighlights`.\n */\nconst EMPTY_REFS: readonly CompletionReference<unknown>[] = Object.freeze([])\n\n// ---------------------------------------------------------------------------\n// React hook\n// ---------------------------------------------------------------------------\n\n/** State surface returned by `useCompletion()`. */\nexport interface CompletionState<TItem = unknown> {\n /** Active trigger, or null when the cursor doesn't sit in a completable span. */\n active: ActiveTrigger<TItem> | null\n /** Items for the active query. Empty when no trigger is active. */\n items: readonly CompletionItem<TItem>[]\n /** True while the provider's `suggest` promise is pending. */\n loading: boolean\n /** Index of the highlighted item inside `items`. */\n selectedIndex: number\n /** Move the highlight. Wraps at the ends. No-op when `items` is empty. */\n selectNext: () => void\n selectPrev: () => void\n /**\n * Commit the highlighted item. Returns the new text + cursor pair, or\n * `null` when there's nothing to commit (no active trigger, empty list).\n */\n commit: () => { text: string, cursor: number } | null\n /** Force-close the popover until the user types again. */\n dismiss: () => void\n /** All references in the current buffer, regardless of provider. */\n references: readonly CompletionReference<TItem>[]\n}\n\n/**\n * Drive a prompt-completion popover from React state.\n *\n * Inputs are pull-style — the caller owns `text` + `cursor` (typically\n * mirroring an `OpenTUI TextareaRenderable` or a `<textarea>` element) and\n * passes them in each render. Providers are matched by their `trigger`\n * character; the engine picks at most one active provider per cursor\n * position.\n *\n * Asynchronous suggest calls are aborted when the query changes or the\n * popover closes, so providers that hit a backend don't leak.\n */\nexport function useCompletion<TItem = unknown>(\n input: { text: string, cursor: number },\n providers: readonly CompletionProvider<TItem>[],\n options: { maxQueryLength?: number } = {},\n): CompletionState<TItem> {\n const { text, cursor } = input\n // Destructure primitives up front so the `useMemo` dep array reads them\n // by value (not by `options` reference, which is a fresh object literal\n // on every call from most hosts).\n const { maxQueryLength } = options\n // `dismissed` is keyed by the trigger's `span.start` — the buffer\n // position of the `@` / `/` glyph that opened the popover. Staying\n // dismissed *while the user keeps typing into the same trigger span*\n // is the whole point of Esc; a stricter `${text.length}:${cursor}`\n // key re-opened on the very next keystroke, which made the \"esc\n // dismiss\" hint a lie. The effect below resets this when the trigger\n // goes away entirely (user backspaced past `@`) so retyping `@` at\n // the same position works.\n const [dismissedTriggerStart, setDismissedTriggerStart] = useState<number | null>(null)\n\n const rawTrigger = useMemo(\n () => findActiveTrigger(text, cursor, providers, { maxQueryLength }),\n [text, cursor, providers, maxQueryLength],\n )\n\n const active = useMemo(() => {\n if (!rawTrigger)\n return null\n if (rawTrigger.span.start === dismissedTriggerStart)\n return null\n return rawTrigger\n }, [rawTrigger, dismissedTriggerStart])\n\n // Forget the dismissal once the trigger disappears — otherwise a\n // fresh `@` typed at the same byte position would inherit the prior\n // dismiss and the popover would never come back without scrolling\n // the trigger elsewhere first.\n useEffect(() => {\n if (!rawTrigger)\n setDismissedTriggerStart(null)\n }, [rawTrigger])\n\n const [items, setItems] = useState<readonly CompletionItem<TItem>[]>([])\n const [loading, setLoading] = useState(false)\n const [selectedIndex, setSelectedIndex] = useState(0)\n\n // Cancel any in-flight suggest when the query changes / popover closes.\n const abortRef = useRef<AbortController | null>(null)\n\n useEffect(() => {\n abortRef.current?.abort()\n if (!active) {\n setItems([])\n setLoading(false)\n setSelectedIndex(0)\n return\n }\n const controller = new AbortController()\n abortRef.current = controller\n const ctx: CompletionContext = { text, cursor }\n let cancelled = false\n const out = active.provider.suggest(active.query, ctx, controller.signal)\n if (Array.isArray(out)) {\n setItems(out)\n setSelectedIndex(0)\n setLoading(false)\n return\n }\n setLoading(true)\n void out.then(\n (next) => {\n if (cancelled)\n return\n setItems(next)\n setSelectedIndex(0)\n setLoading(false)\n },\n () => {\n if (cancelled)\n return\n setItems([])\n setLoading(false)\n },\n )\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [active, text, cursor])\n\n // References don't depend on `cursor` — both built-in providers ignore\n // `ctx.cursor` in `parseReferences`, and the consumers (chip highlighting,\n // submit-time activation) only care about positions of references, not\n // about where the caret happens to sit. Dropping `cursor` from the dep\n // array avoids re-walking every provider regex on each arrow-key press.\n // `cursor` is still forwarded so a future provider that wants the cursor\n // can opt in — at the cost of choosing its own memoization strategy.\n //\n // Empty results collapse to a shared `EMPTY_REFS` constant so the prompt\n // textarea's `useChipHighlights` effect doesn't fire on every keystroke\n // of a chip-less buffer (each keystroke would otherwise allocate a fresh\n // `[]`, change identity, and trigger a `clearAllHighlights` repaint).\n const references = useMemo(\n () => {\n const refs = collectReferences(text, providers, cursor)\n return refs.length === 0 ? (EMPTY_REFS as readonly CompletionReference<TItem>[]) : refs\n },\n [text, providers],\n )\n\n const selectNext = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i + 1) % items.length))\n }, [items.length])\n const selectPrev = useCallback(() => {\n setSelectedIndex(i => (items.length === 0 ? 0 : (i - 1 + items.length) % items.length))\n }, [items.length])\n const dismiss = useCallback(() => {\n // Pull from `rawTrigger`, not `active`, so dismiss-while-dismissed\n // (a no-op now but reasonable to support) keeps the right anchor.\n if (rawTrigger)\n setDismissedTriggerStart(rawTrigger.span.start)\n }, [rawTrigger])\n const commit = useCallback((): { text: string, cursor: number } | null => {\n if (!active || items.length === 0)\n return null\n const item = items[Math.min(selectedIndex, items.length - 1)]\n return applyInsert(text, active.span, item.insertText)\n }, [active, items, selectedIndex, text])\n\n // Memoize the returned bag so consumers placing it in a `useEffect`\n // dep array don't observe a fresh reference every render. Same pattern\n // (and rationale) as `useStreamBuffer.return` — see streaming.ts.\n return useMemo<CompletionState<TItem>>(\n () => ({ active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references }),\n [active, items, loading, selectedIndex, selectNext, selectPrev, commit, dismiss, references],\n )\n}\n","/**\n * Completion provider that exposes the discovered project file catalog as\n * `@`-prefixed mentions. Trigger: `@`. Filtering: substring against\n * `name + path`, case-insensitive. Inserted text is `@{path} ` so the\n * cursor lands ready for the next token.\n *\n * Provider is pure UI — file *attachment* (reading bytes, injecting into\n * the prompt) happens at the submission boundary, where the host walks\n * `state.references` and decides what to do with each `FileEntry`.\n */\n\nimport type {\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion'\nimport type { FileEntry } from './files-discovery'\n\n/** Trigger character — `@` is the conventional file-mention prefix in chat UIs. */\nexport const FILES_TRIGGER = '@'\n\n/** Cap on returned items. Keeps the popover compact + render-cheap. */\nconst DEFAULT_RESULT_LIMIT = 50\n\n/** Identity formatter — preserves the discovery path verbatim. */\nconst IDENTITY_FORMAT = (entry: FileEntry): string => entry.path\n\n/**\n * Rank-and-slice a file catalog against a query. Hoisted to a module\n * helper so both the sync and async branches of `suggest()` share one\n * implementation (the async branch hits this once the lazy directory\n * walk resolves; sync branch hits it on every keystroke thereafter).\n *\n * `formatPath` rewrites the catalog's project-root-relative path into\n * the form the host wants emitted into the prompt (typically CWD-rel\n * or absolute when launched from a project subdir — see\n * `formatPathForCwd` in `path-display.ts`). Falls back to the raw\n * `entry.path` when omitted.\n */\nfunction scoreFiles(\n catalog: readonly FileEntry[],\n query: string,\n limit: number,\n formatPath: (entry: FileEntry) => string,\n): CompletionItem<FileEntry>[] {\n const q = query.trim().toLowerCase()\n const scored: { entry: FileEntry, display: string, rank: number }[] = []\n for (const file of catalog) {\n const display = formatPath(file)\n const name = file.name.toLowerCase()\n const path = display.toLowerCase()\n if (q.length === 0) {\n scored.push({ entry: file, display, rank: 4 })\n continue\n }\n if (name === q) {\n scored.push({ entry: file, display, rank: 0 })\n continue\n }\n if (name.startsWith(q)) {\n scored.push({ entry: file, display, rank: 1 })\n continue\n }\n if (name.includes(q)) {\n scored.push({ entry: file, display, rank: 2 })\n continue\n }\n if (path.includes(q)) {\n scored.push({ entry: file, display, rank: 3 })\n continue\n }\n }\n scored.sort((a, b) => {\n if (a.rank !== b.rank)\n return a.rank - b.rank\n return a.display.localeCompare(b.display)\n })\n return scored.slice(0, limit).map<CompletionItem<FileEntry>>(({ entry, display }) => ({\n id: display,\n label: entry.name,\n // Description is the parent directory — gives the user disambiguation\n // signal when multiple files share a basename (`index.ts` × 12).\n //\n // Uses the catalog's RAW project-root-relative path, NOT the\n // cwd-formatted `display`. The cwd-formatted path flips a file's\n // visible shape based on whether it sits above or below\n // `process.cwd()` (absolute parent vs cwd-relative parent vs\n // empty for \"right here\"), which produces a popover that looks\n // half-broken when the same basename exists in multiple project\n // locations: `EDIT_THIS.md /Users/…/zidane` next to plain\n // `EDIT_THIS.md` for a sibling under the cwd. Anchoring the\n // description on `file.path` keeps the disambiguation column\n // stable + project-shaped regardless of where the user launched\n // the TUI from; the inserted text still uses `display` so the\n // tools can resolve it.\n description: parentDir(entry.path),\n insertText: `${FILES_TRIGGER}${display} `,\n data: entry,\n }))\n}\n\n/**\n * Build an `@`-prefixed files completion provider against a *live* catalog.\n *\n * The factory captures a getter so the catalog can be re-scanned (cwd\n * change, manual refresh) without re-instantiating the provider — the\n * App keeps one provider for the lifetime of the prompt block and just\n * mutates the underlying state.\n *\n * `limit` caps the result list so the popover stays bounded on huge\n * monorepos. Filtering is substring on `path` + `name`, case-insensitive;\n * ranking prefers (in order): exact name match, name prefix, name\n * substring, path substring, alphabetical.\n */\nexport function createFilesCompletionProvider(opts: {\n /** Live file catalog. Re-evaluated per call so refreshes take effect immediately. */\n getCatalog: () => readonly FileEntry[]\n /**\n * Optional hook called the first time the host needs the catalog —\n * e.g. when the user opens the `@` popover. Hosts wire this to a\n * lazy directory walk so boot doesn't pay for it in monorepos\n * where the popover may never open. Idempotent contract: callers\n * may invoke it on every `suggest`; the host caches internally.\n *\n * When provided, `suggest()` returns a Promise on the very first\n * invocation if the catalog is still empty — the popover's loading\n * state surfaces while the walk completes; subsequent calls are\n * sync because `getCatalog()` then returns the populated state.\n */\n ensureCatalog?: () => Promise<readonly FileEntry[]>\n /** Max items returned to the popover. Default: 50. */\n limit?: number\n /**\n * Rewrite the catalog's project-root-relative path into the form\n * inserted into the prompt + matched by `parseReferences`. Wire this\n * to `formatPathForCwd` so paths emitted into the buffer line up\n * with the agent's CWD-resolving tools when the TUI launches from a\n * project subdirectory. Default: identity.\n *\n * Stable identity expected — `parseReferences` calls it once per\n * catalog entry per keystroke for the highlight pass; pure\n * pure-function shape keeps the popover responsive on huge repos.\n */\n formatPath?: (entry: FileEntry) => string\n}): CompletionProvider<FileEntry> {\n const limit = opts.limit ?? DEFAULT_RESULT_LIMIT\n const formatPath = opts.formatPath ?? IDENTITY_FORMAT\n return {\n id: 'files',\n trigger: FILES_TRIGGER,\n label: 'Files',\n suggest(query) {\n // Kick off lazy discovery (no-op on subsequent calls). If the\n // host wired a thunk and the catalog isn't ready yet, hand the\n // popover a Promise — it already supports `loading` and refreshes\n // `items` when the promise resolves.\n if (opts.ensureCatalog) {\n const pending = opts.ensureCatalog()\n const current = opts.getCatalog()\n if (current.length === 0) {\n return pending.then(loaded => scoreFiles(loaded, query, limit, formatPath))\n }\n }\n return scoreFiles(opts.getCatalog(), query, limit, formatPath)\n },\n parseReferences(text, _ctx: CompletionContext) {\n const catalog = opts.getCatalog()\n if (catalog.length === 0)\n return []\n // Keyed by the DISPLAYED path (what `insertText` emitted) so a\n // post-format buffer still resolves back to its `FileEntry`.\n const byPath = new Map<string, FileEntry>()\n for (const file of catalog) byPath.set(formatPath(file), file)\n const refs: CompletionReference<FileEntry>[] = []\n // Match `@<path>` at start-of-buffer or after whitespace. Paths are\n // non-whitespace sequences — files contain `/`, `.`, `-`, etc., so a\n // word-boundary rule won't do. We greedy-match `\\S+` then trim\n // trailing punctuation that's almost never part of the intended\n // reference (sentence end). The trimmed candidate must exist in the\n // catalog for the span to be highlighted — typos don't produce\n // false-positive references.\n // Capture groups: 1 = leading boundary (start-of-buffer or whitespace),\n // 2 = path body without the `@` sigil. `@` itself is matched but not\n // captured because the consumer only needs the raw path to look up\n // the catalog entry.\n const rx = /(^|\\s)@(\\S+)/g\n for (const m of text.matchAll(rx)) {\n const rawCandidate = m[2]\n // Strip trailing `.`, `,`, `;`, `:`, `)` , `]`, `}`, `!`, `?` — when\n // the exact candidate isn't in the catalog but a shorter prefix is,\n // the user almost certainly mentioned the file in regular prose.\n // Single regex replace + slice instead of a per-char loop keeps this\n // O(n) in candidate length rather than O(n²) on pathological tails.\n const stripped = byPath.has(rawCandidate)\n ? rawCandidate\n : rawCandidate.replace(/[.,;:)\\]}!?]+$/, '')\n const file = byPath.get(stripped)\n if (!file)\n continue\n const start = m.index + m[1].length\n // `trimmed` matches the visible \"@<stripped>\" span — recompute its\n // length from `stripped` so we don't over-paint on the trailing punct.\n const trimmedLen = 1 + stripped.length // `@` + path\n refs.push({\n providerId: 'files',\n start,\n end: start + trimmedLen,\n // `itemId` mirrors `suggest()`'s `id` (the displayed path)\n // so the engine's reference ↔ item correlation stays\n // consistent on either side of the formatter. `data.path`\n // still carries the raw project-root-relative path for\n // downstream consumers (`uniqueFilesFromReferences`,\n // submission attach hooks, …).\n itemId: stripped,\n data: file,\n })\n }\n return refs\n },\n }\n}\n\n/** Return the parent directory of a forward-slashed path, or `''` for root entries. */\nfunction parentDir(path: string): string {\n const lastSlash = path.lastIndexOf('/')\n return lastSlash <= 0 ? '' : path.slice(0, lastSlash)\n}\n\n/**\n * Walk a reference list and return the deduplicated set of files in\n * first-mention order — input to \"attach these files to the prompt\"\n * downstream logic.\n */\nexport function uniqueFilesFromReferences(\n references: readonly CompletionReference<unknown>[],\n): FileEntry[] {\n const out: FileEntry[] = []\n const seen = new Set<string>()\n for (const ref of references) {\n if (ref.providerId !== 'files')\n continue\n if (seen.has(ref.itemId))\n continue\n seen.add(ref.itemId)\n out.push(ref.data as FileEntry)\n }\n return out\n}\n","/**\n * Completion provider that exposes the discovered skills catalog as\n * slash-commands. Trigger: `/`. Filtering: substring against `name +\n * description`, case-insensitive. Inserted text is `/{name} ` so the\n * cursor lands ready for the next token.\n *\n * Skill activation is **not** performed here — the provider is pure UI.\n * Submission flow at the host (TUI `onSubmitPrompt`) walks the prompt for\n * skill references via `parseReferences` and calls `agent.activateSkill`\n * before `agent.run`.\n */\n\nimport type { SkillConfig } from '../skills'\nimport type {\n CompletionContext,\n CompletionItem,\n CompletionProvider,\n CompletionReference,\n} from './completion'\n\n/** Trigger character — slash-commands convention. */\nexport const SKILLS_TRIGGER = '/'\n\n/** Valid skill-name shape (matches the parser): lowercase alnum + dashes. */\nconst SKILL_NAME_RX = /^[a-z0-9][a-z0-9-]*$/\n\n/**\n * Filter + rank visible skills against a query. Hoisted to a module\n * helper so the sync and async branches of `suggest()` share one\n * implementation (the async branch hits this once the lazy SKILL.md\n * scan resolves; sync branch hits it on every keystroke thereafter).\n */\nfunction scoreSkills(\n catalog: readonly SkillConfig[],\n query: string,\n): CompletionItem<SkillConfig>[] {\n const q = query.trim().toLowerCase()\n const items = catalog\n .filter(skill => SKILL_NAME_RX.test(skill.name))\n .filter((skill) => {\n if (q.length === 0)\n return true\n return (\n skill.name.toLowerCase().includes(q)\n || skill.description.toLowerCase().includes(q)\n )\n })\n // Prefix matches first, then substring matches, then alphabetical.\n .sort((a, b) => {\n const an = a.name.toLowerCase()\n const bn = b.name.toLowerCase()\n if (q) {\n const aPrefix = an.startsWith(q)\n const bPrefix = bn.startsWith(q)\n if (aPrefix !== bPrefix)\n return aPrefix ? -1 : 1\n }\n return an.localeCompare(bn)\n })\n return items.map<CompletionItem<SkillConfig>>(skill => ({\n id: skill.name,\n label: skill.name,\n description: skill.description,\n insertText: `${SKILLS_TRIGGER}${skill.name} `,\n data: skill,\n }))\n}\n\n/**\n * Build a slash-command completion provider against a *live* skills\n * catalog. The factory captures a getter so the catalog can change across\n * renders (toggles, reload) without re-instantiating the provider.\n *\n * Pass `getEnabled` to additionally hide skills the user has toggled off\n * — when undefined, every catalog entry is offered.\n */\nexport function createSkillsCompletionProvider(opts: {\n /** Live catalog. Re-evaluated per call so toggles take effect immediately. */\n getCatalog: () => readonly SkillConfig[]\n /** Optional enable-set filter; when undefined every catalog skill is offered. */\n getEnabled?: () => readonly string[] | undefined\n /**\n * Optional hook called the first time the host needs the catalog —\n * typically when the user opens the `/` popover. Mirror of the\n * files provider's `ensureCatalog`; same idempotent contract.\n *\n * Hosts wire this to a lazy SKILL.md scan so the boot path stays\n * free of disk reads in deeply-nested skill trees. Returns a Promise\n * on the very first `suggest()` call when the catalog is still\n * empty so the popover surfaces its loading state.\n */\n ensureCatalog?: () => Promise<readonly SkillConfig[]>\n}): CompletionProvider<SkillConfig> {\n const visible = (): SkillConfig[] => {\n const all = opts.getCatalog()\n const enabled = opts.getEnabled?.()\n if (enabled === undefined)\n return [...all]\n const allow = new Set(enabled)\n return all.filter(s => allow.has(s.name))\n }\n return {\n id: 'skills',\n trigger: SKILLS_TRIGGER,\n label: 'Skills',\n suggest(query) {\n // Lazy-load on first suggest if the host wired a thunk. Same\n // pattern as the files provider — popover loading state covers\n // the gap, subsequent suggests are sync.\n if (opts.ensureCatalog) {\n const pending = opts.ensureCatalog()\n if (opts.getCatalog().length === 0) {\n return pending.then(() => scoreSkills(visible(), query))\n }\n }\n return scoreSkills(visible(), query)\n },\n parseReferences(text, _ctx: CompletionContext) {\n const catalog = visible()\n if (catalog.length === 0)\n return []\n const byName = new Map<string, SkillConfig>()\n for (const skill of catalog) byName.set(skill.name, skill)\n const refs: CompletionReference<SkillConfig>[] = []\n // Match `/name` at start-of-buffer or after whitespace. The lookbehind\n // is implemented manually (no regex `\\b` because `/` isn't a word\n // character) so we work in environments that lack lookbehind support.\n // Grammar must match `SKILL_NAME_RX` — `[a-z0-9-]` only, no `_`. Using\n // `\\w` here would parse `/foo_bar` as a candidate, only for the catalog\n // lookup to drop it; aligning the two grammars keeps the parser honest.\n const rx = /(^|\\s)(\\/([a-z0-9][a-z0-9-]*))/g\n for (const m of text.matchAll(rx)) {\n const name = m[3]\n const skill = byName.get(name)\n if (!skill)\n continue\n const start = m.index + m[1].length\n refs.push({\n providerId: 'skills',\n start,\n end: start + m[2].length,\n itemId: skill.name,\n data: skill,\n })\n }\n return refs\n },\n }\n}\n\n/**\n * Walk a parsed prompt for skill references and return the deduplicated\n * list of skill names — input to `agent.activateSkill(name)` calls on\n * submit.\n */\nexport function uniqueSkillNamesFromReferences(\n references: readonly CompletionReference<unknown>[],\n): string[] {\n const out: string[] = []\n const seen = new Set<string>()\n for (const ref of references) {\n if (ref.providerId !== 'skills')\n continue\n if (seen.has(ref.itemId))\n continue\n seen.add(ref.itemId)\n out.push(ref.itemId)\n }\n return out\n}\n","/**\n * Keybindings — a single source of truth for the app's global shortcuts.\n *\n * Every customizable action is declared once in {@link KEYBINDING_DEFS}\n * with its default trigger, label, and description. The TUI imports\n * `DEFAULT_KEYBINDINGS` for the baked-in defaults; on launch we load\n * `<userDir>/keybindings.json` (when present) and merge user overrides\n * on top so the user can rebind any action without recompiling.\n *\n * Scope (deliberate):\n *\n * - We expose only the **global, action-opening** shortcuts here —\n * \"open the Settings modal\", \"cycle agent profile\", \"enter\n * select-turn mode\", etc. Modal-internal letter shortcuts (`f`\n * fork, `d` delete, `c` copy on the turn-details modal) and\n * universal navigation (`↑/↓/↵/⎋`) stay hardcoded — they live\n * too close to a renderable's input contract to remap safely.\n *\n * - Storage lives at the **user level** (`<userDir>/keybindings.json`),\n * never inside a project's `.{prefix}/`. Shortcut muscle memory is\n * a per-user concern, not a per-repo one.\n *\n * Format on disk: JSON with `//` and `/* … *\\/` comments allowed\n * (parsed via a small JSONC stripper). Each entry maps an action id\n * to a binding spec string. Specs are parsed by {@link parseBindingSpec};\n * unknown / malformed entries fall back to the default for that action\n * (with a debug log under `ZIDANE_DEBUG`) so a syntax slip never bricks\n * the TUI.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\n\n// ---------------------------------------------------------------------------\n// Action catalog\n// ---------------------------------------------------------------------------\n\n/**\n * Every action the keybindings system can drive. Adding a new action\n * here forces every consumer (TUI keyboard handler, hint rows, settings\n * UI) to handle it, and ships a default that the user can override.\n */\nexport type KeyAction\n // Global action shortcuts — fire from the top-level keyboard\n // handler in `app.tsx`, gated on screen / busy state.\n = | 'openSettings'\n | 'openSessionDetails'\n | 'openModelPicker'\n | 'openEffortPicker'\n | 'openTodos'\n | 'openKeybindings'\n | 'cycleAgent'\n | 'enterSelectTurnMode'\n | 'cancelToolCall'\n | 'changeCwd'\n // Message-queue navigation — only meaningful when the user has\n // type-ahead prompts waiting in the queue box above the prompt\n // input. `enterQueueSelection` moves the focus from the textarea\n // into the queue; while there, `pushQueuedMessage` (steers the\n // selected entry into the live run) and `dropQueuedMessage`\n // (removes the entry) act on the highlighted row.\n | 'enterQueueSelection'\n | 'pushQueuedMessage'\n | 'dropQueuedMessage'\n // Turn-details modal actions (`ctrl+s` select-turn mode → enter).\n // Each binding only matches while that modal is mounted, so the\n // single-letter defaults can clash with letters used elsewhere\n // without conflict.\n | 'turnFork'\n | 'turnDelete'\n | 'turnCopy'\n | 'turnEdit'\n // Session-details modal actions (`ctrl+x`).\n | 'sessionDelete'\n | 'sessionCopyId'\n | 'sessionGenerateTitle'\n | 'sessionExportMarkdown'\n | 'sessionExportJson'\n | 'sessionCompact'\n\n/** Resolved keybindings — every action has a current spec string. */\nexport type KeyBindings = Record<KeyAction, string>\n\n/**\n * Logical grouping for one action — used by the keybindings panel to\n * render section headers. Stable strings (not an enum) so future\n * groups can be added without churning every existing entry.\n */\nexport type KeyBindingGroup\n = | 'Global'\n | 'Message queue'\n | 'Turn details'\n | 'Session details'\n\n/** Static metadata for one action — used by the loader, hint rows, settings UI. */\nexport interface KeyBindingDef {\n /** Stable identifier — JSON key + TS union member. */\n action: KeyAction\n /** Default trigger spec (e.g. `'ctrl+o'`). */\n default: string\n /** Short label used in shortcut hint rows. */\n label: string\n /** Longer description for the settings UI and the annotated JSON file. */\n description: string\n /**\n * Group label rendered as a section header in the keybindings panel.\n * Items are listed in {@link KEYBINDING_DEFS} order; the panel just\n * inserts a header whenever this field changes between adjacent\n * entries — so groups stay contiguous and the order here is the\n * order on screen.\n */\n group: KeyBindingGroup\n}\n\n/**\n * The canonical action catalog. Order is the source of truth for the\n * generated keybindings file and the settings UI.\n */\nexport const KEYBINDING_DEFS: readonly KeyBindingDef[] = [\n {\n action: 'openSettings',\n default: 'ctrl+o',\n label: 'settings',\n description: 'open the Settings modal (toggles, theme, keybindings)',\n group: 'Global',\n },\n {\n action: 'openSessionDetails',\n default: 'ctrl+x',\n label: 'session',\n description: 'open the session details modal (stats, export, delete, rename)',\n group: 'Global',\n },\n {\n action: 'openModelPicker',\n default: 'ctrl+m',\n label: 'model',\n description: 'open the cross-provider model picker',\n group: 'Global',\n },\n {\n action: 'openEffortPicker',\n default: 'ctrl+l',\n label: 'effort',\n description: 'open the reasoning-effort picker (when the active model supports it)',\n group: 'Global',\n },\n {\n action: 'openTodos',\n default: 'ctrl+t',\n label: 'todos',\n description: 'open the active run\\'s todo list (the agent\\'s `todowrite` checkpoints)',\n group: 'Global',\n },\n {\n action: 'openKeybindings',\n default: 'ctrl+y',\n label: 'keybindings',\n description: 'open the keybindings panel — list every shortcut and jump to the keybindings.json file',\n group: 'Global',\n },\n {\n action: 'cycleAgent',\n default: 'shift+tab',\n label: 'cycle agent',\n description: 'cycle to the next agent profile (when multiple profiles are registered)',\n group: 'Global',\n },\n {\n action: 'enterSelectTurnMode',\n default: 'ctrl+s',\n label: 'messages',\n description: 'enter select-turn mode to navigate previous messages',\n group: 'Global',\n },\n {\n action: 'cancelToolCall',\n default: 'ctrl+k',\n label: 'cancel tool',\n description: 'open the in-flight tool picker to cancel a single tool call without aborting the run (esc still aborts the whole run)',\n group: 'Global',\n },\n {\n action: 'changeCwd',\n default: 'ctrl+g',\n label: 'cwd',\n description: 'open the directory picker to change the working directory',\n group: 'Global',\n },\n // Message-queue navigation — only matches when the queue box above\n // the prompt has at least one entry. Defaults pick keys the prompt\n // textarea doesn't already claim (no default for enter — see below).\n //\n // `enterQueueSelection` is intentionally unbound by default: the\n // natural way to enter the queue is to press plain `↑` on an empty\n // prompt buffer (handled in `PromptBlock`'s onKeyDown), which doesn't\n // need a global shortcut. The action stays in the catalog so users\n // who want a dedicated key — e.g. for entering the queue WITHOUT\n // emptying their draft first — can rebind it in `keybindings.json`.\n // We don't ship a default because no arrow / modifier combo is both\n // free in OpenTUI's textarea AND forwarded by every common terminal\n // (ctrl+arrow → macOS Mission Control; cmd+arrow → terminal scroll /\n // tab nav; alt+arrow → terminal word-nav).\n {\n action: 'enterQueueSelection',\n default: '',\n label: 'select queue',\n description: 'move focus from the prompt textarea into the type-ahead queue box (also: ↑ on an empty prompt)',\n group: 'Message queue',\n },\n {\n action: 'pushQueuedMessage',\n default: 'ctrl+return',\n label: 'push',\n description: 'steer the selected queued message into the live run (delivered between tool calls)',\n group: 'Message queue',\n },\n {\n action: 'dropQueuedMessage',\n default: 'backspace',\n label: 'drop',\n description: 'remove the selected queued message — exits back to the prompt when the queue empties (the big \"remove\" key works everywhere: backspace on Win/Linux, the laptop key labeled \"delete\" on Mac, which sends backspace)',\n group: 'Message queue',\n },\n // Turn-details modal — only fires while that modal is mounted.\n {\n action: 'turnFork',\n default: 'f',\n label: 'fork',\n description: 'fork the session at the selected turn (two-press confirm)',\n group: 'Turn details',\n },\n {\n action: 'turnDelete',\n default: 'd',\n label: 'delete',\n description: 'delete the selected turn (two-press confirm)',\n group: 'Turn details',\n },\n {\n action: 'turnCopy',\n default: 'c',\n label: 'copy',\n description: 'copy the selected turn content to the clipboard (OSC 52)',\n group: 'Turn details',\n },\n {\n action: 'turnEdit',\n default: 'e',\n label: 'edit',\n description: 'edit the text content of the selected turn',\n group: 'Turn details',\n },\n // Session-details modal — only fires while that modal is mounted.\n {\n action: 'sessionDelete',\n default: 'd',\n label: 'delete',\n description: 'delete the entire session (two-press confirm)',\n group: 'Session details',\n },\n {\n action: 'sessionCopyId',\n default: 'c',\n label: 'copy id',\n description: 'copy the full session id to the clipboard (OSC 52)',\n group: 'Session details',\n },\n {\n action: 'sessionGenerateTitle',\n default: 'g',\n label: 'generate title',\n description: 'generate a title for the session via the active provider/model',\n group: 'Session details',\n },\n {\n action: 'sessionExportMarkdown',\n default: 'e',\n label: 'export md',\n description: 'export the session as Markdown under .{prefix}/sessions/',\n group: 'Session details',\n },\n {\n action: 'sessionExportJson',\n default: 'j',\n label: 'export json',\n description: 'export the session as JSON under .{prefix}/sessions/',\n group: 'Session details',\n },\n {\n action: 'sessionCompact',\n default: 'k',\n label: 'compact',\n description: 'compact older turns via an LLM summary (model still sees everything from the boundary down)',\n group: 'Session details',\n },\n]\n\n/** Index by action for O(1) lookup of label / description / default. */\nexport const KEYBINDING_DEF_BY_ACTION: Readonly<Record<KeyAction, KeyBindingDef>>\n = KEYBINDING_DEFS.reduce(\n (acc, def) => Object.assign(acc, { [def.action]: def }),\n {} as Record<KeyAction, KeyBindingDef>,\n )\n\n/** Defaults, derived from {@link KEYBINDING_DEFS} so the two never drift. */\nexport const DEFAULT_KEYBINDINGS: KeyBindings\n = KEYBINDING_DEFS.reduce(\n (acc, def) => Object.assign(acc, { [def.action]: def.default }),\n {} as KeyBindings,\n )\n\n// ---------------------------------------------------------------------------\n// Spec parsing + matching\n// ---------------------------------------------------------------------------\n\n/** Parsed shape of a `\"ctrl+shift+o\"` spec. */\nexport interface ParsedBinding {\n ctrl: boolean\n shift: boolean\n alt: boolean\n meta: boolean\n /** The key name (e.g. `'o'`, `'tab'`, `'escape'`). Always lowercase. */\n name: string\n}\n\n/**\n * Aliases the parser accepts on input so the on-disk format can be\n * forgiving (different terminals + docs label modifiers + keys\n * differently). Output names are canonicalized to one form per key so\n * `matchesBinding` only has to compare one canonical against a\n * KeyEvent's `name`. Unknown names pass through verbatim — that's\n * useful for terminal-specific keys we don't have to enumerate.\n */\nconst NAME_ALIASES: Readonly<Record<string, string>> = {\n esc: 'escape',\n enter: 'return',\n ret: 'return',\n space: 'space',\n spc: 'space',\n del: 'delete',\n ins: 'insert',\n pgup: 'pageup',\n pgdn: 'pagedown',\n pagup: 'pageup',\n pagedn: 'pagedown',\n}\n\nconst MODIFIER_ALIASES: Readonly<Record<string, 'ctrl' | 'shift' | 'alt' | 'meta'>> = {\n ctrl: 'ctrl',\n control: 'ctrl',\n shift: 'shift',\n alt: 'alt',\n option: 'alt',\n opt: 'alt',\n meta: 'meta',\n cmd: 'meta',\n command: 'meta',\n super: 'meta',\n win: 'meta',\n}\n\n/**\n * Parse a `'ctrl+shift+o'`-style spec into structured fields. Returns\n * `null` for empty / malformed inputs; the caller falls back to the\n * default for that action.\n *\n * Tolerant by design:\n * - Case-insensitive (`Ctrl+O` → same as `ctrl+o`).\n * - Modifier order doesn't matter (`shift+ctrl+o` works).\n * - Mixed `+` and `-` separators allowed (`ctrl-o`, `ctrl+o`).\n * - Trailing / leading whitespace is stripped.\n * - Duplicate modifiers collapse silently.\n */\nexport function parseBindingSpec(spec: string | undefined | null): ParsedBinding | null {\n if (typeof spec !== 'string')\n return null\n const trimmed = spec.trim().toLowerCase()\n if (!trimmed)\n return null\n // Split on `+` OR `-`. `-` is tolerated mostly for users who copy\n // bindings from VS Code-style strings (`ctrl-o`).\n const parts = trimmed.split(/[+\\-]/).map(p => p.trim()).filter(Boolean)\n if (parts.length === 0)\n return null\n const result: ParsedBinding = { ctrl: false, shift: false, alt: false, meta: false, name: '' }\n for (const part of parts) {\n const modifier = MODIFIER_ALIASES[part]\n if (modifier) {\n result[modifier] = true\n continue\n }\n if (result.name) {\n // A second non-modifier token would mean a binding like `o+p` —\n // not meaningful in OpenTUI's keypress model (one key at a time).\n return null\n }\n result.name = NAME_ALIASES[part] ?? part\n }\n if (!result.name)\n return null\n return result\n}\n\n/**\n * Test a KeyEvent against a parsed-or-string binding. Accepts either\n * form so callers can pre-parse (hot path) or pass the raw spec for\n * one-off checks.\n *\n * Modifier semantics: ALL declared modifiers must be pressed AND no\n * extra modifiers may be pressed. So `'o'` does NOT match `ctrl+o`,\n * and `'ctrl+o'` does NOT match `ctrl+shift+o`. This avoids surprises\n * where a binding that should be specific accidentally fires for a\n * superset of key combinations.\n */\nexport function matchesBinding(\n event: { name?: string, ctrl?: boolean, shift?: boolean, meta?: boolean, option?: boolean, alt?: boolean },\n spec: string | ParsedBinding,\n): boolean {\n const parsed = typeof spec === 'string' ? parseBindingSpec(spec) : spec\n if (!parsed)\n return false\n // OpenTUI's `ParsedKey` exposes `option` for Alt; some terminals\n // also report it as `alt`. Accept either.\n const altPressed = !!event.option || !!event.alt\n if (parsed.ctrl !== !!event.ctrl)\n return false\n if (parsed.shift !== !!event.shift)\n return false\n if (parsed.alt !== altPressed)\n return false\n if (parsed.meta !== !!event.meta)\n return false\n if ((event.name ?? '').toLowerCase() !== parsed.name)\n return false\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Display helpers\n// ---------------------------------------------------------------------------\n\n/** Verbose key names → compact single-cell glyphs used by hint rows. */\nconst KEY_GLYPHS: Readonly<Record<string, string>> = {\n up: '↑',\n down: '↓',\n left: '←',\n right: '→',\n return: '↵',\n enter: '↵',\n delete: '⌫',\n backspace: '⌫',\n escape: 'esc',\n space: '␣',\n tab: '⇥',\n}\n\n/**\n * Render a binding spec (`\"ctrl+return\"`, `\"backspace\"`, `\"delete\"`) as a\n * compact display string the user recognizes at a glance. Substitutes\n * arrow / enter / backspace glyphs for their verbose names so the hint\n * row stays narrow; modifier names + plain keys pass through unchanged.\n *\n * Empty / missing specs return an empty string — callers decide whether\n * to render a placeholder (e.g. `'—'` for an unbound action in the\n * keybindings panel) or hide the surface entirely (e.g. hint rows that\n * fall back to a different chord when the binding is empty).\n */\nexport function formatBindingForDisplay(spec: string | null | undefined): string {\n if (!spec)\n return ''\n const segments = spec.toLowerCase().split('+')\n const key = segments.pop() ?? ''\n const modifiers = segments.join('+')\n const glyph = KEY_GLYPHS[key] ?? key\n return modifiers ? `${modifiers}+${glyph}` : glyph\n}\n\n// ---------------------------------------------------------------------------\n// Merge + file IO\n// ---------------------------------------------------------------------------\n\n/**\n * Merge a partial map of user overrides into the defaults. Unknown\n * action keys are dropped (a future TUI version may have retired the\n * action); invalid spec strings (parse fails) fall back to the default\n * for that action.\n */\nexport function mergeKeybindings(\n overrides: Readonly<Record<string, unknown>> | null | undefined,\n): KeyBindings {\n if (!overrides || typeof overrides !== 'object')\n return { ...DEFAULT_KEYBINDINGS }\n const merged: KeyBindings = { ...DEFAULT_KEYBINDINGS }\n for (const def of KEYBINDING_DEFS) {\n const raw = overrides[def.action]\n if (typeof raw !== 'string')\n continue\n if (!parseBindingSpec(raw))\n continue // invalid spec → keep the default\n merged[def.action] = raw\n }\n return merged\n}\n\n/** Path of the user-level keybindings file. */\nexport function keybindingsPath(userDir: string): string {\n return resolve(userDir, 'keybindings.json')\n}\n\n/**\n * One contiguous section of the catalog — every action in a single\n * `group` rendered together with the section header inserted once.\n * Adjacent sections with the same group are NOT merged: catalog order\n * wins so the on-screen story matches the on-disk file.\n */\nexport interface KeyBindingSection {\n group: KeyBindingDef['group']\n rows: ReadonlyArray<{ def: KeyBindingDef, spec: string }>\n}\n\n/**\n * Walk {@link KEYBINDING_DEFS} in order and bucket rows into contiguous\n * sections by `group`. Shared between the standalone `KeybindingsModal`\n * and the `Keybindings` tab inside `SettingsModal` so both surfaces\n * render the same catalog with the same ordering rules.\n */\nexport function groupBindings(bindings: KeyBindings): readonly KeyBindingSection[] {\n const sections: Array<{ group: KeyBindingDef['group'], rows: Array<{ def: KeyBindingDef, spec: string }> }> = []\n for (const def of KEYBINDING_DEFS) {\n const last = sections[sections.length - 1]\n const spec = bindings[def.action] ?? ''\n if (last && last.group === def.group) {\n last.rows.push({ def, spec })\n continue\n }\n sections.push({ group: def.group, rows: [{ def, spec }] })\n }\n return sections\n}\n\n/**\n * Width (in cells) of the fixed key column used by every binding-row\n * renderer. Derived once from {@link KEYBINDING_DEFS} so a new action\n * with a wider default spec (`ctrl+shift+x`, …) automatically grows\n * the column instead of truncating the label.\n */\nexport const KEYBINDING_KEY_COL_WIDTH = (() => {\n let max = 8\n for (const def of KEYBINDING_DEFS) {\n const width = formatBindingForDisplay(def.default).length\n if (width > max)\n max = width\n }\n return max + 2 // breathing room before the label column\n})()\n\n/**\n * Load + merge the user's keybindings file (when present). Always\n * returns a fully-resolved {@link KeyBindings} — defaults fill in for\n * any missing / invalid entries so consumers can treat the result as\n * total.\n *\n * Failure modes (all tolerated, all fall back to defaults):\n * - File missing\n * - File not valid JSON / JSONC (after comment stripping)\n * - Top-level is not an object\n * - Individual entries have invalid spec strings\n */\nexport function readKeybindings(userDir: string): KeyBindings {\n const path = keybindingsPath(userDir)\n if (!existsSync(path))\n return { ...DEFAULT_KEYBINDINGS }\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(stripJsonComments(raw)) as unknown\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return { ...DEFAULT_KEYBINDINGS }\n return mergeKeybindings(parsed as Record<string, unknown>)\n }\n catch {\n return { ...DEFAULT_KEYBINDINGS }\n }\n}\n\n/**\n * Create the user-level `keybindings.json` if it doesn't exist,\n * prefilled with every default. Returns the absolute file path either\n * way so the caller can open it in `$EDITOR`. Idempotent: an existing\n * user file is NEVER overwritten — the user's customizations are\n * sacred.\n */\nexport function ensureKeybindingsFile(userDir: string): string {\n const path = keybindingsPath(userDir)\n if (existsSync(path))\n return path\n writeFileAtomic(path, renderDefaultFile(), { ensureDir: true })\n return path\n}\n\n/**\n * Render the default `keybindings.json` body. JSONC with a header\n * explaining the format + per-action comments derived from\n * {@link KEYBINDING_DEFS}'s descriptions. The user sees the catalog +\n * defaults at a glance the moment they open the file.\n */\nfunction renderDefaultFile(): string {\n const lines: string[] = []\n lines.push('// Zidane keybindings — edit values below to customize.')\n lines.push('//')\n lines.push('// Format:')\n lines.push('// \"<action>\": \"<modifier>+<modifier>+<key>\"')\n lines.push('//')\n lines.push('// Modifiers: ctrl, shift, alt (option), meta (cmd).')\n lines.push('// Keys: a-z, 0-9, tab, return, escape, space, delete, …')\n lines.push('//')\n lines.push('// Comments (`//` line, `/* block */`) are stripped before parsing.')\n lines.push('// Restart the TUI to apply changes.')\n lines.push('{')\n KEYBINDING_DEFS.forEach((def, i) => {\n const trailingComma = i < KEYBINDING_DEFS.length - 1 ? ',' : ''\n lines.push(` // ${def.description}`)\n lines.push(` ${JSON.stringify(def.action)}: ${JSON.stringify(def.default)}${trailingComma}`)\n })\n lines.push('}')\n lines.push('')\n return lines.join('\\n')\n}\n\n// ---------------------------------------------------------------------------\n// JSONC parsing — `//` line + `/* block */` comments, single pass\n// ---------------------------------------------------------------------------\n\n/**\n * Strip `//` line comments and `/* … *\\/` block comments from a JSONC\n * source string. Stays out of string literals so a `\"foo // bar\"` value\n * keeps its slashes. Exported for unit-tests; the only runtime caller\n * is {@link readKeybindings}.\n *\n * Trade-off: this is a tiny state-machine, not a full JSONC parser. It\n * doesn't understand `\\\\` outside strings (irrelevant for JSON) and\n * doesn't track line numbers in error messages. Good enough for a\n * config file — corruption in production gracefully falls back to the\n * defaults via the try/catch in `readKeybindings`.\n */\nexport function stripJsonComments(input: string): string {\n let out = ''\n let i = 0\n let inString = false\n let escape = false\n while (i < input.length) {\n const c = input[i]\n if (inString) {\n out += c\n if (escape)\n escape = false\n else if (c === '\\\\')\n escape = true\n else if (c === '\"')\n inString = false\n i++\n continue\n }\n if (c === '\"') {\n inString = true\n out += c\n i++\n continue\n }\n if (c === '/' && input[i + 1] === '/') {\n while (i < input.length && input[i] !== '\\n')\n i++\n continue\n }\n if (c === '/' && input[i + 1] === '*') {\n i += 2\n while (i < input.length && !(input[i] === '*' && input[i + 1] === '/'))\n i++\n i += 2\n continue\n }\n out += c\n i++\n }\n return out\n}\n","/**\n * Pure helpers for the file-edit approval gate.\n *\n * Lives in `chat/` (not `tui/`) so unit tests can exercise the decision\n * logic without mounting React. The TUI binds these helpers in\n * `app.tsx`'s `applyGate` and in the modal's submit path; replay code\n * (`eventsFromTurns`) reuses the result parser.\n */\n\nimport type { ToolResultContent } from '../types'\nimport type { ApprovalDecision } from './safe-mode-context'\nimport type { EditOutcome, EditOutcomeKind, EditPayload } from './types'\n\n/**\n * Convert a per-hunk approval mask into an `EditOutcome[]`. `true` →\n * `applied`; `false` → `denied` with the supplied reason.\n *\n * Length is `Math.max(mask.length, fallbackLength)` so callers passing a\n * shorter mask still get a fully-populated array — missing entries\n * default to applied, matching the \"no decision => keep\" convention.\n */\nexport function maskToOutcomeKinds(\n mask: readonly boolean[],\n fallbackLength: number,\n deniedReason = 'denied by user',\n): EditOutcome[] {\n const len = Math.max(mask.length, fallbackLength)\n const out: EditOutcome[] = []\n for (let i = 0; i < len; i++) {\n const keep = i < mask.length ? mask[i] : true\n out.push(keep ? { kind: 'applied' } : { kind: 'denied', reason: deniedReason })\n }\n return out\n}\n\n/**\n * Apply an `ApprovalDecision` to a payload, returning the resolved\n * per-hunk outcomes + the gate-level verdict.\n *\n * Pure — does not mutate `input` or `payload`. The TUI's `applyGate`\n * consumes the result: stashes `outcomes` in the pending-annotation map\n * (keyed by callId) so `tool:transform` can append the\n * `<edit-outcomes>` block to the tool result, rebinds `ctx.input.edits`\n * to the approved subset for `partial`, and emits the `syntheticEvent`\n * for fully-denied or fully-blocked calls.\n */\nexport interface ResolvedApproval {\n /** Final state of every hunk after the decision (1:1 with payload.hunks). */\n outcomes: EditOutcome[]\n /** True when no hunk will be applied — gate should `block` the call. */\n shouldBlock: boolean\n /**\n * Synthetic `EditPayload` to render in the transcript. Identical to the\n * incoming payload but with `outcomes` set so the renderer can badge\n * each hunk. Only meaningful when at least one hunk was denied —\n * an all-applied decision returns `null` here and the normal\n * `tool:before` event suffices.\n */\n syntheticEvent: EditPayload | null\n}\n\nexport function resolveApprovalForPayload(\n decision: ApprovalDecision,\n payload: EditPayload,\n): ResolvedApproval {\n const total = payload.hunks.length\n\n if (decision === 'deny') {\n const outcomes: EditOutcome[] = Array.from(\n { length: total },\n () => ({ kind: 'denied' as const, reason: 'denied by user' }),\n )\n return {\n outcomes,\n shouldBlock: true,\n syntheticEvent: { ...payload, outcomes },\n }\n }\n\n if (typeof decision === 'object' && decision.kind === 'partial') {\n const outcomes = maskToOutcomeKinds(decision.mask, total)\n const anyApplied = outcomes.some(o => o.kind === 'applied')\n return {\n outcomes,\n shouldBlock: !anyApplied,\n syntheticEvent: { ...payload, outcomes },\n }\n }\n\n // accept-once / accept-session / accept-safelist: every hunk applies.\n // No synthetic event — `tool:before` will paint the standard payload.\n return {\n outcomes: Array.from({ length: total }, () => ({ kind: 'applied' as const })),\n shouldBlock: false,\n syntheticEvent: null,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Result annotation — XML-like sentinel block appended to edit tool\n// results so the renderer (live + replay) can reconstruct per-hunk\n// outcomes without a host-side side-channel on `tool_call.input`.\n//\n// Shape (one block per call):\n//\n// <edit-outcomes>\n// #1 applied\n// #2 denied: denied by user\n// #3 applied\n// </edit-outcomes>\n//\n// - Opening + closing tags each on their own line.\n// - One line per hunk: `#<1-based index> <kind>[: <reason>]`.\n// - `kind` is one of `applied | denied | skipped | failed`.\n//\n// The TUI's `tool:transform` hook appends the block when at least one\n// hunk is NOT applied — an all-applied call needs no annotation since\n// the legacy summary line conveys the same information.\n// ---------------------------------------------------------------------------\n\n/** Sentinel tags used by {@link buildEditOutcomesAnnotation} / parser. */\nconst ANNOTATION_OPEN = '<edit-outcomes>'\nconst ANNOTATION_CLOSE = '</edit-outcomes>'\n\nconst OUTCOME_KIND_RE = /^applied|denied|skipped|failed$/\nconst OUTCOME_LINE_RE = /^#(\\d+) (applied|denied|skipped|failed)(?:: ?(.*))?$/\n\n/**\n * Render an `EditOutcome[]` as the wire-format annotation block. Returns\n * the body to APPEND to a tool result; callers join with a leading\n * `\\n\\n` separator. Idempotent on missing reasons — bare `applied` lines\n * stay terse.\n */\nexport function buildEditOutcomesAnnotation(outcomes: readonly EditOutcome[]): string {\n const lines: string[] = [ANNOTATION_OPEN]\n for (let i = 0; i < outcomes.length; i++) {\n const o = outcomes[i]\n if (!OUTCOME_KIND_RE.test(o.kind))\n continue\n const reason = o.reason ? `: ${o.reason}` : ''\n lines.push(`#${i + 1} ${o.kind}${reason}`)\n }\n lines.push(ANNOTATION_CLOSE)\n return lines.join('\\n')\n}\n\n/**\n * Parse an `<edit-outcomes>…</edit-outcomes>` annotation block out of a\n * tool result body. Returns the outcomes keyed by 1-based hunk index, or\n * `null` when the block is missing / malformed.\n *\n * Anchored on the explicit tag pair so the parser doesn't false-positive\n * on natural prose that happens to contain `#1 applied`.\n */\nexport function parseEditOutcomesFromResult(\n result: string | readonly ToolResultContent[],\n): EditOutcome[] | null {\n const text = typeof result === 'string'\n ? result\n : result\n .filter((b): b is Extract<ToolResultContent, { type: 'text' }> => b.type === 'text')\n .map(b => b.text)\n .join('\\n')\n\n if (!text)\n return null\n\n const openIdx = text.indexOf(`\\n${ANNOTATION_OPEN}\\n`)\n const startIdx = openIdx >= 0 ? openIdx + 1 : (text.startsWith(`${ANNOTATION_OPEN}\\n`) ? 0 : -1)\n if (startIdx < 0)\n return null\n\n const closeNeedle = `\\n${ANNOTATION_CLOSE}`\n const closeIdx = text.indexOf(closeNeedle, startIdx)\n if (closeIdx < 0)\n return null\n\n const body = text.slice(startIdx + ANNOTATION_OPEN.length + 1, closeIdx)\n const found: Array<{ idx: number, outcome: EditOutcome }> = []\n for (const line of body.split('\\n')) {\n if (line.length === 0)\n continue\n const m = OUTCOME_LINE_RE.exec(line)\n if (!m)\n return null\n const idx = Number.parseInt(m[1], 10)\n if (!Number.isFinite(idx) || idx < 1)\n return null\n const kind = m[2] as EditOutcomeKind\n const reason = m[3]?.trim()\n found.push({\n idx,\n outcome: {\n kind,\n ...(reason ? { reason } : {}),\n },\n })\n }\n\n if (found.length === 0)\n return null\n\n const maxIdx = Math.max(...found.map(f => f.idx))\n const outcomes: EditOutcome[] = Array.from({ length: maxIdx }, () => ({ kind: 'applied' }))\n for (const { idx, outcome } of found)\n outcomes[idx - 1] = outcome\n return outcomes\n}\n\n/**\n * Strip the first `<edit-outcomes>…</edit-outcomes>` block out of a tool\n * result body, returning the surrounding text. Used by the\n * `tool:transform` hook to peel a body-emitted annotation before\n * re-appending the merged (approval ∪ body) version — otherwise the\n * result would carry two annotation blocks and\n * {@link parseEditOutcomesFromResult} would only see the first.\n *\n * Anchored on the same `\\n<edit-outcomes>\\n` / start-of-string newline\n * shape the parser uses, so prose that incidentally mentions\n * `<edit-outcomes>` (e.g. a model summarizing its own format) isn't\n * mistakenly stripped. Trims a single leading `\\n\\n` separator when\n * present so successive strips don't leave dangling blank lines.\n * Idempotent on inputs that don't contain a properly-anchored block.\n */\nexport function stripEditOutcomesAnnotation(text: string): string {\n // Mirror `parseEditOutcomesFromResult`'s anchor: either preceded by\n // a newline (the common case after a `\\n\\n` separator from the body)\n // OR at the very start of the body. A bare `<edit-outcomes>` inside\n // prose without that surrounding newline shape stays untouched.\n const newlineNeedle = `\\n${ANNOTATION_OPEN}\\n`\n const newlineIdx = text.indexOf(newlineNeedle)\n let openIdx: number\n if (newlineIdx >= 0)\n openIdx = newlineIdx + 1\n else if (text.startsWith(`${ANNOTATION_OPEN}\\n`))\n openIdx = 0\n else\n return text\n\n const closeIdx = text.indexOf(ANNOTATION_CLOSE, openIdx)\n if (closeIdx < 0)\n return text\n const blockEnd = closeIdx + ANNOTATION_CLOSE.length\n // Eat one preceding `\\n\\n` separator (the shape the writer emits)\n // so the trimmed text doesn't end with an awkward double newline.\n const sepStart = openIdx >= 2 && text.slice(openIdx - 2, openIdx) === '\\n\\n'\n ? openIdx - 2\n : openIdx\n return text.slice(0, sepStart) + text.slice(blockEnd)\n}\n\n/**\n * Merge body-side outcomes (keyed against the approved subset the tool\n * actually ran on, in subset-position order) into approval-side outcomes\n * (1:1 with the model's ORIGINAL `edits` list, with `denied` entries for\n * every hunk the user dropped).\n *\n * Algorithm: walk the approval array; every `applied` placeholder\n * corresponds to one approved hunk that the body ran. Consume body's\n * outcomes in order against those placeholders. Non-`applied` approval\n * entries (`denied`, `skipped`) stay untouched — they describe gate-\n * level decisions the body never saw.\n *\n * Pure. Returns a fresh array; never mutates either input.\n *\n * Edge cases:\n * - `body` is empty / shorter than the approved count → remaining\n * approval `applied` placeholders stay as `applied` (the body ran\n * happily; absence of a body entry means nothing failed).\n * - `body` longer than approved count → trailing body entries are\n * ignored. Shouldn't happen in practice (body sees the rebound\n * subset), but the guard keeps the merge total-pure.\n */\nexport function mergeApprovalAndBodyOutcomes(\n approval: readonly EditOutcome[],\n body: readonly EditOutcome[] | null,\n): EditOutcome[] {\n if (!body || body.length === 0)\n return approval.slice()\n const out: EditOutcome[] = []\n let bi = 0\n for (const entry of approval) {\n if (entry.kind === 'applied' && bi < body.length) {\n out.push(body[bi])\n bi++\n }\n else {\n out.push(entry)\n }\n }\n return out\n}\n\n/**\n * Rewrite a `multi_edit` body header so the totals reflect the model's\n * ORIGINAL edit list (the merged outcomes count) instead of the subset\n * the body actually saw after gate rebinding. Without this, a partially\n * approved call surfaces a misleading `applied 2 of 2 edits` (subset\n * counts) on the wire even when the original was `applied 2 of 3`.\n *\n * Three body-side shapes are handled (matching `multi_edit`'s emit):\n * 1. `Edited <path>: applied N edits (R replacements).`\n * 2. `Edited <path>: applied N of M edits (R replacements).`\n * 3. `multi_edit error: no edits applied to <path> (M attempted).`\n *\n * The replacements count is preserved verbatim — it's a body-side stat\n * the chat layer can't recompute. When the first line doesn't look like\n * any of the three shapes (e.g. an unrelated error preamble bubbled up),\n * the text is returned unchanged.\n */\nexport function rewriteMultiEditHeader(\n text: string,\n merged: readonly EditOutcome[],\n path: string,\n): string {\n const newlineIdx = text.indexOf('\\n')\n const firstLine = newlineIdx < 0 ? text : text.slice(0, newlineIdx)\n const rest = newlineIdx < 0 ? '' : text.slice(newlineIdx)\n\n // Match either success shape (with or without \"of M\") and capture R.\n // The path component uses `.+` greedy so colons (Windows `C:\\…`) and\n // parens (unusual but valid filenames) don't trip the match — the\n // regex engine backtracks to find the longest path that still lets\n // `: applied N…` line up against the trailing structure.\n const successMatch = firstLine.match(\n /^Edited .+: applied \\d+(?: of \\d+)? edits? \\((\\d+) replacement/,\n )\n // Match the all-failed shape (no replacements stat, defaults to 0).\n // `startsWith` instead of regex: the prefix is unique enough and\n // sidesteps the same path-content escaping concerns.\n const isFailedShape = firstLine.startsWith('multi_edit error: no edits applied to ')\n && firstLine.endsWith(' attempted).')\n if (!successMatch && !isFailedShape)\n return text\n\n const replacements = successMatch ? Number.parseInt(successMatch[1], 10) || 0 : 0\n const counts = summarizeOutcomes(merged)\n const applied = counts.applied\n const total = merged.length\n\n let newHeader: string\n if (applied === total) {\n newHeader = `Edited ${path}: applied ${total} edit${total === 1 ? '' : 's'} (${replacements} replacement${replacements === 1 ? '' : 's'}).`\n }\n else if (applied > 0) {\n newHeader = `Edited ${path}: applied ${applied} of ${total} edits (${replacements} replacement${replacements === 1 ? '' : 's'}).`\n }\n else {\n newHeader = `multi_edit error: no edits applied to ${path} (${total} attempted).`\n }\n\n return newHeader + rest\n}\n\n/**\n * Aggregate counts for the transcript's summary badge (`3 applied · 1\n * denied · 1 skipped`). Exported so renderers don't reimplement the\n * tally. Pure / O(n).\n */\nexport function summarizeOutcomes(outcomes: readonly EditOutcome[] | undefined): {\n applied: number\n denied: number\n skipped: number\n failed: number\n pending: number\n total: number\n} {\n const counts = { applied: 0, denied: 0, skipped: 0, failed: 0, pending: 0 }\n if (!outcomes)\n return { ...counts, total: 0 }\n for (const o of outcomes)\n counts[o.kind] += 1\n return { ...counts, total: outcomes.length }\n}\n","import type { ResolvedMatch } from '../tools/edit-utils'\nimport type { EditHunk, EditPayload } from './types'\nimport { resolveOldString, styleReplacementForVia } from '../tools/edit-utils'\n\n// ---------------------------------------------------------------------------\n// extractEditPayload — pulls `EditPayload` out of a tool call's raw input.\n//\n// Used by both the live `tool:before` hook and the historical replay path\n// (`eventsFromTurns`). Returns `undefined` when the tool isn't one of the\n// supported edit tools, or when the input shape is unexpected — in either\n// case the renderer falls back to the unstructured `↳ name(args)` line.\n//\n// Outcomes are NOT pulled from input — they live on the paired tool_result\n// text via the `<edit-outcomes>` annotation block written by the TUI's\n// `tool:transform` hook. `eventsFromTurns` pairs call ↔ result by callId\n// and attaches outcomes after the fact via `parseEditOutcomesFromResult`.\n//\n// `write_file` needs the *previous* on-disk content to render a diff;\n// that's the caller's responsibility (the TUI's hook reads it before the\n// tool runs). Pass it as `priorContent`; an empty string is fine for a\n// fresh create. Historical replay has no priorContent and falls through\n// to `oldString: ''` — every line renders as an addition, matching\n// `git diff` for newly-added files.\n// ---------------------------------------------------------------------------\n\ninterface EditStepInput {\n old_string: unknown\n new_string: unknown\n replace_all?: unknown\n}\n\nexport function extractEditPayload(\n name: string,\n input: Record<string, unknown>,\n priorContent?: string,\n): EditPayload | undefined {\n const path = input.path\n if (typeof path !== 'string' || path === '')\n return undefined\n\n if (name === 'edit') {\n const oldString = input.old_string\n const newString = input.new_string\n if (typeof oldString !== 'string' || typeof newString !== 'string')\n return undefined\n const hunks: EditHunk[] = [{\n oldString,\n newString,\n ...(input.replace_all === true ? { replaceAll: true } : {}),\n }]\n return {\n tool: 'edit',\n path,\n hunks,\n ...(priorContent !== undefined ? { priorContent } : {}),\n }\n }\n\n if (name === 'multi_edit') {\n const steps = input.edits\n if (!Array.isArray(steps) || steps.length === 0)\n return undefined\n const hunks: EditHunk[] = []\n for (const raw of steps as EditStepInput[]) {\n if (typeof raw?.old_string !== 'string' || typeof raw?.new_string !== 'string')\n return undefined\n hunks.push({\n oldString: raw.old_string,\n newString: raw.new_string,\n ...(raw.replace_all === true ? { replaceAll: true } : {}),\n })\n }\n return {\n tool: 'multi_edit',\n path,\n hunks,\n ...(priorContent !== undefined ? { priorContent } : {}),\n }\n }\n\n if (name === 'write_file') {\n const content = input.content\n if (typeof content !== 'string')\n return undefined\n const hunks: EditHunk[] = [{ oldString: priorContent ?? '', newString: content }]\n return {\n tool: 'write_file',\n path,\n hunks,\n ...(priorContent !== undefined ? { priorContent } : {}),\n }\n }\n\n return undefined\n}\n\n// ---------------------------------------------------------------------------\n// Line diff — LCS table walk, returns a sequence of `context | add | remove`\n// rows. Backed by the standard 2-D dynamic-programming LCS; O(n·m) time\n// and memory in the line counts of the two inputs. Edit blocks are short\n// (the agent's `old_string` / `new_string` rarely exceed a few dozen\n// lines), so we don't bother with the linear-space Hunt–Szymanski\n// optimization — clarity wins.\n//\n// Trailing newline policy: a string ending with `\\n` produces a trailing\n// empty `\"\"` line after split. We trim it so a single-line edit doesn't\n// emit a phantom empty `+` row below the actual content.\n// ---------------------------------------------------------------------------\n\nexport type DiffOp = 'context' | 'add' | 'remove'\n\nexport interface DiffLine {\n op: DiffOp\n text: string\n}\n\nexport function computeLineDiff(oldString: string, newString: string): DiffLine[] {\n const oldLines = splitLines(oldString)\n const newLines = splitLines(newString)\n\n // LCS table: lcs[i][j] = length of LCS of oldLines[0..i) and newLines[0..j).\n const n = oldLines.length\n const m = newLines.length\n const lcs: number[][] = Array.from({ length: n + 1 }, () => Array.from<number>({ length: m + 1 }).fill(0))\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < m; j++) {\n lcs[i + 1][j + 1] = oldLines[i] === newLines[j]\n ? lcs[i][j] + 1\n : Math.max(lcs[i][j + 1], lcs[i + 1][j])\n }\n }\n\n // Walk the table from (n, m) → (0, 0) recording operations. The walk\n // produces ops in reverse order, so the result is reversed at the\n // end. Diagonals at equal lines → context; otherwise step into the\n // higher of (up, left) and emit remove/add respectively. Ties break\n // toward `remove` so adjacent remove/add pairs stay grouped (the\n // intra-line highlighter pairs them up).\n const out: DiffLine[] = []\n let i = n\n let j = m\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {\n out.push({ op: 'context', text: oldLines[i - 1] })\n i--\n j--\n continue\n }\n if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) {\n out.push({ op: 'add', text: newLines[j - 1] })\n j--\n continue\n }\n out.push({ op: 'remove', text: oldLines[i - 1] })\n i--\n }\n out.reverse()\n return out\n}\n\n/**\n * Split a string into lines preserving empty lines but dropping the\n * implicit trailing `\"\"` produced by a final `\\n`. Exported only for\n * its tests — callers should use `computeLineDiff`.\n */\nexport function splitLines(s: string): string[] {\n if (s === '')\n return []\n const parts = s.split('\\n')\n if (parts[parts.length - 1] === '')\n parts.pop()\n return parts\n}\n\n// ---------------------------------------------------------------------------\n// Inline diff — word-level segments for a paired (remove, add) row.\n//\n// Same LCS recipe over **word tokens** so the renderer can bold the\n// genuinely-different runs inside each line. Tokenization keeps runs of\n// word characters + non-word characters as separate units so a rename\n// like `oldName → newName` highlights the single token rather than the\n// whole word boundary.\n//\n// Returns parallel segment arrays for the old and new lines — same\n// segment order, `changed: true` on the diverging tokens. Renderer\n// renders unchanged tokens in the line's base fg + changed tokens in\n// a brighter accent (with the row's bg still tinted).\n// ---------------------------------------------------------------------------\n\nexport interface InlineSegment {\n text: string\n changed: boolean\n}\n\nexport interface InlineDiff {\n oldSegments: InlineSegment[]\n newSegments: InlineSegment[]\n}\n\nexport function computeInlineDiff(oldLine: string, newLine: string): InlineDiff {\n const oldTokens = tokenize(oldLine)\n const newTokens = tokenize(newLine)\n\n const n = oldTokens.length\n const m = newTokens.length\n const lcs: number[][] = Array.from({ length: n + 1 }, () => Array.from<number>({ length: m + 1 }).fill(0))\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < m; j++) {\n lcs[i + 1][j + 1] = oldTokens[i] === newTokens[j]\n ? lcs[i][j] + 1\n : Math.max(lcs[i][j + 1], lcs[i + 1][j])\n }\n }\n\n // Walk table — same as computeLineDiff but emit segments tagged\n // changed/unchanged into the side they belong to.\n const oldSegments: InlineSegment[] = []\n const newSegments: InlineSegment[] = []\n let i = n\n let j = m\n while (i > 0 || j > 0) {\n if (i > 0 && j > 0 && oldTokens[i - 1] === newTokens[j - 1]) {\n pushSegment(oldSegments, { text: oldTokens[i - 1], changed: false })\n pushSegment(newSegments, { text: newTokens[j - 1], changed: false })\n i--\n j--\n continue\n }\n if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) {\n pushSegment(newSegments, { text: newTokens[j - 1], changed: true })\n j--\n continue\n }\n pushSegment(oldSegments, { text: oldTokens[i - 1], changed: true })\n i--\n }\n oldSegments.reverse()\n newSegments.reverse()\n return { oldSegments, newSegments }\n}\n\n/**\n * Coalesce adjacent same-state segments so the renderer emits one\n * `<span>` per run instead of one per token — keeps the React tree\n * shallow on dense lines without changing the visual output.\n *\n * Walking direction is reverse (we push during the reverse walk, then\n * the caller reverses the array), so we coalesce against the *tail*.\n */\nfunction pushSegment(buf: InlineSegment[], seg: InlineSegment): void {\n const tail = buf[buf.length - 1]\n if (tail && tail.changed === seg.changed)\n tail.text = seg.text + tail.text\n else\n buf.push(seg)\n}\n\n/**\n * Tokenize on word / non-word boundaries. Each run of `\\w+` is one\n * token; each run of `\\W+` (whitespace, punctuation) is another. This\n * gives the right granularity for renames (`oldName` → `newName`) and\n * for symbol swaps (`+ → -`) without exploding into per-char segments.\n *\n * Exported only for its tests.\n */\nexport function tokenize(s: string): string[] {\n if (s === '')\n return []\n const out: string[] = []\n // Greedy: alternating word / non-word runs. Avoids per-char tokens\n // which would inflate the LCS table on long lines.\n const re = /\\w+|\\W+/g\n for (const match of s.matchAll(re))\n out.push(match[0])\n return out\n}\n\n// ---------------------------------------------------------------------------\n// Unified diff serialization — feeds OpenTUI's native `<diff>` renderable.\n//\n// `<diff>` parses standard unified diff syntax (`--- a/path`, `+++ b/path`,\n// `@@ -l,n +l,m @@`, then `[ +-]<line>` rows) and handles per-language\n// syntax highlighting + bg coloring + wrap. We build the string from our\n// `EditPayload` and let the renderable do the rest.\n//\n// Line numbers in the chunk header are SYNTHETIC. `edit` only carries an\n// `old_string` snippet (no file position), `multi_edit` sequential edits\n// invalidate each other's positions, and `write_file` always starts at\n// line 1. Synthetic `@@ -1,N +1,M @@` is correct enough for the visual\n// gutter; users read the path in the header for context.\n//\n// New-file convention: when `oldString === ''` we emit `--- /dev/null`\n// and `@@ -0,0 +1,n @@`, matching `git diff` for newly added files.\n// ---------------------------------------------------------------------------\n\n/**\n * Apply the payload's hunks against `priorContent` and return the\n * resulting file body. Mirrors the agent's tool-side semantics:\n * - `replaceAll === true` → `String.replaceAll`\n * - otherwise → first-occurrence `String.replace`\n *\n * Hunks are applied in order — a `multi_edit` later hunk operates on\n * the output of the earlier ones, just like the actual tool.\n */\nexport function applyEditPayload(payload: EditPayload, priorContent: string): string {\n let out = priorContent\n for (const hunk of payload.hunks) {\n out = hunk.replaceAll\n ? out.replaceAll(hunk.oldString, hunk.newString)\n : out.replace(hunk.oldString, hunk.newString)\n }\n return out\n}\n\n/**\n * Like `buildUnifiedDiff` but operating against the full file content\n * so the diff carries *real* file line numbers and configurable\n * surrounding context.\n *\n * Strategy:\n * 1. Apply the payload to `priorContent` → `newContent`.\n * 2. Run `computeLineDiff` over the whole file.\n * 3. Group non-context ops into hunks, padding each with up to\n * `contextLines` of context above and below. Adjacent hunks\n * whose context regions touch are merged so we don't emit two\n * `@@` headers separated by zero context lines.\n *\n * The output line numbers in the `@@` header are 1-based and reflect\n * the change's position in the actual file — what the user expects\n * when reading a diff alongside their editor.\n *\n * For `write_file` creating a new file (priorContent === ''), this\n * falls back to the same `--- /dev/null` convention as\n * `buildUnifiedDiff`.\n */\nexport function buildContextualDiff(\n payload: EditPayload,\n priorContent: string,\n contextLines = 3,\n): string {\n const newContent = applyEditPayload(payload, priorContent)\n const isNewFile = priorContent === ''\n const ops = computeLineDiff(priorContent, newContent)\n\n // Pre-compute 1-based file line numbers for each op. `oldLine[i]` is\n // the original-file line `ops[i]` corresponds to (for context /\n // remove ops; undefined for adds since they don't exist in the old).\n // Same for `newLine[i]`. Add-only and remove-only ops still need a\n // base anchor for the hunk header, which we read off the *next*\n // context/anchor line — captured by tracking running counters.\n const oldLineFor: number[] = []\n const newLineFor: number[] = []\n let ol = 1\n let nl = 1\n for (const op of ops) {\n oldLineFor.push(ol)\n newLineFor.push(nl)\n if (op.op !== 'add')\n ol++\n if (op.op !== 'remove')\n nl++\n }\n\n // Group indices of change ops into hunks with `contextLines` padding.\n // Each entry: `[startIdx, endIdx]` inclusive into `ops`.\n const hunks: Array<[number, number]> = []\n for (let i = 0; i < ops.length; i++) {\n if (ops[i].op === 'context')\n continue\n const start = Math.max(0, i - contextLines)\n const end = Math.min(ops.length - 1, i + contextLines)\n const last = hunks[hunks.length - 1]\n if (last && start <= last[1] + 1)\n last[1] = Math.max(last[1], end)\n else\n hunks.push([start, end])\n }\n\n // No-op edit (e.g. `edit` with old_string === new_string) → empty diff.\n if (hunks.length === 0)\n return ''\n\n const parts: string[] = []\n parts.push(isNewFile ? '--- /dev/null' : `--- a/${payload.path}`)\n parts.push(`+++ b/${payload.path}`)\n\n for (const [start, end] of hunks) {\n const slice = ops.slice(start, end + 1)\n const oldCount = slice.filter(l => l.op !== 'add').length\n const newCount = slice.filter(l => l.op !== 'remove').length\n // For hunks that contain only adds (oldCount === 0), git emits the\n // line *before* the insertion, with count 0. We replicate that by\n // using `oldLineFor[start] - 1` clamped at 0.\n const oldStart = oldCount === 0\n ? Math.max(0, oldLineFor[start] - 1)\n : oldLineFor[start]\n const newStart = newCount === 0\n ? Math.max(0, newLineFor[start] - 1)\n : newLineFor[start]\n parts.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`)\n for (const line of slice) {\n const prefix = line.op === 'add' ? '+' : line.op === 'remove' ? '-' : ' '\n parts.push(`${prefix}${line.text}`)\n }\n }\n return `${parts.join('\\n')}\\n`\n}\n\n// ---------------------------------------------------------------------------\n// summarizeEditPayload — compact-mode digest of an edit call.\n//\n// Produces per-hunk stats + a one-line preview suitable for the\n// transcript's compact diff view (`Settings.editDiffDisplay === 'compact'`).\n// Uses `priorContent` when available so each summary entry carries the\n// real file line number; falls back to per-hunk LCS over the snippet\n// pair when priorContent is absent (historical replay).\n//\n// Multi-file safety: this only summarizes the payload it's handed; the\n// caller is responsible for separating per-file payloads.\n// ---------------------------------------------------------------------------\n\nexport interface EditHunkSummary {\n /** 1-based line number in the new file where the change starts; undefined when unknown (no priorContent). */\n line?: number\n /** Number of lines added in this hunk. */\n added: number\n /** Number of lines removed in this hunk. */\n removed: number\n /** First removed line preview (trimmed, may be empty for pure additions). */\n firstOld?: string\n /** First added line preview (trimmed, may be empty for pure deletions). */\n firstNew?: string\n}\n\nexport interface EditSummary {\n totalAdded: number\n totalRemoved: number\n hunks: EditHunkSummary[]\n}\n\n/**\n * Build a per-hunk digest used by the compact diff view.\n *\n * Strategy:\n * - When `priorContent` is present and the payload describes a real\n * file transformation, compute the contextual diff once, then walk\n * the LCS ops splitting at runs of `add` / `remove` to anchor each\n * summary entry to the **real** file line. This guarantees the\n * summary's `L<n>` matches what the user sees in their editor.\n * - Otherwise, fall back to per-hunk LCS over the (oldString,\n * newString) snippet pair. Line numbers are absent because the\n * snippet has no file position.\n */\nexport function summarizeEditPayload(payload: EditPayload): EditSummary {\n const prior = payload.priorContent\n if (prior !== undefined) {\n const newContent = applyEditPayload(payload, prior)\n const ops = computeLineDiff(prior, newContent)\n return summarizeOpsByHunk(ops)\n }\n\n // No prior content — diff each (oldString, newString) snippet pair\n // in isolation. Line numbers can't be derived; we still get\n // meaningful +/− counts and a first-line preview.\n const hunks: EditHunkSummary[] = []\n let totalAdded = 0\n let totalRemoved = 0\n for (const hunk of payload.hunks) {\n const ops = computeLineDiff(hunk.oldString, hunk.newString)\n let added = 0\n let removed = 0\n let firstOld: string | undefined\n let firstNew: string | undefined\n for (const op of ops) {\n if (op.op === 'add') {\n added++\n if (firstNew === undefined)\n firstNew = op.text\n }\n else if (op.op === 'remove') {\n removed++\n if (firstOld === undefined)\n firstOld = op.text\n }\n }\n totalAdded += added\n totalRemoved += removed\n hunks.push({\n added,\n removed,\n ...(firstOld !== undefined ? { firstOld } : {}),\n ...(firstNew !== undefined ? { firstNew } : {}),\n })\n }\n return { totalAdded, totalRemoved, hunks }\n}\n\n/**\n * Walk an LCS op stream and emit one summary entry per *run* of\n * non-context ops, with the new-file line number where each run\n * starts. Adjacent add/remove ops collapse into the same entry —\n * matches git's hunk grouping at zero context.\n */\nfunction summarizeOpsByHunk(ops: readonly DiffLine[]): EditSummary {\n const hunks: EditHunkSummary[] = []\n let totalAdded = 0\n let totalRemoved = 0\n let nl = 1\n let i = 0\n while (i < ops.length) {\n const op = ops[i]\n if (op.op === 'context') {\n nl++\n i++\n continue\n }\n const runStartLine = nl\n let added = 0\n let removed = 0\n let firstOld: string | undefined\n let firstNew: string | undefined\n while (i < ops.length && ops[i].op !== 'context') {\n const cur = ops[i]\n if (cur.op === 'add') {\n added++\n if (firstNew === undefined)\n firstNew = cur.text\n nl++\n }\n else {\n removed++\n if (firstOld === undefined)\n firstOld = cur.text\n }\n i++\n }\n totalAdded += added\n totalRemoved += removed\n hunks.push({\n line: runStartLine,\n added,\n removed,\n ...(firstOld !== undefined ? { firstOld } : {}),\n ...(firstNew !== undefined ? { firstNew } : {}),\n })\n }\n return { totalAdded, totalRemoved, hunks }\n}\n\n// ---------------------------------------------------------------------------\n// previewEditPayload — model-faithful preview that mirrors the tool body's\n// lenient resolver (curly-quote recovery, line-number-prefix stripping,\n// model-side `<n>`→`<name>` desanitize). The naive `applyEditPayload`\n// only does exact `String.replace`, which renders a blank diff whenever\n// the model emits an `old_string` the tool body would still recover via\n// `resolveOldString`. The modal uses this so what the user previews\n// matches what the tool would actually apply.\n//\n// Returns per-hunk `resolution` metadata so the modal can:\n// - mark hunks the tool wouldn't find (`resolved: false`) with a\n// warning glyph in the list,\n// - render an explanatory fallback panel instead of an empty diff box.\n// ---------------------------------------------------------------------------\n\nexport interface HunkResolution {\n /**\n * True when the tool body would find this hunk's `old_string` AND apply\n * it without ambiguity. False when `resolveOldString` returned null OR\n * the match was ambiguous (multiple occurrences and `replace_all` off).\n */\n resolved: boolean\n /** Path the resolver took — `'exact'` when no recovery was needed. */\n via?: ResolvedMatch['via']\n /** Match count in the running content for the hunk. */\n occurrences?: number\n /** True when the resolver found >1 match without `replace_all`. */\n ambiguous?: boolean\n}\n\nexport interface PreviewResult {\n /** Full unified diff of the (resolvable) hunks against `priorContent`. */\n diffText: string\n /** 1:1 with `payload.hunks`. */\n resolution: HunkResolution[]\n /**\n * Per-hunk isolated diff text rendered against the cumulative content\n * after applying all earlier resolved hunks. Useful for the modal's\n * focused-hunk view so hunk #N's preview reflects the state hunks\n * 1..N-1 will leave the file in (same as the tool's order).\n */\n perHunkDiff: string[]\n /**\n * Resolved hunks with `oldString` rewritten to the haystack's actual\n * bytes and `newString` re-styled to preserve curly-quote typography\n * / line-prefix conventions. Pass this to `buildContextualDiff` to\n * paint the unified diff.\n */\n resolvedPayload: EditPayload\n}\n\nexport function previewEditPayload(\n payload: EditPayload,\n priorContent: string,\n contextLines = 3,\n): PreviewResult {\n const resolution: HunkResolution[] = []\n const resolvedHunks: EditHunk[] = []\n const perHunkDiff: string[] = []\n let running = priorContent\n\n for (const hunk of payload.hunks) {\n // `write_file` synthesizes a hunk with `oldString = priorContent`,\n // which is always a perfect match by construction.\n if (hunk.oldString === '' || hunk.oldString === running) {\n resolution.push({ resolved: true, via: 'exact', occurrences: 1 })\n resolvedHunks.push(hunk)\n perHunkDiff.push(buildContextualDiff(\n { ...payload, hunks: [hunk] },\n running,\n contextLines,\n ))\n running = hunk.newString\n continue\n }\n\n const match = resolveOldString(running, hunk.oldString)\n if (!match) {\n resolution.push({ resolved: false })\n resolvedHunks.push(hunk)\n perHunkDiff.push('')\n continue\n }\n\n const ambiguous = match.occurrences > 1 && !hunk.replaceAll\n const styledNew = styleReplacementForVia(hunk.newString, match.via, match.actual)\n const resolvedHunk: EditHunk = {\n oldString: match.actual,\n newString: styledNew,\n ...(hunk.replaceAll ? { replaceAll: true } : {}),\n }\n resolution.push({\n resolved: !ambiguous,\n via: match.via,\n occurrences: match.occurrences,\n ...(ambiguous ? { ambiguous: true } : {}),\n })\n resolvedHunks.push(resolvedHunk)\n // Ambiguous matches paint nothing — the tool would reject the call;\n // the modal renders the `UnresolvedHunkPanel` instead so the user\n // can see what went wrong rather than a misleading first-occurrence\n // preview.\n perHunkDiff.push(ambiguous\n ? ''\n : buildContextualDiff(\n { ...payload, hunks: [resolvedHunk] },\n running,\n contextLines,\n ))\n if (!ambiguous) {\n running = hunk.replaceAll\n ? running.replaceAll(match.actual, styledNew)\n : running.replace(match.actual, styledNew)\n }\n }\n\n const resolvedPayload: EditPayload = { ...payload, hunks: resolvedHunks }\n // Whole-payload diff renders only the hunks the tool would actually\n // apply. Ambiguous + unresolved hunks are surfaced via `resolution`\n // instead — letting them through here would paint misleading\n // first-occurrence diffs.\n const applicableHunks = resolvedHunks.filter((_, i) => resolution[i].resolved)\n const diffText = applicableHunks.length === 0\n ? ''\n : buildContextualDiff(\n { ...payload, hunks: applicableHunks },\n priorContent,\n contextLines,\n )\n\n return { diffText, resolution, perHunkDiff, resolvedPayload }\n}\n\nexport function buildUnifiedDiff(payload: EditPayload): string {\n const parts: string[] = []\n const isNewFile = payload.tool === 'write_file' && payload.hunks[0]?.oldString === ''\n // File header. Both `---` and `+++` lines are mandatory per the parser\n // (see `Missing \"--- ...\"` throw in the upstream parseDiff).\n parts.push(isNewFile ? `--- /dev/null` : `--- a/${payload.path}`)\n parts.push(`+++ b/${payload.path}`)\n\n for (const hunk of payload.hunks) {\n const lines = computeLineDiff(hunk.oldString, hunk.newString)\n const oldCount = lines.filter(l => l.op !== 'add').length\n const newCount = lines.filter(l => l.op !== 'remove').length\n const oldStart = oldCount === 0 ? 0 : 1\n const newStart = newCount === 0 ? 0 : 1\n parts.push(`@@ -${oldStart},${oldCount} +${newStart},${newCount} @@`)\n for (const line of lines) {\n const prefix = line.op === 'add' ? '+' : line.op === 'remove' ? '-' : ' '\n parts.push(`${prefix}${line.text}`)\n }\n }\n return `${parts.join('\\n')}\\n`\n}\n\n// ---------------------------------------------------------------------------\n// File extension → tree-sitter filetype name.\n//\n// Names must match what `setupTreeSitter` registered (`bash`, `python`,\n// `rust`, `go`, `json`, `yaml`, `html`, `css`) plus what OpenTUI ships\n// out of the box (`typescript`, `tsx`, `javascript`, `jsx`, `markdown`).\n// Unknown extensions return `undefined` — the `<diff>` renderable falls\n// back to plain text rendering without highlighting (no error, no crash).\n// ---------------------------------------------------------------------------\n\nconst FILETYPE_BY_EXT: Readonly<Record<string, string>> = {\n ts: 'typescript',\n mts: 'typescript',\n cts: 'typescript',\n tsx: 'tsx',\n js: 'javascript',\n mjs: 'javascript',\n cjs: 'javascript',\n jsx: 'jsx',\n py: 'python',\n pyi: 'python',\n rs: 'rust',\n go: 'go',\n json: 'json',\n jsonc: 'json',\n sh: 'bash',\n bash: 'bash',\n zsh: 'bash',\n yaml: 'yaml',\n yml: 'yaml',\n html: 'html',\n htm: 'html',\n css: 'css',\n md: 'markdown',\n markdown: 'markdown',\n}\n\nexport function filetypeFromPath(path: string): string | undefined {\n // Strip query / fragment so `path?ts=…` style references still match.\n const cleaned = path.split(/[?#]/, 1)[0]\n const lastDot = cleaned.lastIndexOf('.')\n if (lastDot === -1 || lastDot === cleaned.length - 1)\n return undefined\n const ext = cleaned.slice(lastDot + 1).toLowerCase()\n return FILETYPE_BY_EXT[ext]\n}\n","import type { SessionRun, SessionStore } from '../session'\nimport type { SessionTurn, ThinkingLevel, ToolResultContent } from '../types'\nimport type { ProviderKey } from './auth'\nimport type { EditOutcome, SessionMeta, Settings, StreamEvent } from './types'\nimport { Buffer } from 'node:buffer'\nimport { existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { formatTokenUsage } from '../stats'\nimport { toolResultToText } from '../types'\nimport { parseEditOutcomesFromResult } from './edit-approval'\nimport { extractEditPayload } from './edit-diff'\nimport { previewLine } from './format'\n\n// NOTE: `createTuiStore` moved to `src/session/sqlite.ts` so this module —\n// the public `zidane/chat` entry — does NOT statically import `bun:sqlite`.\n// A future GUI consumer that imports `zidane/chat` no longer transitively\n// pulls in the Bun-only sqlite binding. Hosts construct their own store\n// (e.g. via `createTuiStore` from `zidane/session/sqlite` for terminal hosts)\n// and pass it through `runTui({ store })` / `resolveConfig({ store })`.\n\nfunction ensureStateDir(path: string): void {\n const dir = dirname(path)\n if (existsSync(dir))\n return\n try {\n mkdirSync(dir, { recursive: true })\n }\n catch (err) {\n throw new Error(\n `Could not create TUI state directory at \"${dir}\". `\n + `Override the location via \\`runTui({ storageDir, prefix })\\` or the `\n + `\\`ZIDANE_STORAGE_DIR\\` env var. Original error: ${errorMessage(err)}`,\n )\n }\n}\n\n// ---------------------------------------------------------------------------\n// Persisted UI state — what to resume on next launch.\n//\n// `StateStoreApi` is a thin facade that binds load/save to a specific JSON\n// path, so the rest of the UI can persist state without knowing the layout.\n// ---------------------------------------------------------------------------\n\nexport interface TuiState {\n lastProvider?: ProviderKey\n lastSessionId?: string\n /** Per-provider last-used model id. */\n lastModelByProvider?: Partial<Record<ProviderKey, string>>\n /**\n * Per-model last-picked reasoning effort. Keyed by model id (not provider)\n * so switching the same model across providers preserves the user's\n * intent. Models without reasoning support are absent from the map.\n */\n lastEffortByModel?: Record<string, ThinkingLevel>\n /**\n * Last-active agent profile id (see {@link AgentRegistry}). Resumed on\n * launch so a returning user lands back in the same mode (Build / Plan /\n * a host profile). Falls back to the host's `defaultAgent` then the first\n * registry key if the id is no longer registered.\n */\n lastAgent?: string\n /** User-toggled transcript filters. Persisted so they outlive a launch. */\n settings?: Partial<Settings>\n}\n\nexport interface StateStoreApi {\n load: () => TuiState\n save: (state: TuiState) => void\n}\n\nexport function createStateStore(path: string): StateStoreApi {\n return {\n load: () => loadState(path),\n save: state => saveState(path, state),\n }\n}\n\nexport function loadState(path: string): TuiState {\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as TuiState\n }\n catch {\n // Corrupt state file → treat as absent so the user can reach the auth screen.\n }\n return {}\n}\n\nexport function saveState(path: string, state: TuiState): void {\n ensureStateDir(path)\n writeFileAtomic(path, JSON.stringify(state, null, 2))\n}\n\n// Re-export the model-info shape used by views below so callers don't have to\n// pull from `./config` for transcript helpers alone.\nexport type { ModelInfo } from './config'\n\n// ---------------------------------------------------------------------------\n// Session view helpers — derive UI-friendly shapes from persisted turns.\n// ---------------------------------------------------------------------------\n\n/**\n * Load every session and project it to the compact `SessionMeta` shape used by\n * the picker. Sorted by recency via the underlying store's `list()` contract\n * (sqlite store returns by `updated_at DESC`).\n *\n * Robust to per-row failures: `Promise.allSettled` so a single corrupt or\n * unreadable row (malformed JSON, partial write, transient I/O error)\n * doesn't take down the entire picker. Failed rows are silently skipped;\n * a stderr line is emitted under `ZIDANE_DEBUG` for diagnosis.\n */\nexport async function listSessionMeta(\n store: SessionStore,\n filter?: {\n /**\n * Restrict to sessions belonging to this project. Omit to ignore\n * the axis; pass `null` to ask for untagged (legacy) sessions\n * specifically. The TUI defaults to passing the current git root /\n * cwd here so each project sees only its own conversations.\n */\n projectRoot?: string | null\n },\n): Promise<SessionMeta[]> {\n const ids = await store.list(filter)\n const settled = await Promise.allSettled(ids.map(async (id) => {\n const data = await store.load(id)\n if (!data)\n return null\n return {\n id,\n title: deriveSessionTitle(data.turns, data.metadata),\n turnCount: data.turns.length,\n userMessageCount: data.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: data.runs.length,\n ...(data.projectRoot ? { projectRoot: data.projectRoot } : {}),\n updatedAt: data.updatedAt,\n }\n }))\n const metas: SessionMeta[] = []\n for (let i = 0; i < settled.length; i++) {\n const result = settled[i]\n if (result.status === 'fulfilled' && result.value) {\n metas.push(result.value)\n }\n else if (result.status === 'rejected' && process.env.ZIDANE_DEBUG) {\n const cause = result.reason instanceof Error ? result.reason.message : String(result.reason)\n process.stderr.write(`[zidane/chat] failed to load session \"${ids[i]}\": ${cause}\\n`)\n }\n }\n return metas\n}\n\n/** Derive a short title from the first user message — returns null when empty. */\nexport function titleFromTurns(turns: SessionTurn[]): string | null {\n const first = turns.find(t => t.role === 'user')\n if (!first)\n return null\n for (const block of first.content) {\n if (block.type === 'text' && block.text.trim()) {\n const oneLine = block.text.replace(/\\s+/g, ' ').trim()\n return oneLine.length > 60 ? `${oneLine.slice(0, 60)}…` : oneLine\n }\n }\n return null\n}\n\n/**\n * Display title for a session — preferred sources, in order:\n *\n * 1. `metadata.title` if it's a non-empty string (typically set by\n * the session-details modal's \"generate title\" action).\n * 2. {@link titleFromTurns} — the first user message, one-line + clipped.\n * 3. The literal `'untitled'`.\n *\n * Total / pure / defensive on non-string metadata values.\n */\nexport function deriveSessionTitle(\n turns: SessionTurn[],\n metadata?: Record<string, unknown>,\n): string {\n const stored = typeof metadata?.title === 'string' ? metadata.title.trim() : ''\n if (stored.length > 0)\n return stored\n return titleFromTurns(turns) ?? 'untitled'\n}\n\n/**\n * Replay persisted turns as a viewable transcript. Mirrors the event shape\n * produced live by the agent hooks so loaded and streaming history render\n * identically — including subagent ancestry when `runs` is supplied.\n *\n * Subagent reconstruction:\n * - Every turn carries a `runId`. We look that up in `runs` to get the\n * run's `depth` and tag the resulting events with `{ depth, childId }`\n * — the same shape the live `child:*` bubble hooks produce.\n * - We synthesize `spawn-start` / `spawn-end` markers at each child-run\n * boundary so the transcript reads the same as a live run did\n * (`🌱 [run-id] task` … child events … `🌳 [run-id] done · tokens`).\n * - For child runs (`depth > 0`), the user-role \"task\" text is suppressed\n * because `spawn-start` already shows it.\n *\n * Without `runs` (legacy callers / tests), the function falls back to the\n * old behavior: depth-0 events with no subagent grouping.\n */\nexport function eventsFromTurns(\n turns: SessionTurn[],\n runs: readonly SessionRun[] = [],\n): StreamEvent[] {\n // First-write-wins on duplicate ids. Historical sessions persisted\n // before the per-mint `initialRunCounter` re-sync (see `src/agent.ts`)\n // could have a child agent and its parent both registering runs with\n // the SAME `run_<n>` id — the subagent ran first (lands first in\n // `runs[]`), the parent's collision came later (lands last).\n // Last-write-wins would resolve the id to the collision (depth=0,\n // no parentRunId), the structural ancestry walk would return empty,\n // and the renderer would paint a subagent's transcript at depth 0:\n // indent gets crazy, spawn markers placed wrong, the whole block\n // looks \"off\". Preferring the FIRST entry keeps the depth + parent\n // info from the subagent record, which is what every turn under\n // that runId actually wants. Clean sessions have one entry per id,\n // so first/last makes no difference.\n const runById = new Map<string, SessionRun>()\n for (const run of runs) {\n if (!runById.has(run.id))\n runById.set(run.id, run)\n }\n\n // Defensive synthesis for legacy sessions whose `runs[]` is missing\n // entries the turns reference (e.g. the pre-fix `updateRun`-not-\n // upsert bug in the sqlite/memory stores dropped subagent records\n // silently). Without it, an orphan turn's `ancestryOf()` returns an\n // empty chain → depth = 0 → the subagent's user-text-block leaks\n // as a `user-prompt` event and pollutes the TUI's history.\n //\n // Inference is structural: walk turns chronologically, maintain a\n // stack of currently-open runIds, and decide whether a NEW runId\n // is nested or top-level based on whether the stack-top run has any\n // unresolved tool_calls. A nested runId pushes (depth grows); a\n // top-level runId resets the stack first. Tracked per-runId in\n // `pendingToolCallsPerRun` — incremented on `tool_call` blocks,\n // decremented on `tool_result` blocks.\n //\n // Without the nesting gate, the naive \"push every new runId, never\n // pop\" walk treated back-to-back top-level prompts as a deepening\n // chain of subagents, and synthesized depth grew unbounded —\n // `indentFor(36)` = 72 cells, which is the far-right rendering the\n // user reported for a session with many top-level chats plus one\n // missing run record. Real records (present in `runById`) are NEVER\n // overwritten — synthesis only fills the gap.\n const runStack: string[] = []\n const pendingToolCallsPerRun = new Map<string, Set<string>>()\n for (const turn of turns) {\n const rid = turn.runId\n if (!rid)\n continue\n const idxInStack = runStack.indexOf(rid)\n if (idxInStack >= 0) {\n // Already open — pop any deeper nesting that's now closed.\n runStack.length = idxInStack + 1\n }\n else {\n // New runId — nested only when the parent has a pending\n // tool_call (the spawn that's waiting on a subagent).\n const top = runStack[runStack.length - 1]\n const parentHasPending = top !== undefined && (pendingToolCallsPerRun.get(top)?.size ?? 0) > 0\n if (!parentHasPending) {\n // Fresh top-level — drop the stack so a long chat history\n // doesn't accumulate phantom depth.\n runStack.length = 0\n }\n runStack.push(rid)\n\n if (!runById.has(rid)) {\n // Missing record — synthesize. Depth = stack depth - 1 (zero-based).\n // Prompt: the first user text-block on this turn (when present)\n // so the `child-N` spawn-start marker renders with a meaningful\n // preview rather than an empty string. Empty when the first\n // turn isn't a user turn or has no text block — `pushSpawnStart`\n // then falls through to the bare label.\n const depth = runStack.length - 1\n const parentRunId = depth > 0 ? runStack[depth - 1] : undefined\n let inferredPrompt = ''\n if (turn.role === 'user') {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim()) {\n inferredPrompt = block.text\n break\n }\n }\n }\n runById.set(rid, {\n id: rid,\n startedAt: turn.createdAt,\n prompt: inferredPrompt,\n status: 'completed',\n depth,\n ...(parentRunId ? { parentRunId } : {}),\n })\n }\n }\n\n // Update the pending-tool-calls set for this turn's runId. Tracked\n // here (not in the synthesis branch alone) so we have an accurate\n // running view for every turn — the stack-top check above reads\n // this for the next iteration.\n const pending = pendingToolCallsPerRun.get(rid) ?? new Set<string>()\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n pending.add(block.id)\n else if (block.type === 'tool_result')\n pending.delete(block.callId)\n }\n pendingToolCallsPerRun.set(rid, pending)\n }\n\n // Build a chronological mapping from the persisted `runId` (e.g. `run_2`) to\n // a friendly `child-N` label that mirrors what the spawn tool emits live\n // (see `src/tools/spawn.ts`'s local counter). Without this the reloaded\n // transcript labels its subagent block `[run_2]` while the persisted tool\n // result text inside that very block says `[sub-agent child-1]`.\n //\n // Reads from `runById` (NOT the raw `runs` param) so synthesized\n // fallback runs from the legacy-sessions block above also get a\n // proper `child-N` label.\n const childLabelByRunId = new Map<string, string>()\n const childRuns = [...runById.values()]\n .filter(r => (r.depth ?? 0) > 0)\n .sort((a, b) => a.startedAt - b.startedAt)\n childRuns.forEach((r, i) => childLabelByRunId.set(r.id, `child-${i + 1}`))\n const labelFor = (runId: string) => childLabelByRunId.get(runId) ?? runId\n\n // Build a `callId → toolName` map from the assistant `tool_call` blocks so\n // we can tag each user-role `tool_result` event with the tool it answers.\n // The renderer uses this for tool-aware filters (e.g. hiding the spawn\n // tool's result when `hideSubagentOutput` is on).\n const toolByCallId = new Map<string, string>()\n for (const turn of turns) {\n if (turn.role !== 'assistant')\n continue\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n toolByCallId.set(block.id, block.name)\n }\n }\n\n // Build a `callId → resultText` map so the replay path can attach per-\n // edit outcomes back onto the corresponding `tool` event. Live capture\n // mutates `_outcomes` on input before tool execution; on disk, only the\n // tool_result body carries the post-decision shape, so the replay\n // parses it (see `parseEditOutcomesFromResult`).\n const resultByCallId = new Map<string, string>()\n for (const turn of turns) {\n if (turn.role !== 'user')\n continue\n for (const block of turn.content) {\n if (block.type === 'tool_result') {\n resultByCallId.set(\n block.callId,\n typeof block.output === 'string' ? block.output : toolResultToText(block.output),\n )\n }\n }\n }\n\n // Ancestry chain for a turn's run — `[root child, …, the turn's own run]`,\n // excluding any depth-0 (top-level) ancestor since top-level runs don't\n // render spawn-start/spawn-end markers. Used by the stack-based\n // transition logic below so depth ≥ 2 (grandchild) sequences don't\n // emit duplicate `spawn-start` / premature `spawn-end` markers when\n // walking back up the tree. parent→A→B→A→parent walks cleanly:\n // T1 ancestry [] → no transition\n // T3 ancestry [A] → open A\n // T5 ancestry [A, B] → open B (A stays)\n // T7 ancestry [A] → close B (A stays)\n // T9 ancestry [] → close A\n const ancestryOf = (turnRunId: string | undefined): SessionRun[] => {\n if (!turnRunId)\n return []\n const chain: SessionRun[] = []\n let cursor: SessionRun | undefined = runById.get(turnRunId)\n const seen = new Set<string>()\n while (cursor) {\n // Defensive cycle guard — a corrupt run row pointing at its own\n // ancestor would otherwise loop forever.\n if (seen.has(cursor.id))\n break\n seen.add(cursor.id)\n if ((cursor.depth ?? 0) > 0)\n chain.unshift(cursor)\n cursor = cursor.parentRunId ? runById.get(cursor.parentRunId) : undefined\n }\n return chain\n }\n\n const events: StreamEvent[] = []\n /** Currently-open child runs, root-of-tree first, innermost last. */\n const openStack: SessionRun[] = []\n /** True iff anything has been emitted at the active depth-0 level. Used to decide separators between top-level turns. */\n let lastDepthAtEmission = -1\n\n const pushSpawnEnd = (run: SessionRun) => {\n const tag = run.status === 'aborted' || run.status === 'error' ? run.status : 'done'\n // Same caching gotcha as the live path: `tokensIn` mirrors\n // `AgentStats.totalIn` (new uncached only). `formatTokenUsage` folds\n // cache reads/creations into the headline `in` number.\n const usage = formatTokenUsage({\n totalIn: run.tokensIn ?? run.totalUsage?.input ?? 0,\n totalOut: run.tokensOut ?? run.totalUsage?.output ?? 0,\n totalCacheRead: run.totalUsage?.cacheRead ?? 0,\n totalCacheCreation: run.totalUsage?.cacheCreation ?? 0,\n })\n events.push({\n kind: 'spawn-end',\n text: `${tag} ${usage}`,\n childId: labelFor(run.id),\n depth: run.depth ?? 1,\n })\n }\n\n const pushSpawnStart = (run: SessionRun) => {\n // Single-line preview — newlines / tabs in the prompt collapse to\n // single spaces so the spawn-start marker stays on one visual row.\n // See the matching comment in `app.tsx`'s `spawn:before` hook for\n // the failure mode (multi-line prompt prints the marker across\n // multiple visual rows, the next event lands far right).\n const taskPreview = previewLine(run.prompt, 80)\n events.push({\n kind: 'spawn-start',\n text: taskPreview,\n childId: labelFor(run.id),\n depth: run.depth ?? 1,\n })\n }\n\n for (let i = 0; i < turns.length; i++) {\n const turn = turns[i]\n const targetChain = ancestryOf(turn.runId)\n const depth = targetChain.length // 0 for top-level, otherwise innermost depth\n\n // Diff the open stack against the turn's ancestry chain. Common\n // prefix stays; anything beyond in the stack is closed, anything\n // beyond in the chain is opened. Single-shot for depth = 1 (matches\n // the old behavior); multi-shot for depth ≥ 2 (the case the prior\n // implementation got wrong).\n let common = 0\n while (\n common < openStack.length\n && common < targetChain.length\n && openStack[common].id === targetChain[common].id\n ) {\n common++\n }\n while (openStack.length > common) {\n const run = openStack.pop()!\n pushSpawnEnd(run)\n }\n while (openStack.length < targetChain.length) {\n const run = targetChain[openStack.length]\n pushSpawnStart(run)\n openStack.push(run)\n }\n\n const subagentTag = depth > 0 && turn.runId ? { childId: labelFor(turn.runId), depth } : undefined\n // `turnId` tags every event so the TUI's select-turn mode can highlight\n // all events emitted by a single SessionTurn. Subagent ancestry stays in\n // a separate object so the spread order keeps existing tag overrides\n // (childId/depth) wins-last semantics intact.\n const tag = { turnId: turn.id, ...subagentTag }\n\n if (turn.role === 'user') {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim()) {\n // Background-task completion notifications ride as text\n // blocks carrying `<task-notification>` XML (the format\n // `renderTaskNotificationXml` produces). Detect and emit\n // them as their own structured event so the TUI can render\n // a banner — and IMPORTANT: DON'T also emit a `user-prompt`\n // event for the same text, otherwise the raw XML would\n // double-paint alongside the banner. Same dedupe pattern\n // the compact-summary path uses.\n // Pass the FULL tag (turnId + optional childId/depth) so a\n // subagent's task notification renders under the right\n // `child-N` lane instead of bleeding up to depth 0.\n const taskEvent = parseTaskNotificationBlock(block.text, tag)\n if (taskEvent) {\n events.push(taskEvent)\n continue\n }\n\n // For child runs the \"user\" text is the spawn task itself, already\n // rendered as the `spawn-start` marker above. Drop the duplicate.\n //\n // `user-prompt` carries the **raw** text; the renderer prepends the\n // `❯ ` chevron. No prefix in the payload means `refs` offsets stay\n // raw (no shift), and consumers building user-message indices or\n // history can read `event.text` directly.\n if (depth === 0) {\n // Separator BEFORE the user prompt — matches the live\n // behavior (see `app.tsx`'s prompt-commit path, which only\n // appends a separator when committing a new prompt). The\n // previous \"fire on every depth-0 turn pair\" rule was too\n // aggressive: it inserted a separator between every\n // mid-exchange assistant tool turn and its paired\n // user-side tool_result turn, producing a 2-row gap\n // between consecutive `todowrite` calls in the replayed\n // transcript that's absent during the live cascade.\n if (lastDepthAtEmission === 0)\n events.push({ kind: 'separator', text: '' })\n\n // Reconstruct attachment metadata from sibling content\n // blocks so the transcript shows chips after session reload.\n const attachments: { name: string, mediaType: string, size: number }[] = []\n for (const sibling of turn.content) {\n if (sibling.type === 'image') {\n const raw = typeof sibling.data === 'string'\n ? Buffer.from(sibling.data, 'base64')\n : sibling.data\n attachments.push({\n name: sibling.name ?? 'image',\n mediaType: sibling.mediaType,\n size: raw.length,\n })\n }\n else if (sibling !== block && sibling.type === 'text') {\n const m = sibling.text.match(/^<attachment\\s+(?:name=\"([^\"]+)\"\\s*)?(?:media_type=\"([^\"]+)\")?/)\n if (m) {\n attachments.push({\n name: m[1] ?? 'attachment',\n mediaType: m[2] ?? 'text/plain',\n size: sibling.text.length,\n })\n }\n }\n }\n\n events.push({\n kind: 'user-prompt',\n text: block.text,\n turnId: turn.id,\n ...(attachments.length > 0 ? { attachments } : {}),\n })\n }\n }\n else if (block.type === 'tool_result') {\n const tool = toolByCallId.get(block.callId)\n // Spawn tool-results duplicate the spawn-end marker's `Tokens: …`\n // line — and on pre-fix sessions the legacy \"N in / M out\" shape\n // disagrees with the cache-aware spawn-end. Strip it at the\n // display layer; the persisted output stays intact.\n const raw = toolResultText(block.output)\n const text = tool === 'spawn' ? stripSpawnTokensLine(raw) : raw\n events.push({\n kind: 'tool-result',\n text,\n ...(tool ? { tool } : {}),\n callId: block.callId,\n ...tag,\n })\n }\n else if (block.type === 'compact-summary') {\n // Compaction boundary — single event per marker block. The\n // renderer reads `compact` for the metadata badge (model,\n // usage, replaced-turn count) and `text` for the summary body\n // displayed in the collapsible card. Subagents never produce\n // these markers (compaction targets the top-level conversation),\n // so the subagent ancestry tag is intentionally not threaded\n // through — but `turnId` is, so select-turn mode still highlights.\n events.push({\n kind: 'compact-summary',\n text: block.summary,\n compact: {\n replacedCount: block.replacesTurnIds.length,\n model: block.model,\n compactedAt: block.compactedAt,\n inputTokens: block.usage.input ?? 0,\n outputTokens: block.usage.output ?? 0,\n cacheReadTokens: block.usage.cacheRead ?? 0,\n cacheCreationTokens: block.usage.cacheCreation ?? 0,\n },\n turnId: turn.id,\n })\n }\n }\n lastDepthAtEmission = depth\n continue\n }\n\n if (turn.role === 'assistant') {\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim()) {\n // Persisted turns are finalized — `streaming: false` so the markdown\n // renderer commits to the final layout (no heal pass needed).\n events.push({ kind: 'markdown', text: block.text, streaming: false, ...tag })\n }\n else if (block.type === 'tool_call') {\n // Historical replay has no pre-write snapshot for `write_file`\n // (the captured priorContent only exists at live-hook time).\n // `extractEditPayload` falls back to `oldString: ''` so the\n // diff renders as an all-add view — matches git's \"new file\"\n // convention and is the most honest reconstruction available.\n let edit = extractEditPayload(block.name, block.input)\n if (edit) {\n // Attach per-edit outcomes parsed out of the paired tool_result\n // body so a replayed transcript shows the same applied / denied\n // / skipped badges the live run displayed. Falls through when\n // the body is unstructured (legacy results, error preamble) —\n // the renderer treats absent outcomes as \"all applied\".\n const resultText = resultByCallId.get(block.id)\n if (resultText) {\n const outcomes = parseEditOutcomesFromResult(resultText)\n if (outcomes && outcomes.length === edit.hunks.length)\n edit = { ...edit, outcomes }\n }\n }\n events.push({\n kind: 'tool',\n text: toolCallPreview(block.name, block.input),\n tool: block.name,\n input: block.input,\n ...(edit ? { edit } : {}),\n callId: block.id,\n ...tag,\n })\n }\n }\n lastDepthAtEmission = depth\n }\n }\n\n // Drain any trailing open child runs (e.g. session ended mid-spawn). Pop\n // innermost-first so depth ≥ 2 chains emit `spawn-end` in the right order.\n while (openStack.length > 0) {\n const run = openStack.pop()!\n pushSpawnEnd(run)\n }\n\n return events\n}\n\n/** Shared formatter for the `↳ name(args)` line shown on tool calls. */\n/**\n * Pattern matching the leading `<task-notification>` shape that the\n * agent's notification-injection path produces (see\n * `renderTaskNotificationXml` in `src/agent.ts`). Anchored so a stray\n * notification-shaped phrase mid-prompt doesn't trigger a false-positive\n * detection — the model's wire format always starts the user-turn\n * content block with this exact opening tag.\n */\nconst TASK_NOTIFICATION_RE = /^\\s*<task-notification>([\\s\\S]*?)<\\/task-notification>\\s*$/\n\n/**\n * Per-field extractors. Compiled once at module load (parsing happens\n * on every session replay, and a fresh `new RegExp` per pick burns\n * cycles for no reason).\n */\nconst TASK_NOTIFICATION_FIELD_RES = {\n taskId: /<task-id>([\\s\\S]*?)<\\/task-id>/,\n status: /<status>([\\s\\S]*?)<\\/status>/,\n exitCode: /<exit-code>([\\s\\S]*?)<\\/exit-code>/,\n command: /<command>([\\s\\S]*?)<\\/command>/,\n outputFile: /<output-file>([\\s\\S]*?)<\\/output-file>/,\n durationMs: /<duration-ms>([\\s\\S]*?)<\\/duration-ms>/,\n summary: /<summary>([\\s\\S]*?)<\\/summary>/,\n} as const\n\n/**\n * Detect a `<task-notification>` text block on replay and convert it\n * into a structured `task-notification` StreamEvent so the TUI's banner\n * renderer can paint it cleanly. Returns `null` when the block isn't a\n * notification (the caller falls back to the normal user-prompt path).\n *\n * Reads every field DIRECTLY from its tag — does NOT parse `<summary>`\n * for the underlying data. The summary is a derived display string,\n * not a source of truth: changing its format (localization, signal in\n * the label, whatever) must NEVER break replay. Each field falls back\n * to a safe default so older transcripts pre-dating a tag addition\n * still render.\n */\nfunction parseTaskNotificationBlock(\n text: string,\n tag: { turnId: string, childId?: string, depth?: number },\n): StreamEvent | null {\n const m = text.match(TASK_NOTIFICATION_RE)\n if (!m)\n return null\n\n const body = m[1]\n const pick = (re: RegExp): string | undefined => {\n const inner = body.match(re)\n return inner ? unescapeXml(inner[1].trim()) : undefined\n }\n\n const taskId = pick(TASK_NOTIFICATION_FIELD_RES.taskId) ?? '?'\n const statusRaw = pick(TASK_NOTIFICATION_FIELD_RES.status) ?? 'exited'\n const status: 'exited' | 'killed' = statusRaw === 'killed' ? 'killed' : 'exited'\n const exitCode = Number.parseInt(pick(TASK_NOTIFICATION_FIELD_RES.exitCode) ?? '0', 10) || 0\n const command = pick(TASK_NOTIFICATION_FIELD_RES.command) ?? ''\n const outputPath = pick(TASK_NOTIFICATION_FIELD_RES.outputFile) ?? ''\n const durationMs = Number.parseInt(pick(TASK_NOTIFICATION_FIELD_RES.durationMs) ?? '0', 10) || 0\n const summary = pick(TASK_NOTIFICATION_FIELD_RES.summary)\n ?? `${taskId} ${statusRaw}${exitCode ? ` ${exitCode}` : ''}`\n\n return {\n kind: 'task-notification',\n text: summary,\n task: { taskId, status, exitCode, outputPath, command, durationMs },\n turnId: tag.turnId,\n ...(tag.childId !== undefined ? { childId: tag.childId } : {}),\n ...(tag.depth !== undefined ? { depth: tag.depth } : {}),\n }\n}\n\n/**\n * Reverse of `escapeXml` for the small set of entities the writer\n * emits. Not a full HTML entity decoder — just enough to round-trip\n * `<` / `>` / `&` / quotes through persistence.\n *\n * `&amp;` MUST be replaced last, otherwise `&amp;lt;` becomes `&lt;`\n * then `<` (data corruption — silent double-decode).\n */\nfunction unescapeXml(s: string): string {\n return s\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&apos;/g, '\\'')\n .replace(/&amp;/g, '&')\n}\n\nexport function toolCallPreview(name: string, input: Record<string, unknown>): string {\n const args = JSON.stringify(input)\n return args && args !== '{}' ? `${name}(${args})` : name\n}\n\n/**\n * Update the trailing `'tool'` event matching `callId` so its\n * `edit.outcomes` reflects the canonical merged outcomes the chat\n * layer's `tool:transform` hook just computed. Without this, the live\n * diff badges stay frozen at whatever the gate set (approval-side\n * outcomes) and never reflect body-side failures from `multi_edit`'s\n * best-effort run — only a session reload re-attaches the merged\n * outcomes via {@link eventsFromTurns}.\n *\n * Walks back-to-front because the matching event is overwhelmingly the\n * most recent (we're updating right after the body returned). Returns\n * the same reference when no match is found (or the event already\n * carries the target outcomes), so React skips the re-render.\n */\nexport function updateToolEventOutcomes(\n events: readonly StreamEvent[],\n callId: string,\n outcomes: readonly EditOutcome[],\n): StreamEvent[] {\n for (let i = events.length - 1; i >= 0; i--) {\n const e = events[i]\n if (e.kind !== 'tool' || e.callId !== callId || !e.edit)\n continue\n if (e.edit.outcomes === outcomes)\n return events as StreamEvent[]\n const next = events.slice()\n next[i] = { ...e, edit: { ...e.edit, outcomes } }\n return next\n }\n return events as StreamEvent[]\n}\n\n/** Render tool output as plain text, whether it's a string or structured content. */\nexport function toolResultText(output: string | ToolResultContent[]): string {\n return typeof output === 'string' ? output : toolResultToText(output)\n}\n\n/**\n * Strip the `Tokens: …` line from a spawn tool-result. The spawn-end marker\n * displayed right above already shows the same stats; keeping the line in the\n * rendered tool-result body just produces a visible duplicate (and, on\n * reloaded pre-fix sessions, an *inconsistent* duplicate — the persisted line\n * uses the old `13 in / 4075 out` shape while the freshly synthesized\n * spawn-end uses the cache-aware `in 92615 (cache 92602) / 4075 out` shape).\n *\n * Display-only: the persisted tool_result content is untouched, so the LLM\n * still sees the full string in its context window. Anchored to start-of-line\n * and matches both `Tokens: 13 in / 4075 out` (legacy) and `Tokens: in 13 …`\n * (post-`formatTokenUsage`) shapes.\n */\nexport function stripSpawnTokensLine(text: string): string {\n // Strict: only strip when the `Tokens: …` line is **immediately preceded\n // by** the spawn-tool header (`[sub-agent <id>] (Completed|Aborted|Failed|Timed) …`).\n // The previous `^Tokens:` multi-line regex would eat any body line that\n // happened to start with `Tokens:` — e.g. a model summarizing\n // \"Tokens: …\" inside its response. Anchored matching keeps the strip\n // local to the spawn header even if the model's response continues with\n // arbitrary content.\n //\n // `m` + `g` so every header in a multi-spawn result is handled, not just\n // the one at index 0. Today every spawn tool-result is single-spawn so\n // `g` is a no-op; the multiline anchor lets a future feature concatenating\n // multiple spawn outputs into one body strip every header's tokens line.\n return text.replace(\n /(^\\[sub-agent [^\\n]+\\n)Tokens:[^\\n]*\\n?/gm,\n '$1',\n )\n}\n\n// ---------------------------------------------------------------------------\n// Transcript visibility + spacing — pure data rules consumed by any\n// renderer that walks a `StreamEvent[]`. Live in chat layer because they\n// encode the user-facing semantics (settings + edit-tool error prefixes)\n// rather than any presentation primitive. The TUI re-exports them via\n// `zidane/tui` for back-compat; a GUI shell imports directly from\n// `zidane/chat`.\n// ---------------------------------------------------------------------------\n\n/** Tools whose `tool-result` event is suppressed when `showEditDiffs` is on. */\nexport const EDIT_TOOL_NAMES: ReadonlySet<string> = new Set(['edit', 'multi_edit', 'write_file'])\n\n/**\n * Recognize a tool-result body as carrying NON-success information so the\n * renderer doesn't suppress it under `showEditDiffs`. Three categories:\n *\n * - `edit` → \"Edit error: …\"\n * - `write_file` permission errors wrapped by the loop → \"Tool failed: …\"\n * - `multi_edit` → legacy single-line error `multi_edit error: …`, OR\n * a result carrying an `<edit-outcomes>…</edit-outcomes>` annotation\n * block. The TUI only appends the annotation when at least one hunk\n * was NOT applied, so its mere presence is the signal — the result\n * body needs to stay visible next to the diff so the user can read\n * denial / skip / failure reasons longer than the per-hunk badge.\n * - Fully-denied gate emit (`[fully denied] <edit-outcomes>…`) likewise\n * stays visible.\n *\n * Exported for unit-testability of the visibility matrix.\n */\nexport function isEditErrorResult(text: string): boolean {\n if (text.startsWith('Edit error:'))\n return true\n if (text.startsWith('Tool failed:'))\n return true\n if (text.startsWith('multi_edit error:'))\n return true\n if (text.startsWith('[fully denied]'))\n return true\n // The annotation block only appears when at least one hunk was NOT\n // applied (see `src/tui/app.tsx` `annotateEditResult`), so a present\n // sentinel pair signals \"denial / skip / failure attached — surface\n // the result body so reasons are readable next to the diff badges\".\n if (text.includes('\\n<edit-outcomes>\\n') || text.startsWith('<edit-outcomes>\\n'))\n return true\n return false\n}\n\n/**\n * Per-event visibility — filters honor user toggles and the\n * `hideSubagentOutput` setting. When subagent output is hidden:\n * - Child-agent events are filtered down to the `spawn-start` /\n * `spawn-end` markers so the user still sees \"🌱 working… 🌳 done\".\n * - The parent's `tool-result` for `spawn` is hidden too. Its body\n * duplicates `spawn-end`'s stats line *and* the parent's next\n * markdown turn; showing it again produces an extra\n * `┃ [sub-agent child-1] Completed …` block users just want gone.\n *\n * Renderer-agnostic — returns plain `boolean` so TUI / GUI consumers\n * can filter events identically.\n */\nexport function isVisible(event: StreamEvent, settings: Settings): boolean {\n if (settings.hideSubagentOutput) {\n if ((event.depth ?? 0) > 0)\n return event.kind === 'spawn-start' || event.kind === 'spawn-end'\n if (event.kind === 'tool-result' && event.tool === 'spawn')\n return false\n }\n // Suppress successful `tool-result`s for edit/multi_edit/write_file when\n // the diff renderer is on — the diff itself is the success indicator;\n // the `Edited path: N replacements.` line just adds noise. Error\n // results bypass the suppression so failures stay visible.\n if (\n settings.showEditDiffs\n && event.kind === 'tool-result'\n && event.tool\n && EDIT_TOOL_NAMES.has(event.tool)\n && !isEditErrorResult(event.text)\n ) {\n return false\n }\n switch (event.kind) {\n case 'thinking': return settings.showThinking\n case 'tool': return settings.toolCallDisplay !== 'hidden'\n case 'tool-result': return settings.showToolResults\n default: return true\n }\n}\n\n/**\n * Default top-margin per kind (in rows). Spacing intent:\n * - `info` / `markdown` / `tool` / `error` / `spawn-start` open a new\n * block, so they each get one row of breathing room above.\n * - `thinking` / `tool-result` / `spawn-end` continue the previous\n * block and stay flush.\n *\n * Context-aware overrides live in {@link marginTopFor}.\n */\nconst MARGIN_TOP: Record<StreamEvent['kind'], number> = {\n 'separator': 0,\n 'user-prompt': 1,\n 'info': 1,\n 'thinking': 0,\n 'tool': 1,\n 'tool-result': 0,\n 'error': 1,\n 'markdown': 1,\n 'spawn-start': 1,\n 'spawn-end': 0,\n // Compact-summary opens its own block; visually it's the \"before /\n // after\" hinge between elided history and current context, so it\n // deserves the breathing room a markdown / user-prompt event gets.\n 'compact-summary': 1,\n // Task-notification banners open a new logical block (a task is done,\n // here's what happened) so they get the same gap a `tool` line gets.\n // Consecutive task-notifications stack tight via the rule in\n // `marginTopFor` — a burst of completed tasks reads as one stacked\n // log column rather than scattered banners.\n 'task-notification': 1,\n}\n\nconst TOOL_KINDS: ReadonlySet<StreamEvent['kind']> = new Set(['tool', 'tool-result'])\n\n/**\n * Resolve the top margin (in rows) for an event given the one rendered\n * just before it. Context-aware rules:\n *\n * - A `tool` / `tool-result` event right after another\n * `tool` / `tool-result` collapses to a tight list — call→result\n * pairs and back-to-back calls read as one logical block.\n * - Consecutive `task-notification` banners stack tight — a burst of\n * background-task completions reads as one log column rather than\n * scattered banners.\n *\n * NB: parent-level events (`depth === 0`) following a subagent block\n * (`depth > 0`) get their normal default margin. An earlier revision\n * collapsed this transition to 0 on the assumption that the subagent's\n * `🌳` end-marker provided enough visual separation — it didn't. A\n * 2-cell emoji on the same row as the close marker doesn't create a\n * vertical break, and in hide-subagent-output mode the SubagentBlock\n * box isn't rendered either, so the parent's follow-up markdown was\n * glued directly against the close line. Subagents should READ like\n * tool calls: open with breathing room, close tight inside, and open\n * a clean gap before the parent's next thought — that's the contract\n * the tool-call → markdown transition uses, mirrored here.\n *\n * Renderer-agnostic — TUI uses it as Yoga `marginTop`; a CSS host can\n * use the same number as the row's top margin in `em` / `rem`.\n */\nexport function marginTopFor(event: StreamEvent, previous: StreamEvent | undefined): number {\n if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind))\n return 0\n if (event.kind === 'task-notification' && previous?.kind === 'task-notification')\n return 0\n return MARGIN_TOP[event.kind] ?? 0\n}\n\n/**\n * Build the `resultTurnId → owningAssistantTurnId` map used by the select-\n * turn mode to coalesce a tool-call's surrounding turns into ONE navigation\n * stop.\n *\n * Protocol shape: every `tool_call` block in an assistant turn is closed by\n * a matching `tool_result` block in the *next* user turn (the agent loop's\n * history validator depends on this). When the next user turn's only events\n * are `tool-result`s — i.e. it's pure plumbing for the prior assistant\n * turn — we map it back to that assistant turn here. The select-turn nav\n * index ({@link selectableTurnIds}) skips owned turns, and the renderer's\n * highlight gate ({@link isTurnHighlighted}) extends the selection accent\n * from the assistant turn to the events of any turn it owns. Net effect:\n *\n * - Navigation never lands the cursor on a result-only turn whose own\n * events may be hidden by `showToolResults: false` — the cursor\n * wouldn't be visible.\n * - Selecting an assistant turn highlights the call AND its result as\n * one unit, matching the user's mental model of \"one message\".\n *\n * Owner-lookup is conservative: result-only turns with no matching prior\n * assistant turn (orphaned — usually because the parent was deleted)\n * stay selectable so the user can act on them via the turn-details modal.\n *\n * Subagent (`childId` set) events are ignored — they live in a separate\n * conversation tree.\n */\nexport function turnSelectionOwnership(events: readonly StreamEvent[]): Map<string, string> {\n // Build per-turn event-kind summary in render order. We need:\n // - Whether a turn is \"result-only\" (every event is `tool-result`).\n // - The most recent previous turn that emitted a `tool` event (= the\n // candidate owner for the next result-only turn).\n const orderedTurnIds: string[] = []\n const eventKindsByTurn = new Map<string, StreamEvent['kind'][]>()\n for (const e of events) {\n if (!e.turnId)\n continue\n if (e.childId)\n continue\n if (!eventKindsByTurn.has(e.turnId)) {\n orderedTurnIds.push(e.turnId)\n eventKindsByTurn.set(e.turnId, [])\n }\n eventKindsByTurn.get(e.turnId)!.push(e.kind)\n }\n\n const ownership = new Map<string, string>()\n let lastToolEmitterTurnId: string | null = null\n for (const tid of orderedTurnIds) {\n const kinds = eventKindsByTurn.get(tid)!\n const isResultOnly = kinds.length > 0 && kinds.every(k => k === 'tool-result')\n if (isResultOnly) {\n if (lastToolEmitterTurnId)\n ownership.set(tid, lastToolEmitterTurnId)\n // No owner found — leave the orphan selectable. Don't update\n // `lastToolEmitterTurnId` either (a result-only turn never emits\n // a `tool` event itself).\n continue\n }\n if (kinds.includes('tool'))\n lastToolEmitterTurnId = tid\n }\n return ownership\n}\n\n/**\n * Render-time check: should `event` paint with the selection accent?\n *\n * `true` when the event's own turn is selected, OR when the selected turn\n * `owns` the event's turn via {@link turnSelectionOwnership} (the call and\n * its tool-result rows highlight together). `false` when nothing is\n * selected or the relationship doesn't apply.\n *\n * Pure. Renderer-agnostic — the TUI's `<Transcript>` uses it; a GUI's\n * equivalent walks the same rule.\n */\nexport function isTurnHighlighted(\n event: Pick<StreamEvent, 'turnId'>,\n selectedTurnId: string | null,\n ownership: ReadonlyMap<string, string>,\n): boolean {\n if (selectedTurnId === null || !event.turnId)\n return false\n if (event.turnId === selectedTurnId)\n return true\n return ownership.get(event.turnId) === selectedTurnId\n}\n\n/**\n * Deduplicated, in-order list of **parent-conversation** turn ids that appear\n * in a rendered transcript — the navigation index for the TUI's select-turn\n * mode. Three classes of turns are deliberately skipped:\n *\n * - **Subagent turns** (`childId` set). Nested execution detail; the\n * user's mental model of a \"message\" is the conversational exchange,\n * not each spawn turn. Also filtered out by `isVisible` under\n * `hideSubagentOutput: true` — selecting them would highlight nothing.\n * - **Result-only turns** — see {@link turnSelectionOwnership}. These get\n * coalesced into the assistant turn that emitted their tool_calls.\n * - **Settings-hidden turns** (when `settings` is supplied). A turn whose\n * every event fails {@link isVisible} would render no rows — landing\n * the cursor there hides it from the user entirely. The check is opt-\n * in so SDK callers without a Settings object keep the legacy\n * \"everything visible\" behavior.\n *\n * Synthetic events (separator, spawn-start, spawn-end) have no `turnId` and\n * are skipped naturally.\n */\nexport function selectableTurnIds(\n events: readonly StreamEvent[],\n settings?: Settings,\n): string[] {\n const ownership = turnSelectionOwnership(events)\n\n // Per-turn visible event count (only computed when settings is supplied).\n // We need this to drop turns whose every event is filtered out by user\n // toggles — otherwise `↑/↓` could land the cursor on a row that paints\n // nothing visible.\n const visibleCount = settings ? new Map<string, number>() : null\n if (settings && visibleCount) {\n for (const e of events) {\n if (!e.turnId || e.childId)\n continue\n if (!isVisible(e, settings))\n continue\n visibleCount.set(e.turnId, (visibleCount.get(e.turnId) ?? 0) + 1)\n }\n }\n\n const seen = new Set<string>()\n const ordered: string[] = []\n for (const e of events) {\n if (!e.turnId)\n continue\n if (e.childId)\n continue\n if (seen.has(e.turnId))\n continue\n if (ownership.has(e.turnId))\n continue // result-only — owned by an earlier assistant turn\n if (visibleCount && (visibleCount.get(e.turnId) ?? 0) === 0)\n continue // every event of this turn is hidden by user settings\n seen.add(e.turnId)\n ordered.push(e.turnId)\n }\n return ordered\n}\n\n/** Effective context size of the most recent assistant turn — drives the footer indicator. */\n/**\n * Walk from the end of `turns` and return the cache-aware input-token total\n * of the most recent **parent** (depth-0) assistant turn. Subagent turns\n * are skipped because their context window is the child's, not the parent's\n * — surfacing a subagent's `usage.input` as the footer's \"context used\"\n * after a session ends mid-spawn would be misleading. The next prompt\n * feeds the parent, so the parent's last view is what matters.\n *\n * Zero-context placeholder turns are also skipped. `executeTurn`\n * synthesizes an assistant turn with `usage: { input: 0, output: 0 }`\n * when the provider stream throws / aborts before any usage is reported\n * (see `src/loop.ts`'s catch path) — that turn is purely a shape\n * contract for resume, not a real context-window measurement. Without\n * the skip, an abort mid-stream shadows the prior real turn and the\n * footer reads `ctx 0` on the next session load even though earlier\n * turns accumulated meaningful context (and `run.cost`).\n *\n * `runs` is optional for backwards compatibility (older call sites and\n * tests that don't have ancestry info). Without it, this falls back to the\n * pre-fix behavior — depth is unknown so every assistant turn qualifies.\n */\nexport function lastContextSizeFromTurns(\n turns: SessionTurn[],\n runs: readonly SessionRun[] = [],\n): number {\n const childRunIds = new Set<string>()\n for (const run of runs) {\n if ((run.depth ?? 0) > 0)\n childRunIds.add(run.id)\n }\n for (let i = turns.length - 1; i >= 0; i--) {\n const turn = turns[i]\n if (turn.role !== 'assistant' || !turn.usage)\n continue\n if (turn.runId && childRunIds.has(turn.runId))\n continue\n const size = (turn.usage.input ?? 0)\n + (turn.usage.cacheRead ?? 0)\n + (turn.usage.cacheCreation ?? 0)\n if (size === 0)\n continue\n return size\n }\n return 0\n}\n\n/**\n * Sum provider-reported USD cost across every run in a session. Used to\n * seed the live cost indicator on session activation. Returns 0 when no\n * run carries a `cost` (most providers — only OpenRouter reports today).\n */\nexport function sumRunCosts(runs: readonly SessionRun[]): number {\n let total = 0\n for (const run of runs) {\n if (run.cost)\n total += run.cost\n }\n return total\n}\n","/**\n * User-level config at `~/.{prefix}/config.json`. Holds global flags\n * that need to persist across projects — distinct from `state.json`\n * which can live PER-PROJECT under `<git-root>/.{prefix}/`.\n *\n * Single field today (`projectDb` opt-out); easy to grow without\n * threading every new flag through `runTui()`'s call site.\n *\n * Tolerant on disk errors: missing file → empty config (defaults\n * apply), malformed JSON → empty config (logged under `ZIDANE_DEBUG`).\n * Never throws on read — the host's bootstrap path can't afford it.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { errorMessage } from '../errors'\n\nexport interface UserConfig {\n /**\n * Master switch for project-scoped storage. When `true` (default),\n * launching inside a git repo auto-creates `<git-root>/.{prefix}/`\n * and stores `sessions.db` + `state.json` there. When `false`,\n * everything stays at the user level (`~/.{prefix}/`) regardless of\n * git presence — the classic single-store behavior.\n */\n projectDb?: boolean\n}\n\n/** Default user config — currently `{ projectDb: true }` (opt-in by default). */\nexport const DEFAULT_USER_CONFIG: Readonly<UserConfig> = Object.freeze({\n projectDb: true,\n})\n\n/**\n * Build the absolute path to `~/.{prefix}/config.json` given a user\n * directory (`<storageDir>/<prefix>`). Exported so callers debugging a\n * \"why isn't my override taking effect?\" can print the resolved path.\n */\nexport function userConfigPath(userDir: string): string {\n return resolve(userDir, 'config.json')\n}\n\n/**\n * Read + parse the user config file. Returns an empty config (defaults\n * apply) on any failure — the function never throws. Set `ZIDANE_DEBUG`\n * to surface parse failures to stderr.\n */\nexport function loadUserConfig(userDir: string): UserConfig {\n const path = userConfigPath(userDir)\n if (!existsSync(path))\n return {}\n try {\n const raw = readFileSync(path, 'utf8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n return normalizeUserConfig(parsed as Record<string, unknown>)\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] user-config: failed to read \"${path}\": ${errorMessage(err)}\\n`)\n return {}\n }\n}\n\n/**\n * Narrow an arbitrary JSON object to the `UserConfig` shape. Drops\n * unknown keys (so a future zidane version's config field doesn't\n * leak into the typed surface) and validates types per-field.\n */\nfunction normalizeUserConfig(raw: Record<string, unknown>): UserConfig {\n const out: UserConfig = {}\n if (typeof raw.projectDb === 'boolean')\n out.projectDb = raw.projectDb\n return out\n}\n","/**\n * XDG Base Directory resolution for zidane.\n *\n * The user's storage spreads across four logical slots:\n *\n * - **config** — credentials, mcp-credentials, keybindings, config.json,\n * mcps.json, skills (user-editable surface).\n * - **data** — sessions.db (long-lived chat history).\n * - **state** — state.json (per-machine UI bookkeeping: last provider,\n * last model, last agent, settings).\n * - **cache** — persisted tool results, background-task logs,\n * auto-update registry cache (regenerable, safe to wipe).\n *\n * Historically all four collapsed to `~/.zidane/`. This module adds\n * XDG-Base-Directory-aware defaults so a Linux user with no existing\n * `~/.zidane/` lands in the conventional `~/.config/zidane/`,\n * `~/.local/share/zidane/`, `~/.local/state/zidane/`, `~/.cache/zidane/`\n * trio. Existing setups (anyone with a `~/.zidane/` directory or\n * `ZIDANE_STORAGE_DIR` set) keep the single-dir layout unchanged.\n *\n * Resolution flow — applied in this order:\n *\n * 1. Caller passed an explicit `storageDir` (`ChatOptions.storageDir`)\n * OR `ZIDANE_STORAGE_DIR` is set → **legacy-explicit** single-dir\n * layout under `<storageDir>/<prefix>`. The four slots collapse to\n * the same directory.\n * 2. `~/<prefix>/` already exists on disk → **legacy-existing**\n * single-dir layout under that directory. Upgrades never silently\n * shift files around.\n * 3. Any `XDG_*_HOME` env var is set OR `process.platform === 'linux'`\n * → **XDG split**. Each slot lands in its own dir. Per-slot\n * `ZIDANE_{CONFIG,DATA,CACHE,STATE}_DIR` overrides beat the XDG\n * vars for surgical tweaks.\n * 4. Otherwise (macOS/Windows, no XDG signal, no existing dir) →\n * **default** single-dir layout at `~/<prefix>/`. Day-one behavior\n * on these platforms is unchanged.\n *\n * The `userDir` legacy alias always equals `configDir` so existing code\n * that still references `paths.userDir` keeps reading credentials, mcps,\n * etc. from the right place.\n */\n\nimport { existsSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\n\n/** The four slot kinds we resolve. Names match the XDG spec. */\nexport type StorageSlot = 'config' | 'data' | 'state' | 'cache'\n\n/** Which resolution branch produced the layout — surfaced for debugging + tests. */\nexport type StorageMode = 'legacy-explicit' | 'legacy-existing' | 'xdg' | 'default'\n\n/**\n * Per-slot directories. In legacy / default modes the four fields all\n * point at the same path; in XDG mode they fan out into their\n * spec-conventional locations.\n *\n * `mode` carries the resolution branch so a host that wants to print\n * a startup banner (\"zidane is using XDG paths\") can detect the\n * layout without re-running the logic.\n */\nexport interface StorageDirs {\n configDir: string\n dataDir: string\n stateDir: string\n cacheDir: string\n mode: StorageMode\n}\n\nexport interface ResolveStorageDirsOptions {\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n /**\n * Explicit storage root (`ChatOptions.storageDir`). When set, forces\n * legacy-explicit mode regardless of the platform / XDG signals.\n */\n storageDir?: string\n /** Override `os.homedir()` for tests. */\n home?: string\n /** Override `process.platform` for tests. */\n platform?: NodeJS.Platform\n /** Override `process.env` for tests. */\n env?: Readonly<Record<string, string | undefined>>\n}\n\n/**\n * Resolve the four storage slots according to the precedence above.\n *\n * Pure aside from one filesystem probe (`existsSync(home/<prefix>)`)\n * — the same probe `resolveStoragePaths` would do anyway when\n * computing `paths.userDir`. No directories are created here; the\n * relevant write paths (state store, credentials writer, persist\n * dir) handle their own lazy `mkdirSync` already.\n */\nexport function resolveStorageDirs(opts: ResolveStorageDirsOptions = {}): StorageDirs {\n const env = opts.env ?? process.env\n const home = opts.home ?? homedir()\n const platform = opts.platform ?? process.platform\n // Two prefix forms — legacy mode preserves whatever the caller\n // passed (no implicit dot insertion, so `prefix: 'myapp'` lands at\n // `<storageDir>/myapp` not `<storageDir>/.myapp` — matches the\n // pre-XDG behavior tests already rely on). XDG mode uses the de-\n // dotted form (`'.zidane'` → `'zidane'`) because the XDG dir itself\n // is already the dotfile container — `~/.config/.zidane/` would be\n // a double-dot offense.\n const rawPrefix = opts.prefix ?? '.zidane'\n const prefixBare = rawPrefix.replace(/^\\./, '')\n\n // Branch 1 — explicit storage root → legacy-explicit.\n // Host-supplied `storageDir` beats env so SDK callers can pin a\n // location for embedded usage; otherwise `ZIDANE_STORAGE_DIR` wins.\n const explicitStorageDir = opts.storageDir ?? env.ZIDANE_STORAGE_DIR\n if (explicitStorageDir) {\n const base = resolve(explicitStorageDir, rawPrefix)\n return single(base, 'legacy-explicit')\n }\n\n // Branch 2 — `~/<prefix>/` already exists → legacy-existing.\n // Detect ANY of the legacy locations the user might have:\n // - `~/.zidane/` (default prefix)\n // - `~/.<prefix>/` (custom prefix)\n // The probe is a single existsSync (cheap) and skipping it for a\n // user with a customized prefix would leak their data into XDG dirs\n // on next launch.\n const legacyHomeDir = resolve(home, rawPrefix)\n if (existsSync(legacyHomeDir))\n return single(legacyHomeDir, 'legacy-existing')\n\n // Branch 3 — XDG signals present → split layout.\n // The signal is generous: Linux always qualifies, any platform\n // where the user has set any `XDG_*_HOME` var opts in (a savvy\n // mac user can put zidane in their XDG hierarchy too), AND any\n // `ZIDANE_{CONFIG,DATA,STATE,CACHE}_DIR` per-slot override also\n // opts in — otherwise a user pinning JUST `ZIDANE_CACHE_DIR` on\n // macOS would have it silently ignored because none of the\n // `XDG_*_HOME` vars are set.\n const hasXdgSignal = platform === 'linux'\n || !!env.XDG_CONFIG_HOME\n || !!env.XDG_DATA_HOME\n || !!env.XDG_CACHE_HOME\n || !!env.XDG_STATE_HOME\n || !!env.ZIDANE_CONFIG_DIR\n || !!env.ZIDANE_DATA_DIR\n || !!env.ZIDANE_STATE_DIR\n || !!env.ZIDANE_CACHE_DIR\n\n if (hasXdgSignal) {\n return {\n configDir: env.ZIDANE_CONFIG_DIR\n ?? resolve(env.XDG_CONFIG_HOME || resolve(home, '.config'), prefixBare),\n dataDir: env.ZIDANE_DATA_DIR\n ?? resolve(env.XDG_DATA_HOME || resolve(home, '.local', 'share'), prefixBare),\n stateDir: env.ZIDANE_STATE_DIR\n ?? resolve(env.XDG_STATE_HOME || resolve(home, '.local', 'state'), prefixBare),\n cacheDir: env.ZIDANE_CACHE_DIR\n ?? resolve(env.XDG_CACHE_HOME || resolve(home, '.cache'), prefixBare),\n mode: 'xdg',\n }\n }\n\n // Branch 4 — default (mac/windows, no XDG signal, no existing dir).\n // Same as the day-one experience: everything under `~/<prefix>/`.\n return single(legacyHomeDir, 'default')\n}\n\nfunction single(base: string, mode: StorageMode): StorageDirs {\n return {\n configDir: base,\n dataDir: base,\n stateDir: base,\n cacheDir: base,\n mode,\n }\n}\n","import type { Preset } from '../presets'\nimport type { SessionStore } from '../session'\nimport type { AgentRegistry } from './agents'\nimport type { ProviderAuth, ProviderKey } from './auth'\nimport type { KeyBindings } from './keybindings'\nimport type { ModelInfo, ProviderDescriptor } from './providers'\nimport type { StateStoreApi, TuiState } from './store'\nimport type { Picked, Settings } from './types'\nimport type { StorageMode } from './xdg'\nimport { existsSync, mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { BUILTIN_AGENTS, DEFAULT_AGENT_ID, resolveAgentId, singleAgentRegistry } from './agents'\nimport { detectAuth } from './auth'\nimport { applyApiKeyEnv, credentialsPath } from './credentials'\nimport { readKeybindings } from './keybindings'\nimport { findGitRoot } from './project-root'\nimport { BUILTIN_PROVIDERS, modelsForDescriptor } from './providers'\nimport { createStateStore } from './store'\nimport { loadUserConfig } from './user-config'\nimport { resolveStorageDirs } from './xdg'\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Re-exported for callers who want `ModelInfo` from the same module as `ChatOptions`. */\nexport type { ModelInfo }\n\n/**\n * Provider registry — a map keyed by `descriptor.key`. Passed verbatim to the\n * chat engine; there is no implicit merge with built-ins. Hosts that want the\n * four default providers spread {@link BUILTIN_PROVIDERS}; hosts that only\n * want their own pass exactly their own.\n */\nexport type ProviderRegistry = Readonly<Record<string, ProviderDescriptor>>\n\n/**\n * Options accepted by `resolveConfig()` (and, transitively, by `runTui()` and\n * any future GUI launcher). Every field is optional — defaults boot a working\n * chat against the built-in providers with sessions stored under `~/.zidane/`.\n */\nexport interface ChatOptions {\n /**\n * Directory name used under `storageDir` for sessions + state.\n * Default: `'.zidane'` → `~/.zidane/sessions.db` + `~/.zidane/state.json`.\n * Pass e.g. `'.myapp'` for `~/.myapp/...`, or `'myapp'` for a visible dir.\n */\n prefix?: string\n /**\n * Storage root. Defaults to the user's home directory. Combined with\n * `prefix` to form the data directory.\n */\n storageDir?: string\n /**\n * Provider registry. Each value is a {@link ProviderDescriptor} carrying\n * label + factory + credential metadata. **No automatic merge** — hosts\n * pass exactly what they want.\n *\n * - Omitted → {@link BUILTIN_PROVIDERS} (anthropic + openai + openrouter + cerebras).\n * - `providers: BUILTIN_PROVIDERS` → same as omitted, but explicit.\n * - `providers: { anthropic: anthropicDescriptor }` → only Anthropic.\n * - `providers: { ...BUILTIN_PROVIDERS, mine: customDescriptor }` → built-ins + custom.\n * - `providers: { mine: customDescriptor }` → custom-only, **no built-ins**.\n */\n providers?: ProviderRegistry\n /**\n * Legacy single-agent preset (tools, system prompt, behavior). When set\n * **without** {@link ChatOptions.agents}, the resolver wraps this preset\n * into a one-entry {@link AgentRegistry} keyed `default` so existing\n * callers keep working and the picker is effectively hidden.\n *\n * Prefer `agents` for any multi-profile setup. Passing both throws.\n */\n preset?: Preset\n /**\n * Agent profile registry — keyed by `profile.id`. Each profile bundles a\n * preset with display metadata (label, description, accent). The TUI\n * shows a picker (Ctrl+A) when more than one profile is registered.\n *\n * - Omitted (and `preset` omitted) → {@link BUILTIN_AGENTS} (Build + Plan).\n * - Omitted but `preset` provided → single-entry registry around the preset.\n * - Provided → host owns the registry; pass exactly what you want.\n *\n * Hosts mixing built-ins with their own typically spread:\n * `agents: { ...BUILTIN_AGENTS, debug: myDebugProfile }`.\n */\n agents?: AgentRegistry\n /**\n * Id of the profile activated on first launch. Resolved against `agents`;\n * unknown ids fall through to the registry's first key. Persisted\n * `state.lastAgent` overrides this on subsequent launches.\n */\n defaultAgent?: string\n /**\n * Session store — either an instance or a factory called with the\n * resolved storage paths. **Required** at the chat layer so this module\n * stays platform-agnostic (no `bun:sqlite` baked in). Terminal hosts use\n * `createTuiStore` from `zidane/session/sqlite`; a future GUI host can\n * supply a different adapter (IndexedDB, remote API, in-memory) without\n * touching this layer.\n *\n * ```ts\n * import { createTuiStore } from 'zidane/session/sqlite'\n *\n * // Either pass a factory…\n * resolveConfig({ store: paths => createTuiStore(paths.db) })\n *\n * // …or an instance built ahead of time.\n * const store = createTuiStore('/path/to/sessions.db')\n * resolveConfig({ store })\n * ```\n */\n store: SessionStore | ((paths: ResolvedPaths) => SessionStore)\n /**\n * Project-scoped session storage. When `true` (default), launching\n * inside a git repo auto-creates `<git-root>/.{prefix}/` and stores\n * `sessions.db` + `state.json` there — so sessions, mcps.json, and\n * skills all live alongside the project they belong to. Credentials\n * and the cross-project safelist stay at `~/.{prefix}/` regardless.\n *\n * Precedence: `options.projectDb` > `~/.{prefix}/config.json`\n * `projectDb` > `true`. The env var `ZIDANE_PROJECT_DB=0` flips it\n * off too, for one-shot CLI invocations.\n *\n * When `false`, OR when not inside a git repo, everything lives at\n * `~/.{prefix}/` — the classic single-store layout.\n */\n projectDb?: boolean\n /**\n * Override for the project root. Defaults to `process.cwd()`. Hosts\n * embedding the TUI from a non-CLI context (tests, daemons) can pin\n * the cwd here so the git-root walk produces a stable result. Has no\n * effect when `projectDb` resolves to `false`.\n */\n cwd?: string\n /**\n * Auto-update wiring. When set, the App quietly checks the npm registry\n * for a newer release of `packageName` (matching the host's package),\n * caches the result for a day under `userDir/`, and surfaces a passive\n * `↑ vX.Y.Z` chip in the footer when one is available.\n *\n * Consumers wrapping `runTui` for their own CLI inline their own\n * `package.json` version at build time and pass it here. The bundled\n * `zidane-tui` binary wires this automatically in `tui/src/cli.ts`.\n *\n * Defaults to `null` — no auto-update plumbing, no network calls,\n * no chip.\n */\n autoUpdate?: AutoUpdateConfig | null\n}\n\n/**\n * Host-supplied auto-update settings. The check itself is gated on\n * `Settings.checkForUpdates`; passing a config wires the React plumbing.\n *\n * Hosts that want to publish their own consumer-facing package can pass\n * different values without forking anything — the registry / channel /\n * platform-package-prefix are all knobs.\n */\nexport interface AutoUpdateConfig {\n /** Top-level npm package the user installs (`zidane-tui`, `my-app`, …). */\n packageName: string\n /** Inlined at build time — usually `pkg.version`. */\n currentVersion: string\n /** dist-tag. Defaults to `'latest'`. */\n channel?: string\n /** Registry base URL. Defaults to `'https://registry.npmjs.org'`. */\n registry?: string\n /**\n * Override the read-through cache lifetime in ms for the in-TUI hook.\n * Defaults to `0` — every TUI launch fetches fresh, so the chip\n * never lies. Pass a positive value (e.g. `3_600_000` for 1 h) to\n * throttle the registry-poll rate at the cost of staleness.\n *\n * The on-disk cache is always WRITTEN, regardless of this value, so\n * `zidane upgrade` and other tooling read paths benefit immediately.\n */\n cacheTtlMs?: number\n /**\n * Platform-package prefix used by the in-place self-update flow when\n * resolving the per-arch tarball name. Defaults to `'<packageName>-'`\n * (i.e. `zidane-tui-darwin-arm64` when `packageName === 'zidane-tui'`).\n * Override only when your platform packages don't follow the `<name>-<key>`\n * convention.\n */\n platformPackagePrefix?: string\n}\n\n/** Resolved on-disk layout produced by {@link resolveConfig}. */\nexport interface ResolvedPaths {\n /**\n * Effective data root — equals `userDir` (configDir) when project\n * mode is off, `projectDir` when it's on. Kept for callers that\n * wrote against the pre-project-db API; new code should reference\n * the explicit slots (`configDir` / `dataDir` / `cacheDir` /\n * `stateDir`) or `projectDir` / `db` / `state` instead.\n */\n dir: string\n /**\n * User-scoped config root — credentials, mcp-credentials, keybindings,\n * `config.json`, `mcps.json`, skills. Legacy alias for `configDir`;\n * existing call sites still reference `userDir`. In XDG mode this\n * lands at `$XDG_CONFIG_HOME/zidane`; in legacy mode the four slots\n * (config / data / state / cache) collapse to a single\n * `<storageDir>/<prefix>` directory.\n */\n userDir: string\n /**\n * Project-scoped data root. Set to `<git-root>/<prefix>` when project\n * mode resolved to ON and a git root was found. `null` otherwise.\n */\n projectDir: string | null\n /** Resolved `sessions.db` path. Lives under `projectDir` when active, else `dataDir`. */\n db: string\n /** Resolved `state.json` path. Lives under `projectDir` when active, else `stateDir`. */\n state: string\n /**\n * XDG config slot — `$XDG_CONFIG_HOME/zidane` on a fresh Linux install,\n * `<storageDir>/<prefix>` (e.g. `~/.zidane/`) in legacy mode. Holds\n * user-editable surface (`credentials.json`, `mcps.json`, …).\n */\n configDir: string\n /**\n * XDG data slot — `$XDG_DATA_HOME/zidane` on Linux, single-dir\n * elsewhere. Holds long-lived chat history (`sessions.db`).\n */\n dataDir: string\n /**\n * XDG state slot — `$XDG_STATE_HOME/zidane` on Linux, single-dir\n * elsewhere. Holds per-machine UI bookkeeping (`state.json`).\n */\n stateDir: string\n /**\n * XDG cache slot — `$XDG_CACHE_HOME/zidane` on Linux, single-dir\n * elsewhere. Holds regenerable data: persisted tool results,\n * background-task logs, auto-update registry cache.\n */\n cacheDir: string\n /**\n * Which resolution branch produced the layout. `'xdg'` when split\n * across the four XDG dirs; one of `'legacy-explicit'`,\n * `'legacy-existing'`, or `'default'` when the slots collapse to a\n * single directory. Surfaced for diagnostics + tests.\n */\n storageMode: StorageMode\n}\n\n// ---------------------------------------------------------------------------\n// Resolved config\n// ---------------------------------------------------------------------------\n\nexport interface ResolvedConfig {\n prefix: string\n storageDir: string\n paths: ResolvedPaths\n providers: ProviderRegistry\n /**\n * Resolved profile registry. Always non-empty; the resolver throws when\n * the host passes an empty `agents` map (same contract as `providers`).\n */\n agents: AgentRegistry\n /** Id of the profile to activate on mount. Guaranteed to key into `agents`. */\n initialAgentId: string\n store: SessionStore\n stateStore: StateStoreApi\n /** Lookup by `ProviderKey` returning the available models for the picker. */\n modelsFor: (key: ProviderKey) => readonly ModelInfo[]\n initialState: TuiState\n initialSettings: Partial<Settings>\n resumeProvider: ProviderAuth | null\n initialPicked: Picked | null\n /**\n * Effective keybindings — defaults merged with the user-level\n * `<userDir>/keybindings.json` (when present). Always fully-resolved\n * so consumers can treat the record as total. See `./keybindings.ts`\n * for the action catalog + spec format.\n */\n keybindings: KeyBindings\n /**\n * Resolved auto-update knobs (forwarded verbatim from `ChatOptions.autoUpdate`)\n * — `null` when the host didn't wire one. The TUI feeds this into\n * `useUpdateCheck` so the footer chip + the `zidane upgrade` subcommand\n * share a single source of truth.\n */\n autoUpdate: AutoUpdateConfig | null\n}\n\n/** Resolve user options into a fully-bound runtime config. Pure aside from disk reads. */\nexport function resolveConfig(options: ChatOptions): ResolvedConfig {\n const prefix = options.prefix ?? process.env.ZIDANE_PREFIX ?? '.zidane'\n // Distinguish \"user pinned storageDir\" (legacy-explicit mode) from\n // \"default behavior\" (let XDG kick in on Linux). Only the explicit\n // value is forwarded to the resolver; `ResolvedConfig.storageDir`\n // below falls back to `homedir()` for display / back-compat callers\n // that read it.\n const explicitStorageDir = options.storageDir ?? process.env.ZIDANE_STORAGE_DIR\n const storageDir = explicitStorageDir ?? homedir()\n const paths = resolveStoragePaths({\n prefix,\n ...(explicitStorageDir !== undefined ? { storageDir: explicitStorageDir } : {}),\n cwd: options.cwd ?? process.cwd(),\n projectDb: options.projectDb,\n })\n\n // `options.store` is required at the chat layer — the renderer (TUI, GUI)\n // injects an adapter so this module stays free of platform-specific\n // session-storage code. Accepts an instance or a factory; the factory\n // form lets callers wire their adapter to the resolved storage paths\n // without duplicating the prefix/storageDir logic above.\n if (!options.store)\n throw new Error('resolveConfig: `store` is required. Pass a `SessionStore` instance, or a factory — terminal hosts can use `createTuiStore` from `zidane/session/sqlite`.')\n const store: SessionStore = typeof options.store === 'function'\n ? options.store(paths)\n : options.store\n const stateStore = createStateStore(paths.state)\n const initialState = stateStore.load()\n\n // No implicit merge — host controls the registry entirely. Default is the\n // four built-ins; an explicit empty `{}` would mean \"no providers\" (the\n // wizard would have nothing to offer and the AuthScreen would refuse).\n const providers: ProviderRegistry = options.providers ?? BUILTIN_PROVIDERS\n\n // Agent registry resolution. Three paths, mutually exclusive:\n // - `agents` + `preset` both set → throw. The legacy `preset` would\n // get silently ignored otherwise and the host wouldn't notice.\n // - `agents` set → use verbatim (host owns the picker contents).\n // - `preset` set without `agents` → wrap into a single-entry registry\n // so the legacy single-agent path keeps working without code change.\n // - neither set → BUILTIN_AGENTS (Build + Plan).\n if (options.agents && options.preset) {\n throw new Error(\n 'resolveConfig: pass either `preset` (single-agent legacy) or `agents` '\n + '(multi-profile registry), not both. Migrate `preset` into an entry '\n + 'inside `agents` if you want both worlds.',\n )\n }\n const agents: AgentRegistry = options.agents\n ?? (options.preset ? singleAgentRegistry(options.preset) : BUILTIN_AGENTS)\n if (Object.keys(agents).length === 0) {\n throw new Error('resolveConfig: `agents` registry is empty — at least one profile is required.')\n }\n\n // Wire credentials *before* anything that might read them (resume detection,\n // any factory call). Sets `ZIDANE_CREDENTIALS_PATH` so the harness providers\n // find the file the chat engine manages, and injects stored API keys into\n // env so factories that resolve credentials eagerly (like `anthropic()`)\n // see them. Credentials ALWAYS live under `userDir` — they're host-wide\n // secrets, not per-project data, and must never leak into a checked-in\n // project `.{prefix}/` directory.\n process.env.ZIDANE_CREDENTIALS_PATH = credentialsPath(paths.userDir)\n applyApiKeyEnv(paths.userDir, providers)\n\n const modelsFor = makeModelsResolver(providers)\n\n const resumeProvider = resolveResumeProvider(initialState, providers, paths.userDir)\n const initialPicked = resumeProvider ? pickInitial(resumeProvider, providers, initialState) : null\n\n // Prefer persisted `lastAgent`, then the host's `defaultAgent`, then\n // `DEFAULT_AGENT_ID` (= 'build'), then the first registry key. The\n // last fallback is hit when the host's `defaultAgent` was renamed /\n // removed since the state file was written.\n const initialAgentId = resolveAgentId(\n agents,\n initialState.lastAgent,\n options.defaultAgent ?? DEFAULT_AGENT_ID,\n )\n if (!initialAgentId) {\n // Defensive — the empty-check above already guards, so this is unreachable.\n throw new Error('resolveConfig: failed to resolve an initial agent id from a non-empty registry.')\n }\n\n // Keybindings live at the user level only — shortcut muscle memory\n // is per-user, not per-project. `readKeybindings` always returns a\n // fully-merged map (defaults fill in any missing / malformed entries).\n const keybindings = readKeybindings(paths.userDir)\n\n return {\n prefix,\n storageDir,\n paths,\n providers,\n agents,\n initialAgentId,\n store,\n stateStore,\n modelsFor,\n initialState,\n initialSettings: migrateLegacySettings(initialState.settings ?? {}),\n resumeProvider,\n initialPicked,\n keybindings,\n autoUpdate: options.autoUpdate ?? null,\n }\n}\n\n/**\n * Compute the on-disk layout for a single TUI session. Public so hosts\n * (CLI tools, tests, scripted bootstraps) can ask \"where will sessions\n * land?\" without going through `resolveConfig`'s full machinery.\n *\n * Storage-slot resolution (config / data / state / cache) is delegated\n * to {@link resolveStorageDirs}. In legacy modes the four slots collapse\n * to a single `<storageDir>/<prefix>` directory; in XDG mode they fan\n * out into `$XDG_CONFIG_HOME/zidane`, `$XDG_DATA_HOME/zidane`,\n * `$XDG_STATE_HOME/zidane`, `$XDG_CACHE_HOME/zidane`. See the module\n * doc on `./xdg.ts` for the full precedence chain.\n *\n * Decision logic for project mode (least → most specific):\n *\n * 1. Default: OFF — sessions live in the user-scoped `dataDir`\n * and get scoped per-project via the `SessionData.projectRoot` tag.\n * 2. `<configDir>/config.json` `projectDb` flag (when present).\n * 3. `ZIDANE_PROJECT_DB` env var (`'0'` / `'false'` / `'off'` → off,\n * anything else true if set).\n * 4. `options.projectDb` (host call site).\n *\n * Project mode activates only when the resolution above lands on\n * `true` AND a git repo is found above `cwd`. Outside git or with the\n * flag off, sessions + state stay at the user-scoped slots.\n *\n * Auto-creates the project `.{prefix}/` directory when mode resolves\n * to ON (silent — mirrors `ensureStateDir`). User slots are created\n * lazily by the state-store + credentials writers; we don't pre-touch\n * them here so a read-only invocation stays read-only.\n */\nexport function resolveStoragePaths(opts: {\n prefix: string\n /** Explicit storage root. When set, forces legacy-explicit mode. */\n storageDir?: string\n cwd: string\n /** Host override for project mode. Beats the user-config + env-var sources. */\n projectDb?: boolean\n}): ResolvedPaths {\n const dirs = resolveStorageDirs({\n prefix: opts.prefix,\n ...(opts.storageDir !== undefined ? { storageDir: opts.storageDir } : {}),\n })\n\n // User-config + env-var are loaded eagerly so the precedence chain\n // is observable from a single call to this function; resolution is\n // cheap (one fs.existsSync + readFileSync at most). Reads from\n // `configDir` — `config.json` is user-editable surface, not data.\n const userConfig = loadUserConfig(dirs.configDir)\n const envFlag = parseEnvFlag(process.env.ZIDANE_PROJECT_DB)\n const projectDbEnabled = opts.projectDb\n ?? envFlag\n ?? userConfig.projectDb\n ?? false\n\n const gitRoot = projectDbEnabled ? findGitRoot(opts.cwd) : null\n // Project-scoped storage uses the prefix verbatim — same shape as\n // pre-XDG callers expect (`prefix: '.zidane'` → `<repo>/.zidane/`,\n // `prefix: '.myapp'` → `<repo>/.myapp/`). XDG-mode users still get\n // the user-scoped slots split, but project dirs are a\n // checked-in-or-not concern, orthogonal to the XDG question.\n const projectDir = gitRoot ? resolve(gitRoot, opts.prefix) : null\n\n if (projectDir && !existsSync(projectDir)) {\n try {\n mkdirSync(projectDir, { recursive: true })\n }\n catch (err) {\n // Don't crash the bootstrap on mkdir failure (permissions, R/O\n // FS) — fall back to user-scoped storage. Surface the cause\n // under ZIDANE_DEBUG so the operator can diagnose if curious.\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] project-db: mkdir \"${projectDir}\" failed: ${errorMessage(err)}\\n`)\n return userOnlyPaths(dirs)\n }\n }\n\n if (projectDir) {\n return {\n dir: projectDir,\n userDir: dirs.configDir,\n projectDir,\n db: resolve(projectDir, 'sessions.db'),\n state: resolve(projectDir, 'state.json'),\n configDir: dirs.configDir,\n dataDir: dirs.dataDir,\n stateDir: dirs.stateDir,\n cacheDir: dirs.cacheDir,\n storageMode: dirs.mode,\n }\n }\n return userOnlyPaths(dirs)\n}\n\nfunction userOnlyPaths(dirs: {\n configDir: string\n dataDir: string\n stateDir: string\n cacheDir: string\n mode: StorageMode\n}): ResolvedPaths {\n return {\n dir: dirs.configDir,\n userDir: dirs.configDir,\n projectDir: null,\n db: resolve(dirs.dataDir, 'sessions.db'),\n state: resolve(dirs.stateDir, 'state.json'),\n configDir: dirs.configDir,\n dataDir: dirs.dataDir,\n stateDir: dirs.stateDir,\n cacheDir: dirs.cacheDir,\n storageMode: dirs.mode,\n }\n}\n\n/**\n * Migrate legacy `state.json` settings shapes forward. Pre-5.x persisted\n * `showToolCalls: boolean` controlled whether `↳ <tool>` lines rendered;\n * 5.x replaces that with a 3-way `toolCallDisplay: 'hidden' | 'formatted'\n * | 'full'` choice that adds a per-tool clean summary and a JSON debug\n * view alongside the existing \"show / hide\" behavior.\n *\n * Mapping (only when the new key is absent — never overwrite a user's\n * explicit modern choice):\n *\n * showToolCalls: true → toolCallDisplay: 'formatted'\n * showToolCalls: false → toolCallDisplay: 'hidden'\n *\n * The legacy key is dropped from the returned object so the in-memory\n * Settings shape stays clean; persisted state still carries it until\n * the next save overwrites the file, which is harmless (extra fields\n * round-trip through JSON without consequence).\n */\nfunction migrateLegacySettings(loaded: Partial<Settings> & Record<string, unknown>): Partial<Settings> {\n if ('showToolCalls' in loaded && !('toolCallDisplay' in loaded)) {\n const legacy = loaded.showToolCalls\n const { showToolCalls: _, ...rest } = loaded as Record<string, unknown>\n return {\n ...(rest as Partial<Settings>),\n toolCallDisplay: legacy === false ? 'hidden' : 'formatted',\n }\n }\n return loaded\n}\n\n/** Tri-state parse for env-var bools — explicit `null` keeps absence from forcing a default. */\nfunction parseEnvFlag(raw: string | undefined): boolean | undefined {\n if (raw === undefined)\n return undefined\n const v = raw.trim().toLowerCase()\n if (v === '0' || v === 'false' || v === 'off' || v === 'no')\n return false\n if (v === '1' || v === 'true' || v === 'on' || v === 'yes')\n return true\n return undefined\n}\n\nfunction makeModelsResolver(registry: ProviderRegistry): (key: ProviderKey) => readonly ModelInfo[] {\n return (key) => {\n const descriptor = registry[key]\n return descriptor ? modelsForDescriptor(descriptor) : []\n }\n}\n\nfunction resolveResumeProvider(\n state: TuiState,\n providers: ProviderRegistry,\n storageDir: string,\n): ProviderAuth | null {\n // Three independent reasons a resume can fail to materialize — each one\n // sends the TUI to the auth screen as if it were a fresh install. Under\n // `ZIDANE_DEBUG` we tag the specific cause so a \"had to rewrite my API\n // key after reinstall\" report points at the actual culprit instead of\n // forcing the operator to guess between (a) a wiped state file,\n // (b) a renamed provider key, and (c) a credentials file that exists\n // but doesn't contain the previous provider.\n if (!state.lastProvider) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] resume: no \\`lastProvider\\` in state — auth screen will show. (Has \\`${storageDir}/state.json\\` been deleted or never written?)\\n`)\n return null\n }\n if (!providers[state.lastProvider]) {\n if (process.env.ZIDANE_DEBUG) {\n const known = Object.keys(providers).join(', ') || '<none>'\n process.stderr.write(`[zidane/chat] resume: \\`state.lastProvider=\"${state.lastProvider}\"\\` is not in the registry. Known providers: ${known}.\\n`)\n }\n return null\n }\n const detected = detectAuth(storageDir, providers)\n const match = detected.find(p => p.key === state.lastProvider && p.available)\n if (!match && process.env.ZIDANE_DEBUG) {\n const entry = detected.find(p => p.key === state.lastProvider)\n process.stderr.write(`[zidane/chat] resume: \\`${state.lastProvider}\\` is in the registry but has no available auth method (apikey / env / oauth). Detection: ${entry ? JSON.stringify(entry.methods) : '<provider missing from detection>'}.\\n`)\n }\n return match ?? null\n}\n\nfunction pickInitial(auth: ProviderAuth, providers: ProviderRegistry, state: TuiState): Picked | null {\n const descriptor = providers[auth.key]\n if (!descriptor)\n return null\n const remembered = state.lastModelByProvider?.[auth.key]\n // Prefer the remembered model, then the descriptor's declared default.\n // Only construct the provider as a last resort — some factories throw on\n // missing credentials, and we never want that to block the wizard.\n const model = remembered ?? descriptor.defaultModel ?? safeFactoryDefault(descriptor)\n return model ? { provider: auth, model } : null\n}\n\nfunction safeFactoryDefault(descriptor: ProviderDescriptor): string | undefined {\n try {\n return descriptor.factory().meta.defaultModel\n }\n catch {\n return undefined\n }\n}\n","import type { ReactNode } from 'react'\nimport type { ResolvedConfig } from './config'\nimport { createContext, useContext } from 'react'\n\n// ---------------------------------------------------------------------------\n// ConfigContext — renderer-agnostic React context that hands a fully-resolved\n// chat config down to deep consumers (modals, pickers, screens). Kept in its\n// own file so callers that only want `resolveConfig()` don't pay the\n// `import 'react'` cost.\n// ---------------------------------------------------------------------------\n\nconst ConfigContext = createContext<ResolvedConfig | null>(null)\n\nexport function ConfigProvider({ config, children }: { config: ResolvedConfig, children: ReactNode }) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>\n}\n\nexport function useConfig(): ResolvedConfig {\n const ctx = useContext(ConfigContext)\n if (!ctx)\n throw new Error('useConfig must be used inside <ConfigProvider>')\n return ctx\n}\n","/**\n * Live catalogs for files / skills / MCPs surfaced through React\n * context.\n *\n * **Why context, not props.** The modal layer stores its open node in\n * `useState`. React's element-identity reconciliation skips re-rendering\n * a subtree when the element reference hasn't changed — so passing\n * `skillsCatalog` as a prop into `modal.open(<SettingsModal …>)` bakes\n * the snapshot at open time and any subsequent `setSkillsCatalog`\n * doesn't reach the modal. Context propagation IS forwarded through\n * the modal layer regardless of element identity (same path the MCP\n * auth state uses), so reading via `useDiscovery()` gives the modal\n * live state.\n *\n * **Shape.** The provider exposes three groups:\n *\n * - **Catalogs** — the current state. Consumers re-render on every\n * update.\n * - **Ensure thunks** — kick the first load (idempotent). Wired into\n * completion providers; also called by the settings modal on mount\n * to fire-load lazy catalogs (currently `files`) on demand.\n * - **Refresh thunks** — force a rescan, bypassing the SWR throttle.\n * Wired into the settings modal's `ctrl+R`.\n *\n * Renderer-agnostic by design: the context only carries data + thunks.\n * The discovery work itself lives in `*-discovery.ts` modules; the\n * stale-while-revalidate machinery lives in `discovery-slot.ts`. The\n * provider's caller (AppShell for the TUI) owns where the slots live\n * and how their state propagates to `<DiscoveryProvider value>`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpToolSchema } from '../mcp'\nimport type { SkillConfig } from '../skills'\nimport type { FileEntry } from './files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from './mcps-discovery'\nimport { createContext, useContext } from 'react'\n\nexport interface DiscoveryContextValue {\n /** Parsed `SkillConfig[]` from project + user scan paths. */\n skillsCatalog: readonly SkillConfig[]\n /** Parsed `mcps.json` entries. */\n mcpsCatalog: readonly DiscoveredMcp[]\n /** Non-fatal parse errors from `mcps.json` lookups. */\n mcpsErrors: readonly DiscoveryError[]\n /**\n * Per-server full tool catalog from the on-disk cache (see\n * `chat/mcp-tools-cache.ts`). Updated as MCP servers bootstrap and\n * fire the `mcp:tools:list` hook. Missing key → no cache yet; the\n * settings UI surfaces an empty-state row under the expanded server.\n */\n mcpToolsByServer: Record<string, readonly McpToolSchema[]>\n /** Current file walk results — empty until lazy-load lands. */\n filesCatalog: readonly FileEntry[]\n\n /** Force a fresh skills rescan. Resolves once state is committed. */\n refreshSkills: () => Promise<void>\n /** Re-parse `mcps.json`. Sync-shaped under the hood, async for symmetry. */\n refreshMcps: () => Promise<void>\n /** Force a fresh file walk. Wired into a future \"refresh files\" affordance. */\n refreshFiles: () => Promise<void>\n\n /** Idempotent first-load trigger for skills (used by `/` popover + modal mount). */\n ensureSkills: () => Promise<readonly SkillConfig[]>\n /** Idempotent first-load trigger for files (used by `@` popover). */\n ensureFiles: () => Promise<readonly FileEntry[]>\n}\n\nconst DiscoveryContext = createContext<DiscoveryContextValue | null>(null)\n\nexport function DiscoveryProvider({\n value,\n children,\n}: {\n value: DiscoveryContextValue\n children: ReactNode\n}) {\n return (\n <DiscoveryContext.Provider value={value}>\n {children}\n </DiscoveryContext.Provider>\n )\n}\n\n/**\n * Read live discovery state + actions. Throws if used outside a\n * `<DiscoveryProvider>` — discovery is a load-bearing dependency for\n * settings + completion popovers; bailing loud here surfaces wiring\n * mistakes at mount instead of producing empty catalogs at first\n * keystroke.\n */\nexport function useDiscovery(): DiscoveryContextValue {\n const ctx = useContext(DiscoveryContext)\n if (!ctx)\n throw new Error('useDiscovery must be used inside <DiscoveryProvider>')\n return ctx\n}\n\n/**\n * Non-throwing variant — returns `null` when no provider is mounted.\n * Used by composable modals (`<SettingsModal>`, …) that accept catalog\n * props as a fallback for embedders who don't wire the full TUI\n * shell. The in-app flow always mounts `<DiscoveryProvider>` so the\n * modal sees live state; standalone embeds get the prop snapshot.\n */\nexport function useDiscoveryOptional(): DiscoveryContextValue | null {\n return useContext(DiscoveryContext)\n}\n","/**\n * Stale-while-revalidate catalog slot.\n *\n * One slot per (catalog kind, projectDir) pin. Holds the first-load\n * promise + a `lastScannedAt` timestamp + an in-flight `refreshing`\n * promise. Idempotent across concurrent callers, safe to rotate when\n * the project changes (the slot's `abort` controller cancels in-flight\n * walks and the `.then` guards drop stale results).\n *\n * Renderer-agnostic. AppShell wraps it in React refs so React closures\n * stay stable; a non-TUI shell can use it directly against its own\n * state propagation.\n *\n * Contract:\n * - `ensure()` — kick the first load if not started; return the\n * cached promise. On subsequent calls, if `Date.now() -\n * lastScannedAt >= throttleMs` and no refresh is in flight, kick\n * a background revalidate that calls `onLoad(fresh)` when it\n * resolves. The original first-load promise still resolves with\n * the first walk's contents — consumers reading live state\n * elsewhere (React state) see the refreshed catalog.\n * - `refresh()` — force a rescan now, bypassing the throttle.\n * De-duplicates: if a refresh is already in flight, returns the\n * same promise.\n * - `abort()` — cancel any in-flight walk and detach. After this\n * the slot is dead; callers should rotate to a new slot.\n * - `dispose()` — alias of `abort()`, named for symmetry with\n * cleanup callbacks.\n *\n * No state is exposed except through promises + the host's `onLoad`\n * callback; the slot doesn't try to be a store, only a coordinator.\n */\n\n/** Walk function — takes an abort signal, returns the discovered list. */\nexport type Walk<T> = (signal: AbortSignal) => Promise<readonly T[]>\n\n/** Notification: a fresh walk has resolved. Host is expected to commit to its own state store. */\nexport type Commit<T> = (items: readonly T[]) => void\n\nexport interface DiscoverySlotOptions<T> {\n /** Discover function — runs the actual FS / network walk. */\n walk: Walk<T>\n /** Commit hook — receives every successful walk's result (first + refreshes). */\n onLoad: Commit<T>\n /**\n * Min interval between background refreshes triggered by `ensure()`.\n * Default: 3_000ms. `refresh()` ignores this — it's the user's\n * explicit-intent path.\n */\n throttleMs?: number\n /** Optional error sink. Defaults to no-op; useful for `debugLog`. */\n onError?: (err: unknown, phase: 'first-load' | 'refresh') => void\n}\n\nexport interface DiscoverySlot<T> {\n /**\n * Kick the first load (idempotent). Returns the first-load promise\n * once it exists. Schedules a background refresh if past throttle.\n */\n ensure: () => Promise<readonly T[]>\n /** Force-refresh now. De-duped across concurrent callers. */\n refresh: () => Promise<void>\n /** True if a refresh is currently running. */\n isRefreshing: () => boolean\n /** Cancel in-flight work + detach. Idempotent. */\n abort: () => void\n}\n\nexport function createDiscoverySlot<T>(options: DiscoverySlotOptions<T>): DiscoverySlot<T> {\n const throttleMs = options.throttleMs ?? 3_000\n const abortCtrl = new AbortController()\n let firstLoad: Promise<readonly T[]> | null = null\n let refreshing: Promise<void> | null = null\n let lastScannedAt = 0\n let aborted = false\n\n const handleError = (err: unknown, phase: 'first-load' | 'refresh'): void => {\n options.onError?.(err, phase)\n }\n\n const startRefresh = (): Promise<void> => {\n lastScannedAt = Date.now()\n const run = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return\n options.onLoad(items)\n })\n .catch((err) => {\n if (aborted)\n return\n handleError(err, 'refresh')\n })\n .finally(() => {\n if (refreshing === run)\n refreshing = null\n })\n refreshing = run\n return run\n }\n\n const slot: DiscoverySlot<T> = {\n ensure() {\n if (aborted)\n return Promise.resolve([] as readonly T[])\n if (!firstLoad) {\n lastScannedAt = Date.now()\n firstLoad = options.walk(abortCtrl.signal)\n .then((items) => {\n if (aborted)\n return [] as readonly T[]\n options.onLoad(items)\n return items\n })\n .catch((err) => {\n if (!aborted)\n handleError(err, 'first-load')\n return [] as readonly T[]\n })\n return firstLoad\n }\n // SWR: revalidate in the background if stale and not already running.\n if (!refreshing && Date.now() - lastScannedAt >= throttleMs)\n void startRefresh()\n return firstLoad\n },\n refresh() {\n if (aborted)\n return Promise.resolve()\n if (refreshing)\n return refreshing\n // Forced refresh from a user-initiated path (e.g. settings\n // modal `ctrl+R`). If the first load hasn't happened yet,\n // bootstrap through `ensure` so subsequent ensure callers see\n // the right `firstLoad` promise.\n if (!firstLoad)\n return slot.ensure().then(() => {})\n return startRefresh()\n },\n isRefreshing() {\n return refreshing !== null\n },\n abort() {\n if (aborted)\n return\n aborted = true\n abortCtrl.abort()\n },\n }\n return slot\n}\n","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Catppuccin — four flavors built from the official palette.\n//\n// Hex values mirror catppuccin/palette v1.8.0 verbatim. The palette is the\n// data; `catppuccinTheme()` is the mapping into our `Theme` shape — accent\n// → role colors, semantic UI tokens (mantle = modal), and Tree-sitter syntax\n// token mappings that line up with the official Catppuccin styleguide.\n//\n// `Latte` is a light flavor (`base = #eff1f5`, `text = #4c4f69`) and only\n// looks correct in a light terminal — same caveat as the upstream VSCode\n// theme. The other three are dark.\n// ---------------------------------------------------------------------------\n\n/**\n * One Catppuccin flavor as a flat hex map. Mirrors the structure of\n * `catppuccin/palette` so the builder below maps named colors → roles\n * without any per-flavor branching.\n */\ninterface CatppuccinPalette {\n rosewater: string\n flamingo: string\n pink: string\n mauve: string\n red: string\n maroon: string\n peach: string\n yellow: string\n green: string\n teal: string\n sky: string\n sapphire: string\n blue: string\n lavender: string\n text: string\n subtext1: string\n subtext0: string\n overlay2: string\n overlay1: string\n overlay0: string\n surface2: string\n surface1: string\n surface0: string\n base: string\n mantle: string\n crust: string\n}\n\nconst LATTE: CatppuccinPalette = {\n rosewater: '#dc8a78',\n flamingo: '#dd7878',\n pink: '#ea76cb',\n mauve: '#8839ef',\n red: '#d20f39',\n maroon: '#e64553',\n peach: '#fe640b',\n yellow: '#df8e1d',\n green: '#40a02b',\n teal: '#179299',\n sky: '#04a5e5',\n sapphire: '#209fb5',\n blue: '#1e66f5',\n lavender: '#7287fd',\n text: '#4c4f69',\n subtext1: '#5c5f77',\n subtext0: '#6c6f85',\n overlay2: '#7c7f93',\n overlay1: '#8c8fa1',\n overlay0: '#9ca0b0',\n surface2: '#acb0be',\n surface1: '#bcc0cc',\n surface0: '#ccd0da',\n base: '#eff1f5',\n mantle: '#e6e9ef',\n crust: '#dce0e8',\n}\n\nconst FRAPPE: CatppuccinPalette = {\n rosewater: '#f2d5cf',\n flamingo: '#eebebe',\n pink: '#f4b8e4',\n mauve: '#ca9ee6',\n red: '#e78284',\n maroon: '#ea999c',\n peach: '#ef9f76',\n yellow: '#e5c890',\n green: '#a6d189',\n teal: '#81c8be',\n sky: '#99d1db',\n sapphire: '#85c1dc',\n blue: '#8caaee',\n lavender: '#babbf1',\n text: '#c6d0f5',\n subtext1: '#b5bfe2',\n subtext0: '#a5adce',\n overlay2: '#949cbb',\n overlay1: '#838ba7',\n overlay0: '#737994',\n surface2: '#626880',\n surface1: '#51576d',\n surface0: '#414559',\n base: '#303446',\n mantle: '#292c3c',\n crust: '#232634',\n}\n\nconst MACCHIATO: CatppuccinPalette = {\n rosewater: '#f4dbd6',\n flamingo: '#f0c6c6',\n pink: '#f5bde6',\n mauve: '#c6a0f6',\n red: '#ed8796',\n maroon: '#ee99a0',\n peach: '#f5a97f',\n yellow: '#eed49f',\n green: '#a6da95',\n teal: '#8bd5ca',\n sky: '#91d7e3',\n sapphire: '#7dc4e4',\n blue: '#8aadf4',\n lavender: '#b7bdf8',\n text: '#cad3f5',\n subtext1: '#b8c0e0',\n subtext0: '#a5adcb',\n overlay2: '#939ab7',\n overlay1: '#8087a2',\n overlay0: '#6e738d',\n surface2: '#5b6078',\n surface1: '#494d64',\n surface0: '#363a4f',\n base: '#24273a',\n mantle: '#1e2030',\n crust: '#181926',\n}\n\nconst MOCHA: CatppuccinPalette = {\n rosewater: '#f5e0dc',\n flamingo: '#f2cdcd',\n pink: '#f5c2e7',\n mauve: '#cba6f7',\n red: '#f38ba8',\n maroon: '#eba0ac',\n peach: '#fab387',\n yellow: '#f9e2af',\n green: '#a6e3a1',\n teal: '#94e2d5',\n sky: '#89dceb',\n sapphire: '#74c7ec',\n blue: '#89b4fa',\n lavender: '#b4befe',\n text: '#cdd6f4',\n subtext1: '#bac2de',\n subtext0: '#a6adc8',\n overlay2: '#9399b2',\n overlay1: '#7f849c',\n overlay0: '#6c7086',\n surface2: '#585b70',\n surface1: '#45475a',\n surface0: '#313244',\n base: '#1e1e2e',\n mantle: '#181825',\n crust: '#11111b',\n}\n\n/**\n * Compose a `Theme` from a Catppuccin palette flavor.\n *\n * Role-color picks follow the upstream Catppuccin styleguide:\n * - `mauve` is the canonical accent — used here as the brand color.\n * - `green` / `red` / `yellow` keep their universal semantic meaning.\n * - `blue` carries function / model identity (matches the VSCode plugin's\n * `support.function` mapping).\n * - `subtext1` / `overlay0` form the dim/mute pair (one step apart so the\n * two tiers stay visually distinct on every flavor).\n * - `surface1` / `overlay0` give the resting/active border pair.\n *\n * Syntax token mappings line up with the official Catppuccin token rules\n * (keyword = mauve, string = green, function = blue, type = yellow, …) so\n * code fences match what users see in their editor.\n */\nfunction catppuccinTheme(id: string, label: string, p: CatppuccinPalette): Theme {\n return {\n id,\n label,\n colors: {\n brand: p.mauve,\n accent: p.green,\n model: p.blue,\n warn: p.yellow,\n error: p.red,\n dim: p.subtext1,\n mute: p.overlay0,\n border: p.surface1,\n borderActive: p.overlay0,\n // Throbber sweeps mauve → pink — keeps the gradient inside\n // Catppuccin's warm violet/pink family. The fallback `brand →\n // accent` would interpolate mauve → green through grey.\n throbber: { from: p.mauve, to: p.pink },\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: p.mauve,\n textColor: p.subtext1,\n descriptionColor: p.overlay0,\n selectedDescriptionColor: p.subtext0,\n },\n surfaces: {\n // `crust` is the deepest tier in the palette — one step below\n // `mantle`, so the root surface stays distinct from the modal\n // elevation on every flavor (including Latte, where `crust` is\n // the darkest light-flavor tone).\n background: p.crust,\n // `mantle` is one shade darker than `base`, mirroring how the upstream\n // VSCode theme paints the sidebar against the editor — a subtle but\n // present elevation cue for the modal panel.\n modal: p.mantle,\n // Chip pills — skills on `mauve` (the canonical Catppuccin accent,\n // matches the brand role) and files on `blue` (function/identifier\n // accent in the upstream styleguide, so file mentions visually\n // rhyme with code references). Foreground is `base` for all\n // pills so dark flavors read light-on-dark and Latte's mauve/blue\n // are dark enough that `base` (`#eff1f5`) contrasts.\n chips: {\n default: { bg: p.mauve, fg: p.base },\n skills: { bg: p.mauve, fg: p.base },\n files: { bg: p.blue, fg: p.base },\n },\n // `surface0` is Catppuccin's canonical \"row highlight\" tier — one\n // step above `base`, designed for subtle elevation. Works on every\n // flavor including Latte (where it darkens against the light base).\n selection: p.surface0,\n // Diff blocks ride the palette's `green` / `red` accents pre-mixed\n // against `base`. We lean on `surface0` / `surface1` for the row\n // paint and the brighter palette accents for the sign glyph so the\n // `+` / `-` markers pop against the dimmer row tint. Catppuccin\n // doesn't ship `*Container` tokens, so we just compose with the\n // surface tiers — works on every flavor (including Latte by\n // virtue of `surface*` darkening against the light base).\n diff: {\n addBg: p.surface0,\n removeBg: p.surface0,\n addContentBg: p.surface1,\n removeContentBg: p.surface1,\n addFg: p.green,\n removeFg: p.red,\n },\n },\n syntax: {\n 'default': { fg: p.text },\n\n // ---- markdown structure ----\n 'markup.heading': { fg: p.mauve, bold: true },\n 'markup.heading.1': { fg: p.mauve, bold: true },\n 'markup.heading.2': { fg: p.lavender, bold: true },\n 'markup.heading.3': { fg: p.blue, bold: true },\n 'markup.bold': { fg: p.text, bold: true },\n 'markup.strong': { fg: p.text, bold: true },\n 'markup.italic': { fg: p.text, italic: true },\n 'markup.link': { fg: p.sky, underline: true },\n 'markup.link.url': { fg: p.sky, underline: true },\n 'markup.list': { fg: p.peach },\n 'markup.raw': { fg: p.green },\n 'markup.raw.block': { fg: p.green },\n 'markup.quote': { fg: p.overlay2, italic: true },\n\n // ---- code ----\n 'keyword': { fg: p.mauve, bold: true },\n 'keyword.import': { fg: p.pink, bold: true },\n 'keyword.operator': { fg: p.sky },\n 'string': { fg: p.green },\n 'string.escape': { fg: p.pink, bold: true },\n 'character': { fg: p.teal },\n 'comment': { fg: p.overlay1, italic: true },\n 'number': { fg: p.peach },\n 'boolean': { fg: p.peach },\n 'constant': { fg: p.peach },\n 'constant.builtin': { fg: p.peach },\n 'function': { fg: p.blue },\n 'function.call': { fg: p.blue },\n 'function.method': { fg: p.blue },\n 'function.method.call': { fg: p.blue },\n 'function.builtin': { fg: p.blue },\n 'function.macro': { fg: p.teal },\n 'type': { fg: p.yellow },\n 'type.builtin': { fg: p.yellow },\n 'constructor': { fg: p.yellow },\n 'attribute': { fg: p.yellow },\n 'tag': { fg: p.lavender },\n 'variable': { fg: p.text },\n 'variable.builtin': { fg: p.red },\n 'variable.parameter': { fg: p.maroon, italic: true },\n 'variable.member': { fg: p.text },\n 'property': { fg: p.lavender },\n 'operator': { fg: p.sky },\n 'punctuation': { fg: p.overlay2 },\n 'punctuation.bracket': { fg: p.overlay2 },\n 'punctuation.delimiter': { fg: p.overlay2 },\n 'label': { fg: p.sapphire },\n },\n }\n}\n\nexport const CATPPUCCIN_MOCHA = catppuccinTheme('catppuccin-mocha', 'Catppuccin Mocha', MOCHA)\nexport const CATPPUCCIN_MACCHIATO = catppuccinTheme('catppuccin-macchiato', 'Catppuccin Macchiato', MACCHIATO)\nexport const CATPPUCCIN_FRAPPE = catppuccinTheme('catppuccin-frappe', 'Catppuccin Frappé', FRAPPE)\n// Latte assumes a *light* terminal background (`base = #eff1f5`,\n// `text = #4c4f69`). The label is suffixed so the picker reads correctly\n// regardless of where in the cycle it lands.\nexport const CATPPUCCIN_LATTE = catppuccinTheme('catppuccin-latte', 'Catppuccin Latte (light)', LATTE)\n","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Crush — Charm's CharmTone \"Pantera\" dark theme.\n//\n// Palette hex values are copied verbatim from\n// `charmbracelet/x/exp/charmtone/charmtone.go`. Role assignments and visual\n// paint follow `charmbracelet/crush`'s `internal/ui/styles/quickstyle.go`\n// (the actual `Styles` struct populated for Pantera), not just the\n// `quickStyleOpts` palette — because the Chroma map, Glamour heading\n// banner, link split, and diff overrides all diverge from a naive\n// \"primary → brand\" reading.\n//\n// Notable Pantera idiosyncrasies preserved here:\n// - Headings ride `info` (Malibu blue) + bold, not the brand violet. The\n// brand violet only appears as H1's *banner background*, with `Zest`\n// yellow body text. See `quickstyle.go` `Heading` / `H1`.\n// - Inline code is `destructive` (Coral) on `bgLessVisible` (Charcoal).\n// - LinkText is `successMostSubtle` (Guac) bold; URLs are `Zinc` teal\n// with underline.\n// - Diff line / symbol / code colors are *muted* forest/maroon\n// (`#629657` / `#a45c59`) on `#323931` / `#383030`, overriding the\n// brighter `Turtle` / `Cherry` defaults in `diffview/style.go`.\n// - Surface tier cascade is Pepper → BBQ (modal) → Charcoal (selection),\n// matching `bgBase` → `bgLeastVisible` → `bgLessVisible` in Crush.\n// ---------------------------------------------------------------------------\n\nconst CHARMTONE = {\n // warm / red\n cumin: '#BF976F',\n bengal: '#FF6E63',\n sriracha: '#EB4268',\n coral: '#FF577D',\n salmon: '#FF7F90',\n cherry: '#FF388B',\n // pinks / magentas\n cheeky: '#FF79D0',\n blush: '#FF84FF',\n dolly: '#FF60FF',\n pony: '#FF4FBF',\n // purples\n mauve: '#D46EFF',\n hazy: '#8B75FF',\n charple: '#6B50FF',\n // blues\n guppy: '#7272FF',\n malibu: '#00A4FF',\n sardine: '#4FBEFE',\n // greens / teals\n zinc: '#10B1AE',\n turtle: '#0ADCD9',\n guac: '#12C78F',\n julep: '#00FFB2',\n bok: '#68FFD6',\n // yellows\n mustard: '#F5EF34',\n citron: '#E8FF27',\n zest: '#E8FE96',\n // neutrals (dark → light)\n pepper: '#201F26',\n bbq: '#2D2C35',\n charcoal: '#3A3943',\n iron: '#4D4C57',\n oyster: '#605F6B',\n squid: '#858392',\n smoke: '#BFBCC8',\n ash: '#DFDBDD',\n salt: '#F1EFEF',\n butter: '#FFFAF1',\n} as const\n\nconst c = CHARMTONE\n\nexport const CRUSH_THEME: Theme = {\n id: 'crush',\n label: 'Crush (Pantera)',\n colors: {\n // `Charple` is Pantera's `primary` — Crush dialog frames, focused\n // user-message borders, completions selection all paint with this.\n brand: c.charple,\n // `Bok` is the `accent` role (see `themes.go`). Soft mint that reads\n // as \"alive\" without competing with the brand violet.\n accent: c.bok,\n // `Malibu` is Pantera's `info` — also where headings and tool names\n // land, so it doubles as the model-identity tone here.\n model: c.malibu,\n warn: c.julep,\n // `Zest` is Charmtone's dim-yellow; reserved here for the footer's\n // cost indicator so the money figure reads as money even though\n // Crush's `warn` role intentionally stays mint (see above).\n money: c.zest,\n error: c.sriracha,\n // `Smoke` is `fgSubtle` — the body color Markdown documents inherit.\n dim: c.smoke,\n // `Oyster` is `fgMostSubtle` — tertiary tone, dimmer than `dim`.\n mute: c.oyster,\n // `Charcoal` is `separator` / `bgLessVisible` — Crush's resting border.\n border: c.charcoal,\n // Focused elements in Crush get a `primary` (Charple) rim — dialog\n // frames, focused message borders, the compact details panel.\n borderActive: c.charple,\n // Working-throbber gradient: `primary` → `secondary`, exactly the\n // `WorkingGradFromColor` / `WorkingGradToColor` pair Crush's\n // `quickStyle` populates for the `Anim` spinner.\n throbber: { from: c.charple, to: c.dolly },\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: c.charple,\n textColor: c.smoke,\n descriptionColor: c.oyster,\n selectedDescriptionColor: c.squid,\n },\n surfaces: {\n // Surface cascade mirrors Crush's neutral tier ladder:\n // Pepper (bgBase) → BBQ (bgLeastVisible) → Charcoal (bgLessVisible).\n background: c.pepper,\n modal: c.bbq,\n // Chips:\n // - skills (slash commands) ride `primary` / `onPrimary` — the same\n // pair Crush uses for `Completions.Focused` and\n // `Dialog.SelectedItem`.\n // - files ride `info` / `onPrimary` — `info` carries tool / file /\n // reference identity throughout Crush (JobToolName, MCPName).\n chips: {\n default: { bg: c.charple, fg: c.butter },\n skills: { bg: c.charple, fg: c.butter },\n files: { bg: c.malibu, fg: c.butter },\n },\n // Selected-turn lift — `Charcoal`, the same tier Crush uses for\n // separators and resting borders. One step above `modal` (BBQ).\n selection: c.charcoal,\n // Diff blocks — values from Crush's `Styles.Diff` overrides in\n // `quickstyle.go` (lines ~480–500), not the brighter `DefaultDarkStyle`\n // in `diffview/style.go`. Symbol fg `#629657` / `#a45c59` rides the\n // muted forest / maroon Crush actually renders.\n diff: {\n addBg: '#2b322a',\n removeBg: '#312929',\n addContentBg: '#323931',\n removeContentBg: '#383030',\n addFg: '#629657',\n removeFg: '#a45c59',\n },\n },\n syntax: {\n // Chroma `Text` → `fgSubtle` (Smoke). Code body reads one tier below\n // `Ash` / `Salt` — matches Crush's `CodeBlock.Chroma.Text` color.\n 'default': { fg: c.smoke },\n\n // ---- markdown structure ----\n // Heading parent block: `info` + bold. H2-H5 inherit this in Crush\n // (they only add prefix tokens), so we replicate Malibu+bold across\n // levels and override H1 with the brand banner below.\n 'markup.heading': { fg: c.malibu, bold: true },\n // H1 is Crush's signature: bold body text on a `primary` (Charple)\n // background banner. Crush ships `warningSubtle` (Zest yellow) text\n // there; we use `onPrimary` (Butter) cream instead — same banner\n // vibe, drops the yellow that was bleeding into the rest of the UI.\n 'markup.heading.1': { fg: c.butter, bg: c.charple, bold: true },\n 'markup.heading.2': { fg: c.malibu, bold: true },\n 'markup.heading.3': { fg: c.malibu, bold: true },\n // Strong / Emph in Crush set only weight/slant — color inherits from\n // the Document (Smoke). We match by keeping `fg: smoke`.\n 'markup.bold': { fg: c.smoke, bold: true },\n 'markup.strong': { fg: c.smoke, bold: true },\n 'markup.italic': { fg: c.smoke, italic: true },\n // Crush splits links: `LinkText` (the visible label) is\n // `successMostSubtle` (Guac) + bold; `Link` (the URL) is `Zinc`\n // (teal) + underline.\n 'markup.link': { fg: c.guac, bold: true },\n 'markup.link.url': { fg: c.zinc, underline: true },\n // List `Item` in Crush has no color override — bullets inherit\n // Document color (Smoke).\n 'markup.list': { fg: c.smoke },\n // Inline `Code`: `destructive` (Coral) on `bgLessVisible` (Charcoal).\n 'markup.raw': { fg: c.coral, bg: c.charcoal },\n 'markup.raw.block': { fg: c.coral, bg: c.charcoal },\n // BlockQuote in Crush is just indented with `│ ` — no color or italic.\n // We keep `fg: smoke` to match Document inheritance.\n 'markup.quote': { fg: c.smoke },\n\n // ---- code (Chroma mapping from `quickstyle.go`) ----\n 'keyword': { fg: c.malibu }, // Chroma `Keyword` → info\n 'keyword.import': { fg: c.pony }, // KeywordNamespace\n 'keyword.operator': { fg: c.salmon }, // Operator\n 'string': { fg: c.cumin }, // LiteralString\n 'string.escape': { fg: c.bok }, // LiteralStringEscape → successMoreSubtle\n 'character': { fg: c.cumin },\n 'comment': { fg: c.oyster, italic: true }, // fgMostSubtle (italic kept for legibility)\n 'number': { fg: c.julep }, // LiteralNumber → success\n 'boolean': { fg: c.julep },\n 'constant': { fg: c.julep },\n 'constant.builtin': { fg: c.julep },\n 'function': { fg: c.guac }, // NameFunction → successMostSubtle\n 'function.call': { fg: c.guac },\n 'function.method': { fg: c.guac },\n 'function.method.call': { fg: c.guac },\n 'function.builtin': { fg: c.cheeky }, // NameBuiltin\n 'function.macro': { fg: c.bok }, // NameDecorator\n 'type': { fg: c.guppy }, // KeywordType\n 'type.builtin': { fg: c.guppy },\n // `NameClass` in Crush is `Salt` + bold + underline. We drop underline\n // (noisy for non-class types in our broader `constructor` capture).\n 'constructor': { fg: c.salt, bold: true },\n 'attribute': { fg: c.hazy }, // NameAttribute\n 'tag': { fg: c.mauve }, // NameTag\n 'variable': { fg: c.smoke }, // Name → fgSubtle\n 'variable.builtin': { fg: c.cheeky },\n 'variable.parameter': { fg: c.smoke, italic: true },\n 'variable.member': { fg: c.smoke },\n 'property': { fg: c.smoke },\n 'operator': { fg: c.salmon },\n // Crush maps `Punctuation → warningSubtle` (Zest), but at our token\n // granularity that paints every bracket / comma / dot yellow — too\n // much yellow in dense code. Drop to `Squid` (fgMoreSubtle) so\n // punctuation recedes behind the colored tokens it separates.\n 'punctuation': { fg: c.squid },\n 'punctuation.bracket': { fg: c.squid },\n 'punctuation.delimiter': { fg: c.squid },\n 'label': { fg: c.malibu },\n },\n}\n","import type { Theme } from '../theme'\n\ninterface GruvboxPalette {\n dark: boolean\n bg0: string\n bg1: string\n bg2: string\n bg3: string\n bg4: string\n fg0: string\n fg1: string\n fg2: string\n fg3: string\n fg4: string\n red: string\n green: string\n yellow: string\n blue: string\n purple: string\n aqua: string\n orange: string\n gray: string\n}\n\nconst DARK: GruvboxPalette = {\n dark: true,\n bg0: '#282828',\n bg1: '#3c3836',\n bg2: '#504945',\n bg3: '#665c54',\n bg4: '#7c6f64',\n fg0: '#fbf1c7',\n fg1: '#ebdbb2',\n fg2: '#d5c4a1',\n fg3: '#bdae93',\n fg4: '#a89984',\n red: '#fb4934',\n green: '#b8bb26',\n yellow: '#fabd2f',\n blue: '#83a598',\n purple: '#d3869b',\n aqua: '#8ec07c',\n orange: '#fe8019',\n gray: '#928374',\n}\n\nconst LIGHT: GruvboxPalette = {\n dark: false,\n bg0: '#fbf1c7',\n bg1: '#ebdbb2',\n bg2: '#d5c4a1',\n bg3: '#bdae93',\n bg4: '#a89984',\n fg0: '#282828',\n fg1: '#3c3836',\n fg2: '#504945',\n fg3: '#665c54',\n fg4: '#7c6f64',\n red: '#cc241d',\n green: '#98971a',\n yellow: '#d79921',\n blue: '#458588',\n purple: '#b16286',\n aqua: '#689d6a',\n orange: '#d65d0e',\n gray: '#928374',\n}\n\nfunction gruvboxTheme(id: string, label: string, p: GruvboxPalette): Theme {\n return {\n id,\n label,\n colors: {\n brand: p.yellow,\n accent: p.green,\n model: p.blue,\n warn: p.orange,\n error: p.red,\n dim: p.fg3,\n mute: p.gray,\n border: p.bg2,\n borderActive: p.bg4,\n throbber: { from: p.yellow, to: p.orange },\n money: p.yellow,\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: p.yellow,\n textColor: p.fg2,\n descriptionColor: p.fg4,\n selectedDescriptionColor: p.fg3,\n },\n surfaces: {\n background: p.dark ? '#1d2021' : '#f9f5d7',\n modal: p.bg0,\n chips: {\n default: { bg: p.yellow, fg: p.dark ? p.bg0 : p.fg0 },\n skills: { bg: p.yellow, fg: p.dark ? p.bg0 : p.fg0 },\n files: { bg: p.blue, fg: p.dark ? p.bg0 : '#fbf1c7' },\n },\n selection: p.bg1,\n diff: {\n addBg: p.bg1,\n removeBg: p.bg1,\n addContentBg: p.bg2,\n removeContentBg: p.bg2,\n addFg: p.green,\n removeFg: p.red,\n },\n },\n syntax: {\n 'default': { fg: p.fg1 },\n 'markup.heading': { fg: p.yellow, bold: true },\n 'markup.heading.1': { fg: p.yellow, bold: true },\n 'markup.heading.2': { fg: p.orange, bold: true },\n 'markup.heading.3': { fg: p.blue, bold: true },\n 'markup.bold': { fg: p.fg0, bold: true },\n 'markup.strong': { fg: p.fg0, bold: true },\n 'markup.italic': { fg: p.fg1, italic: true },\n 'markup.link': { fg: p.blue, underline: true },\n 'markup.link.url': { fg: p.blue, underline: true },\n 'markup.list': { fg: p.orange },\n 'markup.raw': { fg: p.green },\n 'markup.raw.block': { fg: p.green },\n 'markup.quote': { fg: p.fg4, italic: true },\n\n 'keyword': { fg: p.red, bold: true },\n 'keyword.import': { fg: p.red, bold: true },\n 'keyword.operator': { fg: p.orange },\n 'string': { fg: p.green },\n 'string.escape': { fg: p.orange, bold: true },\n 'character': { fg: p.aqua },\n 'comment': { fg: p.gray, italic: true },\n 'number': { fg: p.purple },\n 'boolean': { fg: p.purple },\n 'constant': { fg: p.purple },\n 'constant.builtin': { fg: p.purple },\n 'function': { fg: p.blue },\n 'function.call': { fg: p.blue },\n 'function.method': { fg: p.blue },\n 'function.method.call': { fg: p.blue },\n 'function.builtin': { fg: p.blue },\n 'function.macro': { fg: p.aqua },\n 'type': { fg: p.yellow },\n 'type.builtin': { fg: p.yellow },\n 'constructor': { fg: p.yellow },\n 'attribute': { fg: p.aqua },\n 'tag': { fg: p.aqua },\n 'variable': { fg: p.fg1 },\n 'variable.builtin': { fg: p.red },\n 'variable.parameter': { fg: p.orange, italic: true },\n 'variable.member': { fg: p.fg1 },\n 'property': { fg: p.blue },\n 'operator': { fg: p.orange },\n 'punctuation': { fg: p.fg4 },\n 'punctuation.bracket': { fg: p.fg2 },\n 'punctuation.delimiter': { fg: p.fg4 },\n 'label': { fg: p.aqua },\n },\n }\n}\n\nexport const GRUVBOX_DARK = gruvboxTheme('gruvbox-dark', 'Gruvbox Dark', DARK)\nexport const GRUVBOX_LIGHT = gruvboxTheme('gruvbox-light', 'Gruvbox Light', LIGHT)\n","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Light — the vanilla light theme every mainstream app ships (Slack, VS Code,\n// Zed, google.com): white background, near-black text, one restrained blue\n// brand accent, conventional green / red / amber for success / error / warn.\n// Not a \"flavor\" of any palette family — deliberately neutral so users who\n// just want \"regular light mode\" have an opt-in that isn't a special theme.\n//\n// Palette draws from the GitHub Light register because it's the most widely\n// recognized neutral light palette in developer tooling and reads correctly\n// on every light terminal background.\n// ---------------------------------------------------------------------------\n\nconst TEXT = '#1f2328' // primary foreground — graphite, not pure black\nconst DIM = '#656d76' // secondary text — captions, helper rows\nconst MUTE = '#8c959f' // tertiary text — separators, the `┃` bar\nconst BORDER = '#d0d7de' // resting border\nconst BORDER_ACTIVE = '#0969da' // brand blue — focused / active rims\n\nconst BRAND = '#0969da' // GitHub link blue — the universal \"click me\" hue\nconst ACCENT = '#1a7f37' // success green\nconst MODEL = '#0550ae' // deeper brand sibling — model id, links\nconst WARN = '#9a6700' // amber on light — readable, doesn't blow out\nconst ERROR = '#d1242f' // danger red\n\nexport const LIGHT_THEME: Theme = {\n id: 'light',\n label: 'Light',\n colors: {\n brand: BRAND,\n accent: ACCENT,\n model: MODEL,\n warn: WARN,\n error: ERROR,\n dim: DIM,\n mute: MUTE,\n border: BORDER,\n borderActive: BORDER_ACTIVE,\n // Throbber sweeps the two brand-blue siblings — stays in-family\n // without detouring through green via the fallback.\n throbber: { from: BRAND, to: MODEL },\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: BRAND,\n textColor: TEXT,\n descriptionColor: MUTE,\n selectedDescriptionColor: DIM,\n },\n surfaces: {\n background: '#ffffff',\n // One tier up from the body — same elevation cue every light app\n // uses (GitHub `canvas.subtle`, VS Code light sidebar, …).\n modal: '#f6f8fa',\n // Skills get the saturated brand pill (blue bg, near-white fg).\n // Files get the inverse — pale blue bg with deep brand fg — so\n // the two reference kinds read as paired counterparts on light.\n chips: {\n default: { bg: BRAND, fg: '#ffffff' },\n skills: { bg: BRAND, fg: '#ffffff' },\n files: { bg: '#ddf4ff', fg: '#0550ae' },\n },\n // GitHub's PR-row \"info\" tint — subtle blue lift that reads as\n // selection without overpowering the body text.\n selection: '#ddf4ff',\n // GitHub diff palette. Row paint is the pale `*Subtle` tier; the\n // content column punches a half-tier deeper so syntax-highlighted\n // code contrasts cleanly against the gutter.\n diff: {\n addBg: '#dafbe1',\n removeBg: '#ffebe9',\n addContentBg: '#aceebb',\n removeContentBg: '#ffcecb',\n addFg: ACCENT,\n removeFg: ERROR,\n },\n },\n syntax: {\n // ---- markdown structure ----\n // Headings cascade through brand blue → deeper brand → graphite so\n // the outline reads as one related family. Body rides `TEXT`.\n 'default': { fg: TEXT },\n 'markup.heading': { fg: BRAND, bold: true },\n 'markup.heading.1': { fg: BRAND, bold: true },\n 'markup.heading.2': { fg: MODEL, bold: true },\n 'markup.heading.3': { fg: TEXT, bold: true },\n 'markup.bold': { fg: TEXT, bold: true },\n 'markup.strong': { fg: TEXT, bold: true },\n 'markup.italic': { fg: TEXT, italic: true },\n 'markup.link': { fg: BRAND, underline: true },\n 'markup.link.url': { fg: BRAND, underline: true },\n 'markup.list': { fg: '#953800' }, // warm orange — stands apart from headings + body\n 'markup.raw': { fg: '#953800' },\n 'markup.raw.block': { fg: '#953800' },\n 'markup.quote': { fg: DIM, italic: true },\n\n // ---- code (GitHub Light token mapping) ----\n 'keyword': { fg: '#cf222e', bold: true },\n 'keyword.import': { fg: '#cf222e', bold: true },\n 'keyword.operator': { fg: '#cf222e' },\n 'string': { fg: '#0a3069' },\n 'string.escape': { fg: '#0a3069', bold: true },\n 'character': { fg: '#0a3069' },\n 'comment': { fg: '#6e7781', italic: true },\n 'number': { fg: '#0550ae' },\n 'boolean': { fg: '#0550ae' },\n 'constant': { fg: '#0550ae' },\n 'constant.builtin': { fg: '#0550ae' },\n 'function': { fg: '#8250df' },\n 'function.call': { fg: '#8250df' },\n 'function.method': { fg: '#8250df' },\n 'function.method.call': { fg: '#8250df' },\n 'function.builtin': { fg: '#8250df' },\n 'function.macro': { fg: '#8250df' },\n 'type': { fg: '#953800' },\n 'type.builtin': { fg: '#953800' },\n 'constructor': { fg: '#953800' },\n 'attribute': { fg: '#0550ae' },\n 'tag': { fg: '#116329' },\n 'variable': { fg: TEXT },\n 'variable.builtin': { fg: '#0550ae' },\n 'variable.parameter': { fg: '#953800' },\n 'variable.member': { fg: '#0550ae' },\n 'property': { fg: '#0550ae' },\n 'operator': { fg: '#cf222e' },\n 'punctuation': { fg: DIM },\n 'punctuation.bracket': { fg: TEXT },\n 'punctuation.delimiter': { fg: TEXT },\n 'label': { fg: '#0550ae' },\n },\n}\n","import type { Theme } from '../theme'\n\n// ---------------------------------------------------------------------------\n// Vaporwave — neon-on-dark, ported from this-fifo/vaporwave-theme-vscode.\n//\n// Hex values come straight from the upstream `vaporwave-color-theme.json`\n// (terminal ANSI table + tokenColors). The signature pink (#E95378) carries\n// the brand role; the bright pink (#FF71CE) is reserved for the keyword\n// token so headings and keywords visually rhyme without competing.\n// ---------------------------------------------------------------------------\n\nconst PALETTE = {\n pink: '#E95378',\n pinkBright: '#FF71CE',\n cyan: '#01CDFE',\n cyanBright: '#59E1E3',\n blue: '#94D0FF',\n green: '#29D398',\n greenBright: '#09F7A0',\n yellow: '#FFFB96',\n red: '#F43E5C',\n text: '#D5D8DA',\n /**\n * ~70% of `text` — used for captions / helper / \"dim\" text so that\n * secondary copy is visually distinct from primary copy. Matches the\n * upstream theme's sidebar foreground (`#b7c5d3`) tonality.\n */\n textDim: '#B7C5D3',\n textBright: '#EEFFFF',\n comment: '#BBBBBB',\n muted: '#6C6F93',\n surface: '#2E303E',\n panel: '#232530',\n // One step below `panel` — mirrors the upstream theme's editor background\n // relative to widgets so modals continue to read as elevated.\n bgDeep: '#1a1c26',\n}\n\nexport const VAPORWAVE_THEME: Theme = {\n id: 'vaporwave',\n label: 'Vaporwave',\n colors: {\n brand: PALETTE.pink,\n accent: PALETTE.greenBright,\n model: PALETTE.blue,\n warn: PALETTE.yellow,\n error: PALETTE.red,\n dim: PALETTE.textDim,\n mute: PALETTE.muted,\n border: PALETTE.surface,\n borderActive: PALETTE.muted,\n // The iconic vaporwave gradient — hot pink to electric cyan. The\n // fallback `brand → accent` would sweep pink → neon green, which\n // misses the synth-cassette aesthetic entirely.\n throbber: { from: PALETTE.pink, to: PALETTE.cyan },\n },\n select: {\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: PALETTE.pink,\n textColor: PALETTE.text,\n descriptionColor: PALETTE.muted,\n selectedDescriptionColor: PALETTE.text,\n },\n surfaces: {\n // Root paint — sits one tier below `modal` so overlaid panels still\n // read as elevated.\n background: PALETTE.bgDeep,\n // Upstream uses `#232530` for editor widgets / dropdowns — same elevation\n // role as our modal panels.\n modal: PALETTE.panel,\n // Chip pills — skills on the signature vaporwave pink (brand role)\n // and files on cyan (the upstream's secondary neon accent). Both\n // read unmistakably against the dark panel foreground.\n chips: {\n default: { bg: PALETTE.pink, fg: PALETTE.panel },\n skills: { bg: PALETTE.pink, fg: PALETTE.panel },\n files: { bg: PALETTE.cyan, fg: PALETTE.panel },\n },\n // `surface` is one shade above `panel` — a quiet purple-grey lift\n // that reads as \"active row\" without clashing with the neon pink.\n selection: PALETTE.surface,\n // Diff blocks — vaporwave doesn't ship desaturated red/green palette\n // tones, so we hand-mix dark wines + dark teals against `panel` for\n // the row paint and brighter neon `green` / `red` for the sign\n // glyph. Keeps the neon vibe without drowning the syntax-highlighted\n // code under saturated bands.\n diff: {\n addBg: '#1a3528',\n removeBg: '#3a1a23',\n addContentBg: '#234c39',\n removeContentBg: '#52232f',\n addFg: PALETTE.greenBright,\n removeFg: PALETTE.red,\n },\n },\n syntax: {\n 'default': { fg: PALETTE.text },\n\n // ---- markdown structure ----\n 'markup.heading': { fg: PALETTE.pink, bold: true },\n 'markup.heading.1': { fg: PALETTE.pink, bold: true },\n 'markup.heading.2': { fg: PALETTE.pinkBright, bold: true },\n 'markup.heading.3': { fg: PALETTE.blue, bold: true },\n 'markup.bold': { fg: PALETTE.textBright, bold: true },\n 'markup.strong': { fg: PALETTE.textBright, bold: true },\n 'markup.italic': { fg: PALETTE.greenBright, italic: true },\n 'markup.link': { fg: PALETTE.yellow, underline: true },\n 'markup.link.url': { fg: PALETTE.yellow, underline: true },\n 'markup.list': { fg: PALETTE.textBright },\n 'markup.raw': { fg: PALETTE.yellow },\n 'markup.raw.block': { fg: PALETTE.yellow },\n 'markup.quote': { fg: PALETTE.yellow, italic: true },\n\n // ---- code (mirrors the upstream tokenColors mapping) ----\n 'keyword': { fg: PALETTE.pinkBright, bold: true },\n 'keyword.import': { fg: PALETTE.pinkBright, bold: true },\n 'keyword.operator': { fg: PALETTE.comment },\n 'string': { fg: PALETTE.yellow },\n 'string.escape': { fg: PALETTE.greenBright, bold: true },\n 'character': { fg: PALETTE.yellow },\n 'comment': { fg: PALETTE.comment, italic: true },\n 'number': { fg: PALETTE.textBright },\n 'boolean': { fg: PALETTE.textBright },\n 'constant': { fg: PALETTE.textBright },\n 'constant.builtin': { fg: PALETTE.textBright },\n 'function': { fg: PALETTE.greenBright },\n 'function.call': { fg: PALETTE.greenBright },\n 'function.method': { fg: PALETTE.greenBright },\n 'function.method.call': { fg: PALETTE.greenBright },\n 'function.builtin': { fg: PALETTE.greenBright },\n 'function.macro': { fg: PALETTE.greenBright },\n 'type': { fg: PALETTE.greenBright },\n 'type.builtin': { fg: PALETTE.greenBright },\n 'constructor': { fg: PALETTE.greenBright },\n 'attribute': { fg: PALETTE.yellow },\n 'tag': { fg: PALETTE.blue },\n 'variable': { fg: PALETTE.blue },\n 'variable.builtin': { fg: PALETTE.cyanBright },\n 'variable.parameter': { fg: PALETTE.text },\n 'variable.member': { fg: PALETTE.blue },\n 'property': { fg: PALETTE.blue },\n 'operator': { fg: PALETTE.comment },\n 'punctuation': { fg: PALETTE.comment },\n 'punctuation.bracket': { fg: PALETTE.textBright },\n 'punctuation.delimiter': { fg: PALETTE.comment },\n 'label': { fg: PALETTE.cyan },\n },\n}\n","/**\n * Renderer-agnostic theme system.\n *\n * A `Theme` bundles every variable that can change visually between\n * \"themes\" — colors, select-row styling, code/markdown syntax highlight\n * tokens, panel backgrounds. Plain JSON: no OpenTUI dependency, no React,\n * no functions. The TUI consumes the theme by reading from `useTheme()`\n * and converting hex strings into OpenTUI's `RGBA`/`SyntaxStyle`; a future\n * GUI consumes the same theme by converting into CSS variables or Tailwind\n * tokens.\n *\n * Components should always read theme values through `useTheme()` /\n * `useColors()` so a runtime theme switch (Settings → Theme) re-paints\n * the whole tree. Importing a raw theme object directly bypasses the\n * context and pins the component to a single look.\n *\n * Built-in flavors (Catppuccin, Vaporwave) live in `./themes/`. This file\n * just defines the shape + the default theme + the registry.\n */\n\nimport {\n CATPPUCCIN_FRAPPE,\n CATPPUCCIN_LATTE,\n CATPPUCCIN_MACCHIATO,\n CATPPUCCIN_MOCHA,\n} from './themes/catppuccin'\nimport { CRUSH_THEME } from './themes/crush'\nimport { GRUVBOX_DARK, GRUVBOX_LIGHT } from './themes/gruvbox'\nimport { LIGHT_THEME } from './themes/light'\nimport { VAPORWAVE_THEME } from './themes/vaporwave'\n\n/** Role-named color palette. Reused throughout the UI for text, borders, accents. */\nexport interface ThemeColors {\n /** Primary accent — selected text, brand emphasis (e.g. `▶` markers). */\n brand: string\n /** Success / progress accent — used sparingly. */\n accent: string\n /** Model badge accent (provider/model name in the footer, links). */\n model: string\n /** Warnings — busy spinner, approval picker border, esc hints. */\n warn: string\n /** Errors — `✗` prefix on error events, validation messages. */\n error: string\n /** Secondary text — captions, helper text, dim transcript lines. */\n dim: string\n /** Tertiary text — separators, descriptions, the `┃` tool-result bar. */\n mute: string\n /** Resting border color. */\n border: string\n /** Border color on focused / active elements. */\n borderActive: string\n /**\n * Optional gradient endpoints for the in-chat working throbber\n * (`<CrushThrobber>`). Falls back to `[brand, accent]` when unset.\n * Themes can pin a richer pair here — e.g. Crush's `Charple` →\n * `Dolly` purple-to-pink — without having to repurpose `accent`.\n */\n throbber?: { from: string, to: string }\n /**\n * Optional tone for the footer's session-cost indicator. Falls back\n * to `warn` when unset. Carved out as its own role because some\n * themes (e.g. Crush) intentionally bind `warn` to a non-yellow tone\n * and a separate \"money\" color reads more naturally on the footer.\n */\n money?: string\n}\n\n/** Select-component styling. Plain prop shape; both OpenTUI's `<select>` and any GUI equivalent can consume this. */\nexport interface ThemeSelect {\n backgroundColor: string\n focusedBackgroundColor: string\n selectedBackgroundColor: string\n selectedTextColor: string\n textColor: string\n descriptionColor: string\n selectedDescriptionColor: string\n}\n\n/** Background + foreground pair for a completion-reference chip pill. */\nexport interface ChipColor {\n /** Background paint — the pill itself. High-saturation by convention. */\n bg: string\n /** Foreground paint — the chip text. Pre-paired with {@link ChipColor.bg} for legibility. */\n fg: string\n}\n\n/**\n * Chip colors keyed by completion-provider id (`'skills'`, `'files'`, …).\n * `default` is the required fallback used by any provider id without an\n * explicit entry — keeps host-registered providers themable out of the\n * box. Use {@link resolveChipColor} to perform the kind-specific → default\n * lookup rather than indexing the map directly.\n */\n/**\n * Map of provider id → chip color pair. `default` is required so callers\n * always have a fallback; every other key is optional so direct indexing\n * (`chips['unknown']`) surfaces as `ChipColor | undefined` and nudges\n * consumers toward {@link resolveChipColor}, which encodes the fallback.\n */\nexport type ChipColorMap = { default: ChipColor } & Partial<Record<string, ChipColor>>\n\n/**\n * Look up the chip color pair for a provider id, falling back to\n * `chips.default` when the theme has no kind-specific entry. Mirrors\n * `resolveChipStyleId` so both rendering surfaces (submitted echo +\n * live textarea) share one canonical lookup rule.\n */\nexport function resolveChipColor(chips: ChipColorMap, providerId: string): ChipColor {\n return chips[providerId] ?? chips.default\n}\n\n/** Panel / surface backgrounds. */\nexport interface ThemeSurfaces {\n /**\n * Solid paint for the app's root surface. Covers the whole TUI viewport\n * so transparent / translucent terminals don't show desktop windows\n * underneath. Conventionally one tier deeper than {@link modal} so\n * modal panels still read as elevated above the body.\n */\n background: string\n /** Background of an overlaid modal panel (settings, model picker, …). */\n modal: string\n /**\n * Completion-chip color pairs keyed by provider id. Built-in themes\n * ship distinct `skills` + `files` tones so the two reference kinds\n * read differently in both the submitted echo and the live textarea.\n */\n chips: ChipColorMap\n /**\n * Background paint for the selected turn's events in select-turn mode.\n * Subtle, low-saturation lift from the terminal default so the whole\n * span of the selected turn reads as one continuous highlighted block\n * without overpowering the foreground text. Inverse-tinted on the\n * light flavor (Latte) so the same visual role works there.\n */\n selection: string\n /**\n * Edit-diff color block. Drives the native `<diff>` renderable used\n * by {@link Settings.showEditDiffs}. Pre-mixed against each theme's\n * primary surface so terminals without true alpha-blend get a\n * legible \"subtle red / subtle green\" effect rather than a flat\n * saturated band.\n *\n * - `*Bg` row-level paint (gutter + content)\n * - `*ContentBg` content-only paint (overrides `*Bg` on the text\n * column). Set when a deeper hue should punch\n * through behind the syntax-highlighted code; omit\n * to let `*Bg` carry the full row.\n * - `*Fg` color for the `+` / `-` glyph in the sign gutter.\n */\n diff: DiffSurfaces\n}\n\n/** Per-row paints used by the native `<diff>` renderable. */\nexport interface DiffSurfaces {\n addBg: string\n removeBg: string\n contextBg?: string\n addContentBg?: string\n removeContentBg?: string\n addFg: string\n removeFg: string\n}\n\n/**\n * One entry in a `SyntaxStyles` map — what OpenTUI's `SyntaxStyle.fromStyles`\n * accepts, minus the `RGBA` conversion. Renderer-agnostic JSON.\n */\nexport interface SyntaxTokenStyle {\n fg?: string\n bg?: string\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n}\n\n/**\n * Map of Tree-sitter / markdown capture group → token style.\n *\n * Two flavours of keys live in the same table:\n * - `markup.*` — the markdown structure captures (heading, bold, italic,\n * list, quote, raw inline + block, link). OpenTUI's markdown parser\n * emits these for the markdown text itself.\n * - bare token names (`keyword`, `string`, `function`, …) — emitted by\n * the embedded language Tree-sitter grammars when a code fence\n * declares a language. OpenTUI passes the same `SyntaxStyle` down to\n * the fenced-code renderable, so this map drives both surfaces.\n */\nexport type SyntaxStyles = Record<string, SyntaxTokenStyle>\n\n/** Full theme bundle. */\nexport interface Theme {\n /** Stable identifier, used as the key in `BUILTIN_THEMES` and `Settings.theme`. */\n id: string\n /** Human-readable label shown in the settings picker. */\n label: string\n colors: ThemeColors\n select: ThemeSelect\n surfaces: ThemeSurfaces\n syntax: SyntaxStyles\n}\n\n// ---------------------------------------------------------------------------\n// Default theme — Bolt palette mapped to our role-named slots\n//\n// Source: Bolt design-system primitives + dark-theme semantic tokens. We\n// pick the dark variants because the TUI almost always renders on dark\n// terminals; the few light-terminal users get the same hues a step\n// brighter / cooler than the comparable light-theme tokens, which still\n// reads as \"brand blue / orange / red / green\" and remains accessible.\n//\n// Color-role mapping. Every \"accent\" slot stays inside the Bolt brand\n// cascade (brand-300 → brand-400 → brand-500 → brand-600) so the\n// chrome reads as one cool family rather than a mosaic of warm + cool\n// dots. Warm tones (orange, red) are reserved for genuine error /\n// danger contexts only:\n//\n// brand-600 borderActive #1488fc focused / active rims\n// brand-500 brand #2ba6ff signature accent — session title, ▶\n// brand-400 warn #53c4ff shortcut keys, spinner, accent numbers\n// brand-300 model #8adaff model id, links, soft brand sibling\n// green-500 accent #22c55e success — spawn markers, ✓\n// red-500 error #ef4444 errors, ✗ glyphs\n// neutral-400 dim #a3a3ac secondary text\n// neutral-600 mute #525258 separators, `┃` bar\n// neutral-700 border #3c3c41 resting borders\n//\n// `warn` lives in the brand cascade rather than orange so the hint\n// rows (which use it for every shortcut key) don't strafe orange dots\n// across an otherwise cool surface. The spinner + approval picker\n// pick up the same brand-tinted accent — reads as \"working / attention\"\n// without competing with the red of `error`.\n//\n// Defined as a local constant first so `BUILTIN_THEMES` references it\n// by name, and so the markdown-structure entries can interpolate the\n// role-named colors without redeclaring them.\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_COLORS: ThemeColors = {\n brand: '#2ba6ff', // bolt brand-500\n accent: '#22c55e', // bolt green-500 (success)\n model: '#8adaff', // bolt brand-300 (soft sky)\n warn: '#53c4ff', // bolt brand-400 (cool accent — keys, spinner, \"attention\")\n error: '#ef4444', // bolt red-500 (danger)\n dim: '#a3a3ac', // bolt neutral-400\n mute: '#525258', // bolt neutral-600\n border: '#3c3c41', // bolt neutral-700\n borderActive: '#1488fc', // bolt brand-600\n // Throbber sweeps brand-500 → brand-300 — a brand-family blue\n // gradient that stays on-message instead of detouring through\n // `accent` green via the fallback.\n throbber: { from: '#2ba6ff', to: '#8adaff' },\n}\n\nexport const DEFAULT_THEME: Theme = {\n id: 'default',\n label: 'Default',\n colors: DEFAULT_COLORS,\n select: {\n // Transparent backgrounds keep the highlight bar from filling with a\n // different color than the surrounding box. The `▶` marker + brand-\n // colored selected text carry the focus affordance instead.\n backgroundColor: 'transparent',\n focusedBackgroundColor: 'transparent',\n selectedBackgroundColor: 'transparent',\n selectedTextColor: DEFAULT_COLORS.brand,\n textColor: DEFAULT_COLORS.dim,\n descriptionColor: DEFAULT_COLORS.mute,\n selectedDescriptionColor: DEFAULT_COLORS.dim,\n },\n surfaces: {\n // One tier below `modal` — neutral-1000-ish — so modal panels still\n // read as elevated above the body when both paint at the same time.\n background: '#0a0a0c',\n // Bolt's `--surface` dark — neutral-950.\n modal: '#111114',\n // Skills (slash-commands, user verbs) get the signature bold-brand\n // pill — saturated `brand-500` background with very light brand\n // text on top: primary action, attention-getting. Files (resource\n // references) get the *inverted* sibling — bright `brand-300`\n // background with deep `brand-950` text. Both pills are vibrant\n // and clearly in the brand family, but they sit on opposite ends\n // of the saturation/lightness axis, so they read as deliberately\n // paired counterparts rather than two pills competing for the\n // same visual role.\n chips: {\n default: { bg: DEFAULT_COLORS.brand, fg: '#eef9ff' },\n skills: { bg: DEFAULT_COLORS.brand, fg: '#eef9ff' },\n files: { bg: '#8adaff', fg: '#122f59' },\n },\n // Translucent brand on dark surface — Bolt's `--brandContainer`\n // (dark) is `#2ba6ff1a` (10% over surface). The terminal can't\n // alpha-blend, so we pre-mix ~15% brand-500 over neutral-950 and\n // bake the result as a solid hex. Reads as \"this turn is active\"\n // without overpowering the text foreground.\n selection: '#142737',\n // Diff blocks — `green-950` / `red-950` pre-mixed over neutral-950 at\n // ~12% (the row paint) with brighter `green-900` / `red-900` at ~25%\n // for the content column so the code text contrasts cleanly against\n // the gutter. Sign glyph fg rides the role colors (`accent` / `error`)\n // for instant visual parity with `✓` / `✗`.\n diff: {\n addBg: '#0e2218',\n removeBg: '#2a1414',\n addContentBg: '#143728',\n removeContentBg: '#3d1d1d',\n addFg: '#22c55e',\n removeFg: '#ef4444',\n },\n },\n syntax: {\n // ---- markdown structure ----\n // Headings cascade through brand-500 → brand-400 → brand-300 so the\n // outline reads as one related family rather than three random\n // accents. Body text rides `--textPrimary` (`#fefeff`).\n 'default': { fg: '#fefeff' },\n 'markup.heading': { fg: '#2ba6ff', bold: true },\n 'markup.heading.1': { fg: '#2ba6ff', bold: true },\n 'markup.heading.2': { fg: '#53c4ff', bold: true },\n 'markup.heading.3': { fg: '#8adaff', bold: true },\n 'markup.bold': { fg: '#ffffff', bold: true },\n 'markup.strong': { fg: '#ffffff', bold: true },\n 'markup.italic': { fg: '#fefeff', italic: true },\n 'markup.link': { fg: DEFAULT_COLORS.model, underline: true },\n 'markup.link.url': { fg: DEFAULT_COLORS.model, underline: true },\n // Bullet markers in markdown rides the warm `orange-400` rather\n // than `warn` (which is now cool brand-400). Lists are dense and\n // benefit from a hue that stands apart from headings + body text,\n // and warm tones are appropriate inside the rendered-markdown\n // surface where they don't accumulate into the chrome.\n 'markup.list': { fg: '#fb923c' },\n // Inline / block `code` rides brand-200 — readable on dark, distinct\n // from regular text + headings without leaving the brand family.\n 'markup.raw': { fg: '#bae7ff' },\n 'markup.raw.block': { fg: '#bae7ff' },\n 'markup.quote': { fg: DEFAULT_COLORS.dim, italic: true },\n\n // ---- code (Tree-sitter language tokens inside fenced blocks) ----\n // Bolt has no purple/violet step, so we lean on green for functions\n // (callable / \"alive\") and reserve orange for types + parameters.\n // String constants live in brand-200, numeric/property tokens in\n // brand-300 so they read as one cool family against the warm types.\n 'keyword': { fg: '#f87171', bold: true }, // dangerHighlight\n 'keyword.import': { fg: '#f87171', bold: true },\n 'keyword.operator': { fg: '#f87171' },\n 'string': { fg: '#bae7ff' }, // brand-200\n 'string.escape': { fg: '#bae7ff', bold: true },\n 'character': { fg: '#bae7ff' },\n 'comment': { fg: '#73737b', italic: true }, // neutral-500\n 'number': { fg: '#8adaff' }, // brand-300\n 'boolean': { fg: '#8adaff' },\n 'constant': { fg: '#8adaff' },\n 'constant.builtin': { fg: '#8adaff' },\n 'function': { fg: '#86efac' }, // green-300\n 'function.call': { fg: '#86efac' },\n 'function.method': { fg: '#86efac' },\n 'function.method.call': { fg: '#86efac' },\n 'function.builtin': { fg: '#86efac' },\n 'function.macro': { fg: '#86efac' },\n 'type': { fg: '#fb923c' }, // warningHighlight (orange-400)\n 'type.builtin': { fg: '#fb923c' },\n 'constructor': { fg: '#fb923c' },\n 'attribute': { fg: '#fb923c' },\n 'tag': { fg: '#4ade80' }, // green-400\n 'variable': { fg: '#fefeff' },\n 'variable.builtin': { fg: '#8adaff' },\n 'variable.parameter': { fg: '#fb923c' },\n 'variable.member': { fg: '#8adaff' },\n 'property': { fg: '#8adaff' },\n 'operator': { fg: '#f87171' },\n 'punctuation': { fg: DEFAULT_COLORS.mute },\n 'punctuation.bracket': { fg: '#fefeff' },\n 'punctuation.delimiter': { fg: '#d4d4dd' }, // neutral-300\n 'label': { fg: '#8adaff' },\n },\n}\n\n/**\n * Built-in theme registry, keyed by `theme.id`. The TUI looks up the active\n * theme here using `Settings.theme`; unknown ids fall back to\n * `DEFAULT_THEME`. Hosts can extend this by passing additional themes to a\n * future `runTui({ themes })` option (not yet wired).\n *\n * Insertion order is the picker cycle order — keep `default` first so a\n * fresh install (no `theme` in `state.json`) sees the familiar yellow theme\n * before the others.\n */\nexport const BUILTIN_THEMES: Readonly<Record<string, Theme>> = {\n [DEFAULT_THEME.id]: DEFAULT_THEME,\n [LIGHT_THEME.id]: LIGHT_THEME,\n [CATPPUCCIN_MOCHA.id]: CATPPUCCIN_MOCHA,\n [CATPPUCCIN_MACCHIATO.id]: CATPPUCCIN_MACCHIATO,\n [CATPPUCCIN_FRAPPE.id]: CATPPUCCIN_FRAPPE,\n [CATPPUCCIN_LATTE.id]: CATPPUCCIN_LATTE,\n [GRUVBOX_DARK.id]: GRUVBOX_DARK,\n [GRUVBOX_LIGHT.id]: GRUVBOX_LIGHT,\n [CRUSH_THEME.id]: CRUSH_THEME,\n [VAPORWAVE_THEME.id]: VAPORWAVE_THEME,\n}\n\n/** Resolve a theme id to its full `Theme`, falling back to default on unknown ids. */\nexport function resolveTheme(id: string | undefined): Theme {\n if (id && BUILTIN_THEMES[id])\n return BUILTIN_THEMES[id]\n return DEFAULT_THEME\n}\n","import type { ReactNode } from 'react'\nimport type { Settings } from './types'\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { BUILTIN_THEMES, DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// Defaults\n// ---------------------------------------------------------------------------\n\nexport const DEFAULT_SETTINGS: Settings = {\n showThinking: true,\n // Tool calls render as clean per-tool summaries by default — `↳ Read\n // src/foo.ts` instead of `↳ read_file({\"path\":\"src/foo.ts\"})`. Flip\n // to `'full'` for the JSON-debug view, `'hidden'` to drop them.\n toolCallDisplay: 'formatted',\n showToolResults: true,\n safeMode: true,\n hideSubagentOutput: true,\n theme: DEFAULT_THEME.id,\n // Off by default — the sessions list filters to the current project\n // (git root / cwd) so the user only sees the conversations relevant\n // to where they're working. Toggle on to inspect or jump across\n // projects, plus see pre-tagging legacy sessions.\n showAllProjects: false,\n // On by default — match the historical \"the TUI picks up where you\n // left off\" behavior. Users who prefer a clean slate on launch can\n // flip this off; resume-on-launch then falls through to the sessions\n // list (or a fresh session when the list is empty).\n resumeLastSession: true,\n // On by default — large tool results (>8 KiB) get written to disk and\n // replaced inline with a `<persisted-output>` stub. Shrinks the prompt\n // prefix on shell/grep-heavy sessions; the model can still re-read the\n // full payload via `read_file` on the persisted path. Flip off to keep\n // every tool result inline regardless of size (matches pre-feature\n // behavior — also useful when debugging tool output).\n persistToolResults: true,\n // On by default — auto-fire compaction once the latest turn's input\n // usage crosses `autoCompactThreshold` of the effective context window.\n // The shipped `compactConversation` is otherwise manual-only; without\n // this trigger most users never reach for `/compact` and let sessions\n // degrade silently.\n autoCompact: true,\n // 80% of the effective window — comfortable headroom before the model\n // hits the wall, aligned with Claude Code's `AUTOCOMPACT_BUFFER_TOKENS`\n // (~167k of 200k = 83.5%). The settings modal cycles 0.6 / 0.7 / 0.8 /\n // 0.9 so users can tune down for verbose workflows (frequent\n // compactions) or up for context-heavy ones (delay until near full).\n autoCompactThreshold: 0.8,\n // On by default — `edit` / `multi_edit` / `write_file` calls render as\n // a unified diff. The paired success result is suppressed (the diff\n // already shows it worked); errors still surface. Flip off to keep\n // the raw `↳ edit({\"path\":\"…\",\"old_string\":\"…\"})` JSON dump.\n showEditDiffs: true,\n // Full unified diff in the transcript by default. Switch to `'compact'`\n // for a one-line per-hunk summary (file path + `+N −M` + first\n // changed line preview) — much quieter on edit-heavy turns; the\n // approval modal still shows the full diff when safeMode prompts fire.\n editDiffDisplay: 'full',\n // 60fps target — matches most displays, doubles OpenTUI's stock 30fps\n // default for visibly snappier scroll + modal animations + cursor.\n // Settings modal cycles 30 / 60 / 120; applied live via the renderer's\n // `targetFps` / `maxFps` setters.\n targetFps: 60,\n // On by default — the agent retains the `ask_user` + `present_plan`\n // tools and the system prompt teaches it when to use them. Off strips\n // both tools at session activation and swaps in the no-interaction\n // variants of the relevant prompt sections.\n allowInteraction: true,\n // On by default — the assistant's streaming text drips in at a smooth\n // cadence (typewriter effect) instead of landing in batches. The\n // display rate is capped + bursts when behind; turn boundaries flush\n // remaining content immediately so completion is never delayed.\n smoothStreaming: true,\n // On by default — the inline indicator above the prompt is a single\n // muted line (\"▸ <in-progress todo>\"), only present while the active\n // run has an `in_progress` item. Users who don't want even that\n // subtle line flip this off; the explicit `ctrl+t` modal still works.\n showTodoIndicator: true,\n // Off by default — the streaming throbber (cycling hex/symbol glyphs)\n // is a stylistic flourish, not a load-bearing affordance. Streaming\n // already shows itself through the prompt's status row and the\n // incremental markdown updates, so we keep the transcript calm out\n // of the box and let users opt back in.\n showThrobber: false,\n // On by default — one warm-cache HTTP roundtrip per 24 h, never\n // blocks boot, swallows errors. CI / NO_UPDATE_NOTIFIER / ZIDANE_NO_UPDATE\n // force-disable. The explicit `zidane upgrade` subcommand bypasses\n // this toggle.\n checkForUpdates: true,\n // `'full'` by default — match the historical chat-screen chrome with\n // every shortcut advertised in the bottom bar, prompt overlay, and\n // title meta. `'minimal'` is the explicit opt-in for the lighter\n // surface (agent / model / keybindings + `@` / `/` triggers).\n uiMode: 'full',\n // `'both'` by default — merge `~/.agents`, `~/.zidane`, `~/.claude`\n // user files with project-local `AGENTS.md` / `CLAUDE.md`. Flip to\n // `'user'` for cross-repo defaults only, `'project'` to scope down\n // when running zidane against an unfamiliar codebase.\n userInstructionsScope: 'both',\n ctrlCBehavior: 'quit',\n // On by default — preserves the historical paste-as-attachment\n // pipeline (drag-and-drop a file → image/binary attachment,\n // multiline paste → `paste.txt` document attachment). Flip off\n // and every paste lands inline in the textarea verbatim.\n attachmentsEnabled: true,\n // `enabledSkills` / `enabledMcps` deliberately default to `undefined`\n // (NOT `[]`): every discovered skill + server is enabled until the user\n // explicitly toggles. The Settings modal seeds the allowlist with the\n // current discovery on first toggle so future additions don't auto-opt\n // out.\n //\n // `disabledMcpTools` is a per-server DENY-LIST (opposite polarity from\n // the `enabledX` allowlists above) — missing key / empty array means\n // every advertised tool is enabled. That polarity flip is deliberate:\n // a new tool shipped by an MCP server we already trust should auto-\n // enable, the same way a new server we explicitly added shouldn't.\n // Default is `undefined` (no per-tool customization).\n}\n\n// ---------------------------------------------------------------------------\n// Bounds\n// ---------------------------------------------------------------------------\n\n/**\n * Hard-clamp a `targetFps` value to a safe range before handing it to\n * the renderer. Defends against an out-of-band edit to `state.json`\n * (`0`, negative, `NaN`, absurd values) silently pegging the loop or\n * tripping the frame-time math. The settings modal only exposes\n * `30 / 60 / 120` so the typical path never trips the clamp; this\n * guard exists for the \"user hand-edited state.json\" case.\n */\nconst MIN_TARGET_FPS = 1\nconst MAX_TARGET_FPS = 240\n\nexport function clampFps(value: number): number {\n if (!Number.isFinite(value) || value <= 0)\n return DEFAULT_SETTINGS.targetFps\n return Math.min(MAX_TARGET_FPS, Math.max(MIN_TARGET_FPS, Math.round(value)))\n}\n\n// ---------------------------------------------------------------------------\n// SettingsProvider — single source of truth for the transcript filters.\n//\n// `onChange` is called with the latest settings whenever a row toggles, so\n// the host can persist them. Persistence pathing lives outside this module\n// to keep the settings layer portable across renderers (TUI, GUI, …).\n// ---------------------------------------------------------------------------\n\ninterface SettingsContextValue {\n settings: Settings\n /** Flip a boolean setting in place. Restricted at the type level to boolean-valued keys. */\n toggle: (key: BooleanSettingKey) => void\n /** Write any setting key to a new value. Used by string-valued choice rows. */\n setSetting: <K extends keyof Settings>(key: K, value: Settings[K]) => void\n}\n\nconst SettingsContext = createContext<SettingsContextValue | null>(null)\n\nexport function SettingsProvider({\n initial,\n onChange,\n children,\n}: {\n initial: Settings\n onChange?: (settings: Settings) => void\n children: ReactNode\n}) {\n const [settings, setSettings] = useState<Settings>(initial)\n\n // `onChange` runs as a side-effect *after* commit, not inside the state\n // updater. Two reasons:\n // 1. React 18 strict-mode dev runs invoke state updaters twice for\n // every transition; a `disk.save(...)` inside the updater would\n // double-fire there.\n // 2. Pure-update is the React contract — updaters must be free of\n // observable side-effects. Persisting from inside breaks the\n // contract regardless of strict mode.\n //\n // A ref keeps the latest `onChange` reachable from the effect without\n // forcing the effect to re-run on every render the parent re-renders.\n // The `initialMount` ref skips the very first commit (we don't want to\n // persist the initial state we just loaded from disk).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const initialMount = useRef(true)\n useEffect(() => {\n if (initialMount.current) {\n initialMount.current = false\n return\n }\n onChangeRef.current?.(settings)\n }, [settings])\n\n const toggle = useCallback((key: BooleanSettingKey) => {\n setSettings(prev => ({ ...prev, [key]: !prev[key] }))\n }, [])\n\n const setSetting = useCallback(<K extends keyof Settings>(key: K, value: Settings[K]) => {\n setSettings(prev => (prev[key] === value ? prev : { ...prev, [key]: value }))\n }, [])\n\n const value = useMemo<SettingsContextValue>(\n () => ({ settings, toggle, setSetting }),\n [settings, toggle, setSetting],\n )\n\n return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>\n}\n\nexport function useSettings(): SettingsContextValue {\n const ctx = useContext(SettingsContext)\n if (!ctx)\n throw new Error('useSettings must be used inside <SettingsProvider>')\n return ctx\n}\n\n/**\n * Keys of `Settings` whose value type is exactly `boolean`. Used to type\n * the toggle table so `SETTINGS_TOGGLES.key` only ever points to a\n * boolean-valued setting — string- and array-valued settings (theme,\n * enabledSkills, …) live in `SETTINGS_CHOICES` or actions.\n *\n * `-?` strips optionality from the mapped output so `Settings[K]` doesn't\n * silently broaden into `T | undefined` and short-circuit the `extends\n * boolean` check via `undefined`.\n */\nexport type BooleanSettingKey = {\n [K in keyof Settings]-?: Settings[K] extends boolean ? K : never\n}[keyof Settings]\n\n/**\n * Setting categories — drives the tab grouping in the TUI's\n * `SettingsModal` and, more importantly, gives any embedder (GUI,\n * web settings panel, …) a stable taxonomy to bucket settings under\n * without re-hardcoding the split.\n *\n * - `'agent'` — agent behaviour, capabilities, persistence,\n * data handling, auth. \"What zidane DOES.\"\n * - `'ui'` — display, rendering, theming, animations,\n * chrome density, keybindings. \"How zidane LOOKS\n * and feels.\"\n *\n * Borderline calls (`showAllProjects`, `hideSubagentOutput`,\n * `resumeLastSession`) are documented inline at each row.\n */\nexport type SettingsCategory = 'agent' | 'ui'\n\nexport interface SettingsCategoryDescriptor {\n id: SettingsCategory\n /** Display label — used by the TUI tab strip + any GUI tab/section header. */\n label: string\n /** One-line gloss the GUI can show as a section subtitle. The TUI omits this. */\n description: string\n}\n\n/**\n * Ordered list of categories. Display order in any consumer (TUI tabs,\n * GUI sidebar, settings JSON dump) should match this array — keeps\n * mental model consistent across surfaces.\n */\nexport const SETTINGS_CATEGORIES: readonly SettingsCategoryDescriptor[] = [\n { id: 'agent', label: 'Agent', description: 'agent behaviour, capabilities, persistence, auth' },\n { id: 'ui', label: 'UI', description: 'display, rendering, theming, animations, keybindings' },\n]\n\n/**\n * Static description of every togglable setting, in display order. The TUI's\n * `SettingsModal` and any future GUI settings panel build their row list\n * from this so labels/descriptions stay in one place.\n */\nexport interface SettingsToggle {\n key: BooleanSettingKey\n label: string\n description: string\n category: SettingsCategory\n}\n\nexport const SETTINGS_TOGGLES: readonly SettingsToggle[] = [\n // ----- Agent (behaviour / data handling) -------------------------------\n { category: 'agent', key: 'safeMode', label: 'Safe mode', description: 'prompt before each tool call (unless safelisted)' },\n { category: 'agent', key: 'allowInteraction', label: 'Interactive prompts', description: 'let the agent pause for `ask_user` questions / `present_plan` approvals' },\n { category: 'agent', key: 'resumeLastSession', label: 'Start from last session', description: 'auto-resume on launch (off = sessions list / fresh chat)' },\n { category: 'agent', key: 'persistToolResults', label: 'Persist large tool results', description: 'write >8 KiB tool outputs to disk and inline a preview' },\n { category: 'agent', key: 'attachmentsEnabled', label: 'Paste attachments', description: 'fold dragged files + multiline pastes into prompt attachments (off = everything pastes inline)' },\n { category: 'agent', key: 'autoCompact', label: 'Auto-compact', description: 'summarize the conversation when context fills past the threshold below' },\n { category: 'agent', key: 'checkForUpdates', label: 'Check for updates', description: 'quietly check npm once a day; footer chip surfaces newer releases. `zidane upgrade` always works regardless.' },\n // ----- UI (display / rendering) ----------------------------------------\n // `showAllProjects` / `hideSubagentOutput` are arguably feature flags\n // but their visible effect is purely \"what shows up in the transcript /\n // sessions list\" — bucketing as UI keeps the Agent tab focused on\n // behaviour the agent observes, not what the user sees.\n { category: 'ui', key: 'showAllProjects', label: 'All projects', description: 'list sessions from every project (off = current only)' },\n { category: 'ui', key: 'hideSubagentOutput', label: 'Hide subagent output', description: 'collapse subagent runs to start/done markers' },\n { category: 'ui', key: 'showThinking', label: 'Thinking blocks', description: 'agent reasoning shown inline' },\n { category: 'ui', key: 'showToolResults', label: 'Tool outputs', description: 'the ┃ result blocks under tool calls' },\n { category: 'ui', key: 'showEditDiffs', label: 'Edit diffs', description: 'render edit / multi_edit / write_file as a unified diff' },\n { category: 'ui', key: 'smoothStreaming', label: 'Smooth streaming', description: 'drip-feed streamed text character-by-character at a steady cadence (typewriter effect)' },\n { category: 'ui', key: 'showTodoIndicator', label: 'Todo indicator', description: 'show the subtle \"in progress\" todo line above the prompt (modal stays accessible regardless)' },\n { category: 'ui', key: 'showThrobber', label: 'Streaming throbber', description: 'animated gradient glyphs at the transcript tail while the assistant is working' },\n]\n\n/**\n * String-valued settings that pick between a known set of options. The\n * `SettingsModal` renders these as one row showing the active label, where\n * `enter` cycles to the next option (wrapping back to the first). Same\n * shape any GUI settings panel can consume — render the options array as a\n * dropdown or segmented control.\n *\n * Keep `options` in stable display order; cycling depends on it.\n */\nexport interface SettingsChoice<K extends keyof Settings = keyof Settings> {\n key: K\n label: string\n description: string\n options: readonly { value: Settings[K], label: string }[]\n category: SettingsCategory\n}\n\nexport const SETTINGS_CHOICES: readonly SettingsChoice[] = [\n // ----- Agent (behaviour / data handling) -------------------------------\n {\n category: 'agent',\n key: 'autoCompactThreshold',\n label: 'Auto-compact threshold',\n description: 'fraction of the effective context window before compaction fires',\n options: [\n { value: 0.6, label: '60%' },\n { value: 0.7, label: '70%' },\n { value: 0.8, label: '80%' },\n { value: 0.9, label: '90%' },\n ],\n },\n {\n category: 'agent',\n key: 'userInstructionsScope',\n label: 'User instructions',\n description: 'which AGENTS.md / CLAUDE.md files load into the system prompt',\n options: [\n { value: 'both', label: 'Both (merged)' },\n { value: 'user', label: 'User only' },\n { value: 'project', label: 'Project only' },\n { value: 'none', label: 'None' },\n ],\n },\n // ----- UI (display / rendering) ----------------------------------------\n {\n category: 'ui',\n key: 'toolCallDisplay',\n label: 'Tool calls display',\n description: 'how `↳ <tool>` lines render — hidden, clean per-tool summary, or full JSON',\n options: [\n { value: 'formatted', label: 'Formatted' },\n { value: 'full', label: 'Full' },\n { value: 'hidden', label: 'Hidden' },\n ],\n },\n {\n category: 'ui',\n key: 'editDiffDisplay',\n label: 'Edit diff density',\n description: 'full unified diff with line numbers · or compact per-hunk summary list',\n options: [\n { value: 'full', label: 'Full' },\n { value: 'compact', label: 'Compact' },\n ],\n },\n {\n category: 'ui',\n key: 'theme',\n label: 'Theme',\n description: 'colors + markdown / syntax styles',\n options: Object.values(BUILTIN_THEMES).map(t => ({ value: t.id, label: t.label })),\n },\n {\n category: 'ui',\n key: 'uiMode',\n label: 'UI mode',\n description: 'chat-screen chrome density — full shows every shortcut · minimal hides them (`ctrl+y` to peek)',\n options: [\n { value: 'full', label: 'Full' },\n { value: 'minimal', label: 'Minimal' },\n ],\n },\n {\n category: 'ui',\n key: 'targetFps',\n label: 'Renderer FPS',\n description: '30 saves CPU · 60 recommended · 120 for ProMotion + modern terminals',\n options: [\n { value: 30, label: '30 fps' },\n { value: 60, label: '60 fps' },\n { value: 120, label: '120 fps' },\n ],\n },\n {\n category: 'ui',\n key: 'ctrlCBehavior',\n label: 'Ctrl+C behavior',\n description: 'quit immediately · confirm first · or stop current action then quit on double-press',\n options: [\n { value: 'quit', label: 'Quit' },\n { value: 'quit-confirm', label: 'Confirm' },\n { value: 'stop-then-quit', label: 'Stop, then quit' },\n ],\n },\n]\n","/**\n * Renderer-agnostic state machine for an \"enabled allowlist\" — the shape\n * used by `settings.enabledSkills` / `settings.enabledMcps` (and any future\n * `enabledX` toggle backed by a `Settings` field).\n *\n * Semantics, kept identical across all three call sites:\n * - `undefined` → every catalog entry is enabled (default — user has\n * never opened the picker; new entries flow in).\n * - `[]` → the whole subsystem is off.\n * - `[names]` → explicit allowlist; the persisted shape stays a\n * concrete array once the user has toggled anything.\n *\n * The hook exposes a live `enabledSet` (Set<string>) + a `toggle(name)`\n * callback that persists the result through `useSettings().setSetting`.\n * The first toggle seeds the persisted allowlist from the current catalog\n * so newly-added entries don't silently drop on the next launch.\n *\n * Renderer-neutral: works for the TUI's `<ToggleListModal>`, a GUI\n * checkbox list, a CLI flag tool. Hook tests live alongside the chat\n * suite; the rendering layer is tested where it lives.\n */\n\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\n/** Settings keys whose value type is `readonly string[] | undefined`. */\nexport type EnabledAllowlistKey = 'enabledSkills' | 'enabledMcps'\n\nexport interface EnabledToggleSet {\n /** Live set of enabled names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Toggle one name on/off; persists immediately via `setSetting`. */\n toggle: (name: string) => void\n}\n\n/**\n * Bind an \"enabled allowlist\" setting to a discovered catalog.\n *\n * `keyOf` extracts the persisted identity from a catalog entry (skill name,\n * MCP server name, …). Pass a stable, collision-free key — the persisted\n * allowlist is keyed against it forever.\n */\nexport function useEnabledToggleSet<T>(opts: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n}): EnabledToggleSet {\n const { settings, setSetting } = useSettings()\n const { catalog, keyOf, settingKey } = opts\n const persisted = settings[settingKey]\n\n const enabledSet = useMemo<ReadonlySet<string>>(() => {\n if (persisted === undefined)\n return new Set(catalog.map(keyOf))\n return new Set(persisted)\n }, [persisted, catalog, keyOf])\n\n // Live catalog membership — used to garbage-collect stale persisted\n // names on every write. Without this, a skill / MCP that was once\n // enabled and then removed from disk lingers in the JSON forever\n // (the next toggle round-trips it back into the array). Functionally\n // harmless today (the agent ignores unknown names) but it lets the\n // persisted state drift away from reality.\n const catalogKeys = useMemo(\n () => new Set(catalog.map(keyOf)),\n [catalog, keyOf],\n )\n\n const toggle = useCallback((name: string) => {\n // Seed from the current `enabledSet` (covers both the seeded-from-catalog\n // and explicit-allowlist branches). Persist as a sorted array filtered\n // against the live catalog so stale names — entries deleted from disk\n // between launches — don't linger forever in the persisted allowlist.\n // `undefined` is reserved for \"fresh install, accept new discoveries\";\n // once the user toggles once the persistence shape stays a concrete list.\n const next = new Set(enabledSet)\n if (next.has(name))\n next.delete(name)\n else\n next.add(name)\n const persisted = [...next]\n .filter(n => catalogKeys.has(n))\n .sort()\n setSetting(settingKey, persisted)\n }, [enabledSet, catalogKeys, settingKey, setSetting])\n\n return { enabledSet, toggle }\n}\n","/**\n * Project file discovery for the `@`-prefixed files completion provider.\n *\n * Primary strategy — `git ls-files --cached --others --exclude-standard`.\n * Git already understands `.gitignore`, `.git/info/exclude`, and global\n * excludes, so we get correctness for free + sub-second performance on\n * even huge monorepos. Falls back to a depth-limited filesystem walk when\n * git isn't available or the directory isn't a repo.\n *\n * Pure: callers cache the returned list (e.g. in App state) and pass it\n * into the provider's `getCatalog`. The list is a snapshot — refresh on\n * cwd change or via a user-driven action.\n */\n\nimport type { Dirent, Stats } from 'node:fs'\nimport { spawn } from 'node:child_process'\nimport { readdir, stat } from 'node:fs/promises'\nimport { resolve, sep } from 'node:path'\nimport { errorMessage } from '../errors'\n\n/** One row in the project file catalog. `path` is forward-slashed + relative to `cwd`. */\nexport interface FileEntry {\n /** Forward-slashed relative path from the discovery cwd. Stable across OSes. */\n path: string\n /** Basename — used for prefix ranking in the completion provider. */\n name: string\n /** Source — `git` when listed via `git ls-files`, `fs` from the walk fallback. */\n source: 'git' | 'fs'\n}\n\n/**\n * Hard cap on the discovered file count. Bigger than any reasonable repo's\n * source tree, small enough that a runaway scan can't pin the renderer's\n * filter. Provider's substring match runs over the full list per\n * keystroke; 10k entries × cheap `.includes()` is well under one frame.\n */\nconst DEFAULT_MAX_FILES = 10_000\n\n/**\n * Names skipped during the fs-walk fallback. Mirrors what `git ls-files`\n * would exclude via the default `.gitignore` shipped in most repos —\n * `node_modules`, `dist`, build caches — plus the `.git` dir itself.\n * Best-effort; the git path is the authoritative one.\n */\nconst FS_WALK_SKIP = new Set<string>([\n '.git',\n '.hg',\n '.svn',\n 'node_modules',\n 'dist',\n 'build',\n 'out',\n 'coverage',\n '.next',\n '.nuxt',\n '.cache',\n '.turbo',\n '.parcel-cache',\n '.vercel',\n '.svelte-kit',\n '.expo',\n '.gradle',\n 'target',\n '__pycache__',\n '.pytest_cache',\n '.venv',\n 'venv',\n '.idea',\n '.vscode-test',\n])\n\n/** Options for `listProjectFiles`. */\nexport interface ListProjectFilesOptions {\n /** Discovery root. Default: `process.cwd()`. */\n cwd?: string\n /** Cap on returned entries. Default: 10,000. Bigger lists fall back to a truncated set. */\n maxFiles?: number\n /** Aborts the scan early. Useful when re-running on rapid project switches. */\n signal?: AbortSignal\n}\n\n/**\n * Discover every non-ignored file under `cwd`. Tries `git ls-files` first;\n * on failure (no git, not a repo, abort) walks the fs with a hand-rolled\n * skip list.\n *\n * Errors are not thrown — the function always returns an array (possibly\n * empty). Callers wanting failure diagnostics can opt into them via\n * `ZIDANE_DEBUG`.\n */\nexport async function listProjectFiles(opts: ListProjectFilesOptions = {}): Promise<FileEntry[]> {\n const cwd = resolve(opts.cwd ?? process.cwd())\n const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES\n const signal = opts.signal\n\n try {\n const paths = await listViaGit(cwd, signal)\n return toEntries(paths, 'git', maxFiles)\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] git ls-files failed (${errorMessage(err)}) — falling back to fs walk\\n`)\n try {\n const paths = await listViaFs(cwd, maxFiles, signal)\n return toEntries(paths, 'fs', maxFiles)\n }\n catch (fsErr) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] fs walk failed: ${errorMessage(fsErr)}\\n`)\n return []\n }\n }\n}\n\n/**\n * Run `git ls-files --cached --others --exclude-standard` and return paths.\n *\n * - `--cached` — tracked files.\n * - `--others` — untracked but not ignored.\n * - `--exclude-standard` — apply `.gitignore`, `.git/info/exclude`, the\n * user's global excludes file, AND skip files marked `assume-unchanged`.\n *\n * Throws on non-zero exit (not a repo / no git) so the fallback path can\n * pick up. Aborts cleanly on `signal.aborted`.\n */\nasync function listViaGit(cwd: string, signal: AbortSignal | undefined): Promise<string[]> {\n return new Promise<string[]>((resolveP, rejectP) => {\n const child = spawn('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n // Buffered as chunk arrays — joining once at the end keeps GC quiet on\n // huge repos. A `let stdout += chunk` accumulator reallocates the whole\n // buffer per chunk; with a ~10MB `git ls-files` output that's quadratic\n // in chunk count.\n const stdoutChunks: string[] = []\n const stderrChunks: string[] = []\n const onAbort = () => child.kill('SIGTERM')\n if (signal) {\n if (signal.aborted) {\n child.kill('SIGTERM')\n rejectP(new Error('aborted'))\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n child.stdout.setEncoding('utf8')\n child.stderr.setEncoding('utf8')\n child.stdout.on('data', chunk => stdoutChunks.push(chunk))\n child.stderr.on('data', chunk => stderrChunks.push(chunk))\n child.on('error', (err) => {\n signal?.removeEventListener('abort', onAbort)\n rejectP(err)\n })\n child.on('close', (code) => {\n signal?.removeEventListener('abort', onAbort)\n if (signal?.aborted) {\n rejectP(new Error('aborted'))\n return\n }\n if (code !== 0) {\n rejectP(new Error(`git ls-files exited ${code}: ${stderrChunks.join('').trim()}`))\n return\n }\n // `-z` separates with NUL so paths containing newlines stay intact.\n const paths = stdoutChunks.join('').split('\\0').filter(p => p.length > 0)\n resolveP(paths)\n })\n })\n}\n\n/**\n * Depth-first fs walk fallback. Used when git is unavailable. Honors a\n * hard-coded skip list; doesn't read `.gitignore`. Acceptable for hosts\n * outside a git checkout (e.g. a freshly-`unzip`ped project).\n */\nasync function listViaFs(\n cwd: string,\n maxFiles: number,\n signal: AbortSignal | undefined,\n): Promise<string[]> {\n const out: string[] = []\n const stack: string[] = ['.']\n while (stack.length > 0) {\n if (signal?.aborted)\n throw new Error('aborted')\n const rel = stack.pop()!\n const abs = rel === '.' ? cwd : resolve(cwd, rel)\n let entries: Dirent[]\n try {\n entries = await readdir(abs, { withFileTypes: true })\n }\n catch {\n // Unreadable dir — skip silently. EACCES / ENOENT during a walk\n // shouldn't fail the whole discovery.\n continue\n }\n for (const e of entries) {\n if (FS_WALK_SKIP.has(e.name))\n continue\n const childRel = rel === '.' ? e.name : `${rel}${sep}${e.name}`\n if (e.isDirectory()) {\n stack.push(childRel)\n }\n else if (e.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n else if (e.isSymbolicLink()) {\n // Resolve once; ignore broken links and follow only if it points\n // to a regular file. We don't recurse into symlinked dirs to\n // avoid cycles.\n let st: Stats\n try {\n st = await stat(resolve(abs, e.name))\n }\n catch {\n continue\n }\n if (st.isFile()) {\n out.push(toForwardSlash(childRel))\n if (out.length >= maxFiles)\n return out\n }\n }\n }\n }\n return out\n}\n\nfunction toForwardSlash(p: string): string {\n return sep === '/' ? p : p.replaceAll(sep, '/')\n}\n\nfunction toEntries(paths: readonly string[], source: 'git' | 'fs', maxFiles: number): FileEntry[] {\n const seen = new Set<string>()\n const out: FileEntry[] = []\n for (const raw of paths) {\n const path = toForwardSlash(raw)\n if (path.length === 0 || seen.has(path))\n continue\n seen.add(path)\n const lastSlash = path.lastIndexOf('/')\n const name = lastSlash >= 0 ? path.slice(lastSlash + 1) : path\n out.push({ path, name, source })\n if (out.length >= maxFiles)\n break\n }\n return out\n}\n","// ---------------------------------------------------------------------------\n// Footer hint composition.\n//\n// Pure presentation logic — given the current screen, run state, and a\n// resolved keybinding map, produce the ordered list of {@link Hint}s\n// the bottom-bar Footer should render. Lives in `zidane/chat` (not\n// `zidane/tui`) because it's renderer-agnostic: the only types involved\n// are `Hint`, `Screen`, `Settings['uiMode']`, and `KeyBindings`. A GUI\n// shell can consume the same list and paint it however it likes.\n//\n// Priority order is intentional and load-bearing:\n//\n// pending approval > live pending interaction > resumed pending\n// interaction > busy (streaming) > auth screen > sessions screen >\n// chat screen (idle).\n//\n// The first four return state-specific hint sets unconditionally —\n// `uiMode` is ignored there because those affordances (approve / abort\n// / navigate) are not the noise the minimal mode is targeting. Minimal\n// mode only collapses the chat-idle row.\n// ---------------------------------------------------------------------------\n\nimport type { Hint } from './hints'\nimport type { KeyBindings } from './keybindings'\nimport type { Screen, SessionMeta, Settings } from './types'\n\n/**\n * Options bag for {@link buildHints}. Flat shape so call sites read as a\n * checklist of \"what state do I have to surface\" — easy to thread\n * through React `useMemo` deps and to construct fresh in tests.\n */\nexport interface BuildHintsOptions {\n screen: Screen\n busy: boolean\n pending: boolean\n /**\n * True when a LIVE pending interaction is on screen (agent is mid-run,\n * awaiting the tool's Promise). Same nav hints as the safe-mode\n * approval picker — esc aborts the run.\n */\n pendingInteractionLive: boolean\n /**\n * True when the chat screen is showing a resumed (not live) pending\n * interaction. The agent isn't running; esc leaves the form without\n * persisting a response, so the hint reads \"leave for later\" — the\n * session re-enqueues the same interaction on next activation.\n */\n pendingInteractionResumed: boolean\n currentSession: SessionMeta | null\n hasMultipleAgents: boolean\n /**\n * Chat-screen chrome density — `'minimal'` collapses the idle chat\n * hint row to just `agent / model / keybindings`. Busy / pending /\n * select-turn / queue states keep their full hint sets because those\n * affordances are load-bearing (abort, navigate, approve).\n */\n uiMode: Settings['uiMode']\n modelLabel: string | null\n modelColor: string\n /** Current reasoning effort label, or `null` when the model has no reasoning knob. */\n effortLabel: string | null\n effortColor: string\n /** Foreground for the `/n` chord that introduces the effort label. */\n effortKeyColor: string\n agentLabel: string\n agentColor: string\n /**\n * Resolved keybindings — every hint key string flows through this so\n * a user-level rebind (e.g. `\"openSettings\": \"ctrl+p\"`) shows up live\n * in the bottom bar without us hard-coding `'ctrl+o'`.\n */\n keybindings: KeyBindings\n /**\n * Number of currently-dispatching tool calls. Drives the\n * `<cancelToolCall> cancel tool` chip in the `busy` hint row — without\n * it, the binding stays inert and there's no point advertising the\n * shortcut. Falls back to \"no chip\" when no tools are in flight\n * (model is mid-stream but hasn't dispatched yet).\n */\n inFlightToolCount: number\n /**\n * Number of currently-active skills. Drives the footer's\n * `✦ N skill(s)` chip on the chat screen — purely informational,\n * a passive surface for \"tool restrictions are in effect\". Hidden\n * when `0` so the row stays uncluttered for the common case.\n */\n activeSkillCount: number\n /** Foreground for the skills chip (typically `COLOR.brand`). */\n skillsChipColor: string\n /**\n * Pre-built `↑ vX.Y.Z` chip from `buildUpdateHint(useUpdateCheck())`,\n * or `null` when no update is available. Always last in the chat-screen\n * row so it's the first to drop when the terminal narrows.\n */\n updateHint: Hint | null\n}\n\n/**\n * Build the footer's shortcut hints for the current screen. On the chat\n * screen the model id rides next to its `ctrl+m` shortcut and the agent\n * label rides next to `shift+tab`, each in its accent color — the bar\n * doubles as the status display without needing separate badges. When\n * the active model exposes reasoning, the `ctrl+m` hint grows a\n * secondary `/n` chord with the current effort label, surfacing the\n * effort picker as a discoverable, in-place affordance.\n */\nexport function buildHints(options: BuildHintsOptions): Hint[] {\n const {\n screen,\n busy,\n pending,\n pendingInteractionLive,\n pendingInteractionResumed,\n currentSession,\n hasMultipleAgents,\n uiMode,\n modelLabel,\n modelColor,\n effortLabel,\n effortColor,\n effortKeyColor,\n agentLabel,\n agentColor,\n keybindings,\n inFlightToolCount,\n activeSkillCount,\n skillsChipColor,\n updateHint,\n } = options\n\n // Priority: safe-mode approval > live interaction > resumed\n // interaction > plain streaming. All three pending shapes share the\n // ↑↓ / ↵ nav prefix; only the esc semantics differ.\n if (pending)\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'abort run' }]\n if (pendingInteractionLive)\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'abort run' }]\n if (pendingInteractionResumed)\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'leave for later' }]\n if (busy) {\n // When tools are dispatching OR background tasks are running,\n // advertise the per-call cancel shortcut alongside the run-wide\n // `esc abort`. Order: cancel-tool first (active affordance for\n // the visible tool/task rows) → esc (broader scope).\n const baseBusyHints: Hint[] = []\n if (inFlightToolCount > 0) {\n baseBusyHints.push({\n key: keybindings.cancelToolCall,\n label: inFlightToolCount === 1 ? 'cancel' : `cancel (${inFlightToolCount})`,\n })\n }\n baseBusyHints.push({ key: 'esc', label: 'abort' })\n return baseBusyHints\n }\n if (screen === 'auth')\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'exit' }]\n if (screen === 'sessions') {\n return [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: keybindings.openSessionDetails, label: 'session' },\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: currentSession ? 'back' : 'exit' },\n ]\n }\n // Chat screen. Prompt-input shortcuts (`↵ send`, `shift+↵ newline`,\n // `↑↓ history`) live in the prompt box's overlay title — the bottom\n // bar only carries agent / model / global affordances. The\n // `cycleAgent` binding shows the active label inline; the\n // `openModelPicker` binding shows the active model id inline; etc.\n // `cycleAgent` is suppressed when there's only one profile and\n // `openSessionDetails` only renders once a session exists.\n //\n // Minimal UI mode: strip the chat-idle bottom bar to just `agent`\n // (when there's >1 profile), `model/effort`, and `keybindings`. The\n // full catalog stays one keystroke away via the keybindings panel.\n // Busy / pending / select-turn / queue branches above already exited\n // before this point — minimal only affects the resting chat row.\n //\n // Compose the model hint. When the active model supports reasoning,\n // the primary `<openModelPicker> model` pair grows a secondary\n // `/n effort` chord that doubles as the discoverability hint for the\n // effort picker (`openEffortPicker`) and the status display for the\n // current effort. The chord shares the model accent so the pair\n // reads as one keyboard binding across every theme.\n const modelHint: Hint | null = modelLabel\n ? {\n key: keybindings.openModelPicker,\n label: modelLabel,\n labelColor: modelColor,\n ...(effortLabel\n ? { extra: { key: shortChord(keybindings.openEffortPicker), keyColor: effortKeyColor, label: effortLabel, labelColor: effortColor } }\n : {}),\n }\n : null\n // Skills chip — purely informational, no shortcut wired. We re-use\n // the Hint shape's `key` slot to render the glyph (`✦`) so the row\n // layout (key + label, padded breakpoints) stays consistent with\n // every other entry. The chip is omitted when no skills are active\n // so the row stays uncluttered for the common case.\n const skillsChip: Hint | null = activeSkillCount > 0\n ? {\n key: '✦',\n keyColor: skillsChipColor,\n label: activeSkillCount === 1 ? '1 skill' : `${activeSkillCount} skills`,\n labelColor: skillsChipColor,\n }\n : null\n // Background-task chip — fires when the user is idle (not `busy`)\n // but a task is still running. Gives them a discoverable way to\n // reach `ctrl+k` for kill. The `busy` branch above already covers\n // the dispatching case; this one is for \"agent finished its turn,\n // task still chugging\".\n const cancelTaskChip: Hint | null = inFlightToolCount > 0\n ? {\n key: keybindings.cancelToolCall,\n label: inFlightToolCount === 1 ? 'cancel task' : `cancel task (${inFlightToolCount})`,\n }\n : null\n // Minimal mode short-circuits the full composition. Same agent / model\n // hints as the full row (so a multi-agent setup still cycles, and the\n // model + effort chord stays visible), plus the `keybindings` panel as\n // the discoverability anchor for everything we just dropped.\n if (uiMode === 'minimal') {\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n { key: keybindings.openKeybindings, label: 'keybindings' },\n ]\n }\n return [\n ...(hasMultipleAgents ? [{ key: keybindings.cycleAgent, label: agentLabel, labelColor: agentColor }] : []),\n ...(modelHint ? [modelHint] : []),\n ...(skillsChip ? [skillsChip] : []),\n ...(cancelTaskChip ? [cancelTaskChip] : []),\n ...(currentSession ? [{ key: keybindings.openSessionDetails, label: 'session' }] : []),\n { key: keybindings.openSettings, label: 'settings' },\n { key: 'esc', label: 'sessions' },\n // Update chip lives at the right-most position so `clipHintsToWidth`\n // drops it first on narrow terminals — discoverability is nice but\n // the action shortcuts must stay.\n ...(updateHint ? [updateHint] : []),\n ]\n}\n\n/**\n * Shorten a binding spec for display as a \"chord continuation\" — the\n * `/n` after `ctrl+m model` in the chat footer. We only strip the\n * `ctrl+` prefix so user-customized chords (`alt+n`, `meta+shift+n`)\n * still render in full. Falls back to the verbatim spec when no\n * `ctrl+` prefix is found, which keeps the visual contract honest:\n * the rendered key always matches the bound trigger.\n */\nexport function shortChord(spec: string): string {\n return spec.startsWith('ctrl+') ? `/${spec.slice('ctrl+'.length)}` : spec\n}\n","/**\n * One-shot title generation for an existing session — feeds the last N\n * turns to the provider with a \"write me a title\" system prompt and\n * returns the cleaned-up result.\n *\n * Provider-agnostic: works with anything implementing the renderer-\n * shared `Provider` interface. No tools, no session mutation, no\n * caching — this is a cheap, isolated completion that should be safe to\n * fire even mid-session without touching the live agent loop.\n */\n\nimport type { Provider } from '../providers'\nimport type { SessionTurn } from '../types'\n\n/** Hard cap on the result length. Anything longer is truncated client-side. */\nconst TITLE_MAX_CHARS = 60\n\n/** Default turn count fed to the model. 10 covers most exchanges without bloat. */\nconst DEFAULT_MAX_TURNS = 10\n\n/** Tokens budgeted for the model's reply. 64 fits any sane title with headroom. */\nconst TITLE_RESPONSE_MAX_TOKENS = 64\n\n/** Per-turn body cap when assembling the prompt — keeps the request payload tight. */\nconst PROMPT_TURN_CHAR_MAX = 2000\n\nexport interface GenerateSessionTitleOptions {\n provider: Provider\n /** Model id used for the call. */\n model: string\n /** Conversation history to summarize. Empty → throws synchronously. */\n turns: readonly SessionTurn[]\n /** Cap on how many trailing turns to feed the model. Defaults to 10. */\n maxTurns?: number\n /** Optional cancellation signal — forwarded to the provider's stream. */\n signal?: AbortSignal\n}\n\n/**\n * Drive the provider's `stream()` once and return a concise title for\n * the conversation represented by `turns`. Throws when:\n * - `turns` contains no text-bearing content (nothing to summarize),\n * - the provider stream completes with no output text,\n * - `signal` is aborted (rethrown verbatim).\n *\n * Output is trimmed, single-line, with surrounding quotes / trailing\n * punctuation stripped, and truncated to `TITLE_MAX_CHARS`.\n */\nexport async function generateSessionTitle({\n provider,\n model,\n turns,\n maxTurns = DEFAULT_MAX_TURNS,\n signal,\n}: GenerateSessionTitleOptions): Promise<string> {\n const slice = turns.slice(-Math.max(1, maxTurns))\n const transcript = renderTurnsForPrompt(slice)\n if (!transcript)\n throw new Error('No text content in the recent turns to summarize.')\n\n const system = [\n 'You generate concise, descriptive titles for chat conversations.',\n 'Reply with ONLY the title — no quotes, no markdown, no trailing punctuation,',\n 'no preamble (do not say \"Title:\" or similar).',\n `Aim for 3 to 7 words and stay under ${TITLE_MAX_CHARS} characters.`,\n ].join(' ')\n\n const userPrompt = `Generate a title for this conversation:\\n\\n${transcript}`\n\n let text = ''\n const result = await provider.stream(\n {\n model,\n system,\n tools: [],\n messages: [provider.userMessage(userPrompt)],\n maxTokens: TITLE_RESPONSE_MAX_TOKENS,\n ...(signal ? { signal } : {}),\n },\n {\n onText: (delta) => { text += delta },\n },\n )\n // Prefer the streamed-text accumulator (driven by deltas) but fall\n // back to the provider's already-concatenated `result.text` for\n // adapters that batch on the final frame instead of emitting deltas.\n const raw = text.trim().length > 0 ? text : result.text\n return cleanTitle(raw)\n}\n\n/**\n * Compact a turn list into a transcript-style prompt:\n *\n * user: …\n * assistant: …\n * user: …\n *\n * Tool calls and tool results are summarized to keep the request\n * payload small — the model needs the rough flow of the conversation,\n * not full JSON blobs. Per-turn text is clipped at `PROMPT_TURN_CHAR_MAX`\n * so a single huge code dump doesn't blow the budget.\n *\n * Returns an empty string when the slice has no text-bearing content\n * (e.g. a turn list that's only tool plumbing).\n */\nfunction renderTurnsForPrompt(turns: readonly SessionTurn[]): string {\n const lines: string[] = []\n for (const turn of turns) {\n const body = textBodyOf(turn)\n if (!body)\n continue\n const role = turn.role === 'assistant' ? 'assistant' : turn.role === 'user' ? 'user' : 'system'\n lines.push(`${role}: ${clip(body, PROMPT_TURN_CHAR_MAX)}`)\n }\n return lines.join('\\n\\n')\n}\n\nfunction textBodyOf(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text.trim())\n else if (block.type === 'tool_call')\n parts.push(`[tool: ${block.name}]`)\n else if (block.type === 'tool_result')\n parts.push(`[tool result]`)\n }\n return parts.join(' ')\n}\n\n/**\n * Normalize a model-generated title into the shape we want to persist:\n *\n * - Collapse internal whitespace + take the first line only (some\n * models emit \"Title: foo\\n\\nReason: …\" despite instructions).\n * - Strip surrounding quote characters (`\"foo\"`, `'foo'`, `` `foo` ``).\n * - Strip a leading `Title:` / `title -` prefix if the model added one.\n * - Strip trailing punctuation (`.`, `!`, `?`) — titles read cleaner\n * without it.\n * - Truncate to `TITLE_MAX_CHARS` with a trailing `…` when over.\n *\n * Exported as `cleanTitle` so tests can pin the normalization rules\n * without going through a mock provider.\n */\nexport function cleanTitle(raw: string): string {\n let s = raw.split('\\n')[0]?.trim() ?? ''\n // Strip a \"Title:\" / \"Title -\" preamble the model sometimes adds.\n s = s.replace(/^\\s*title\\s*[:\\-–—]\\s*/i, '')\n // Strip surrounding matched quotes.\n if (s.length >= 2) {\n const first = s.charAt(0)\n const last = s.charAt(s.length - 1)\n const matched = (first === '\"' && last === '\"')\n || (first === '\\'' && last === '\\'')\n || (first === '`' && last === '`')\n || (first === '“' && last === '”')\n if (matched)\n s = s.slice(1, -1).trim()\n }\n // Trim trailing terminal punctuation — titles don't take periods.\n s = s.replace(/[.!?]+$/, '').trim()\n if (s.length === 0)\n throw new Error('Model returned an empty title.')\n return s.length > TITLE_MAX_CHARS ? `${s.slice(0, TITLE_MAX_CHARS - 1)}…` : s\n}\n\nfunction clip(text: string, max: number): string {\n return text.length > max ? `${text.slice(0, max)}…` : text\n}\n","// ---------------------------------------------------------------------------\n// Footer / title hint primitives.\n//\n// Pure data + width-estimation helpers consumed by the TUI's bottom-bar\n// footer, the prompt-box overlay, the sessions header, and any future\n// GUI shell that wants to render the same `<key> <label> · …` hint\n// strings. Plain-text math only — no renderer primitives, no colour\n// resolution, no React. Width estimates are intentionally `.length`-\n// based (assumes ASCII / single-cell glyphs); the consumer's renderer\n// is responsible for the actual painting and any wide-glyph accounting\n// it needs.\n// ---------------------------------------------------------------------------\n\n/**\n * Single hint entry — `<key> <label>`, optionally followed by an inline\n * secondary chord (`<key><extra.key> <label> <extra.label>`) for compound\n * shortcuts like `ctrl+m/n model effort`.\n *\n * Colour fields are advisory hex strings (resolved against the active\n * theme by the renderer); leaving them undefined lets the renderer pick\n * its conventional fallback (e.g. `warn` for keys, `dim` for labels,\n * `mute` for secondary).\n */\nexport interface Hint {\n key: string\n label: string\n /** Optional override for the key color. Renderer default: warn. */\n keyColor?: string\n /** Optional override for the label color. Renderer default: dim. */\n labelColor?: string\n /**\n * Optional secondary chord + value rendered inline after the primary\n * pair: `<key><extra.key> <label> <extra.label>`. Lets one hint advertise\n * a compound shortcut (e.g. `ctrl+m/n model effort`) without forcing\n * callers into a parallel struct or a custom renderer.\n */\n extra?: {\n key: string\n label: string\n /** Defaults to mute on the renderer side. */\n keyColor?: string\n /** Defaults to dim on the renderer side. */\n labelColor?: string\n }\n}\n\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n */\nexport function truncateTrailing(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max === 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered as\n * `<key> <label> · <key> <label> · …`. Exported so prompt-box overlays\n * can run the same responsive math as the bottom-bar footer when\n * deciding whether trigger hints fit. Pure / total.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n if (hints.length === 0)\n return 0\n return hints.reduce((sum, h, i) => sum + hintLength(h) + (i > 0 ? 3 : 0), 0)\n}\n\n/** Plain-text width of a single hint as rendered by `renderHintSpans`. */\nfunction hintLength(h: Hint): number {\n const base = h.key.length + 1 + h.label.length\n const extra = h.extra ? h.extra.key.length + 1 + h.extra.label.length : 0\n return base + extra\n}\n\n/**\n * Stable empty list so callers can compare by reference. Exported so\n * any consumer that needs to feed \"no primary hints\" into the same\n * `clipHintsToWidth` / `renderHintSpans` pipeline (e.g. the TUI's\n * minimal UI mode, where the prompt overlay shows only `@` / `/`\n * triggers) reuses the same frozen array instead of allocating a new\n * one each render.\n */\nexport const EMPTY_HINTS: readonly Hint[] = Object.freeze([])\n\n/**\n * Return the longest prefix of `hints` whose rendered width fits within\n * `budget`. Used to degrade hint rows gracefully at narrow terminal\n * widths instead of letting an absolutely-positioned hint row wrap\n * mid-segment (which paints the overflow over surrounding borders and\n * looks like garbled glyphs in a TUI).\n *\n * Prefix-only (no reordering, no last-hint priority) so the survivors\n * keep their authored order — the user's muscle memory for \"leftmost\n * hint = primary action\" stays intact as the terminal shrinks.\n */\nexport function clipHintsToWidth(hints: readonly Hint[], budget: number): readonly Hint[] {\n if (budget <= 0 || hints.length === 0)\n return EMPTY_HINTS\n const out: Hint[] = []\n let used = 0\n for (let i = 0; i < hints.length; i++) {\n const h = hints[i]!\n const cost = (i > 0 ? 3 : 0) + hintLength(h)\n if (used + cost > budget)\n break\n out.push(h)\n used += cost\n }\n if (out.length === 0)\n return EMPTY_HINTS\n return out.length === hints.length ? hints : out\n}\n","/**\n * Interactions — plan approvals and Q&A clarifications between the agent\n * and the user.\n *\n * Renderer-agnostic by design: this module owns the tool factories, the\n * pending-state derivation from `session.turns`, the response serializer,\n * and a tiny React queue context. The TUI / GUI consume the queue and\n * provide the actual picker UI; the same data plumbing works for both.\n *\n * Persistence model — the call IS the persisted state:\n *\n * - The agent emits `present_plan` / `ask_user` as a regular tool call.\n * The `tool_call` block lands in `session.turns` like any other tool.\n * When the user responds, the corresponding `tool_result` block follows\n * in the next user turn. Standard tool-call/tool-result contract — no\n * parallel store, no extra schema.\n *\n * - \"Pending\" = the trailing assistant turn has a `tool_call` to one of\n * the interaction tools with no matching `tool_result` in a later\n * user turn. Derived at load time via\n * {@link pendingInteractionsFromTurns}; no parallel bookkeeping.\n *\n * - Resumed sessions land in the same UI as live ones. The host only\n * swaps the resolver:\n * - **Live**: the tool's `execute()` is awaiting a Promise; the\n * resolver resolves that Promise. The agent loop persists the\n * tool_result naturally on its next iteration.\n * - **Resumed**: no Promise exists (the prior run was destroyed).\n * The resolver appends a synthetic user turn with the tool_result\n * block and triggers a follow-up `agent.run()` so the model\n * continues from where it left off.\n *\n * No OpenTUI imports — a GUI consumer can ship its own renderer and reuse\n * this module verbatim.\n */\nimport type { ReactNode } from 'react'\nimport type { ToolDef } from '../tools/types'\nimport type { SessionContentBlock, SessionTurn } from '../types'\nimport { createContext, useCallback, useContext, useRef, useState } from 'react'\n\n// ---------------------------------------------------------------------------\n// Tool identities — canonical names persisted in `session.turns`. Stable\n// across the lifetime of the project; renaming would break resume.\n// ---------------------------------------------------------------------------\n\nexport const PRESENT_PLAN_TOOL = 'present_plan'\nexport const ASK_USER_TOOL = 'ask_user'\n\n/** True when `name` is one of the interaction tool canonical names. */\nexport function isInteractionTool(name: string): boolean {\n return name === PRESENT_PLAN_TOOL || name === ASK_USER_TOOL\n}\n\n// ---------------------------------------------------------------------------\n// Payload shapes — parsed views of each tool's `input` blob.\n// ---------------------------------------------------------------------------\n\nexport interface PlanStep {\n id: string\n title: string\n description?: string\n}\n\nexport interface PlanPayload {\n /** Short headline summary — used as the modal title. */\n title: string\n /** Full plan body. Markdown. */\n plan: string\n /** Optional structured step list — surfaced as a checklist preview. */\n steps?: readonly PlanStep[]\n}\n\nexport interface QuestionChoice {\n id: string\n label: string\n description?: string\n}\n\n/**\n * One question in an `ask_user` batch. Discriminated by `type`:\n *\n * - `text` — single-line free-text answer (rendered via `<input>`).\n * - `textarea` — multi-line free-text answer (rendered via `<textarea>`).\n * - `select` — single pick from a fixed set of `choices`.\n * - `confirm` — yes/no boolean (rendered as a 2-option select).\n *\n * The model batches related clarifications into one tool call instead of\n * pinging the user N times with N round-trips.\n */\nexport type QuestionType = 'text' | 'textarea' | 'select' | 'confirm'\n\ninterface BaseQuestion {\n /** Stable id — keys the response back to the model. */\n id: string\n /** Question text shown above the input (Markdown allowed). */\n prompt: string\n /** Optional helper text under the prompt. */\n description?: string\n /**\n * Whether the answer is required (non-empty). Default: `true` for\n * `select` / `confirm` (no sensible empty), `false` for free-text\n * (skipping = empty string). Free-text questions still accept an\n * answer; the flag only gates submission.\n */\n required?: boolean\n}\n\nexport interface TextQuestion extends BaseQuestion {\n type: 'text' | 'textarea'\n /** Placeholder shown when the input is empty. */\n placeholder?: string\n /** Pre-fill the input with this value (still editable). */\n default?: string\n}\n\nexport interface SelectQuestion extends BaseQuestion {\n type: 'select'\n choices: readonly QuestionChoice[]\n}\n\nexport interface ConfirmQuestion extends BaseQuestion {\n type: 'confirm'\n /** Label for the affirmative option. Default: `'yes'`. */\n affirmLabel?: string\n /** Label for the negative option. Default: `'no'`. */\n denyLabel?: string\n}\n\nexport type Question = TextQuestion | SelectQuestion | ConfirmQuestion\n\nexport interface QuestionPayload {\n /** Optional intro / context shown above every question (Markdown). */\n intro?: string\n /** One or more questions for the user to answer in one form. */\n questions: readonly Question[]\n}\n\n/** Per-question answer value — `string` for text/textarea/select, `boolean` for confirm. */\nexport type AnswerValue = string | boolean\n\ninterface BaseRequest {\n /** Stable id — the `tool_call.id` from the model. */\n id: string\n /** Tool canonical name. */\n tool: string\n /** Run that emitted the call, when known. */\n runId?: string\n /** Turn id the call lives in. */\n turnId?: string\n /** Unix ms when the request was raised. */\n createdAt: number\n}\n\nexport interface PlanRequest extends BaseRequest {\n kind: 'plan'\n tool: typeof PRESENT_PLAN_TOOL\n payload: PlanPayload\n}\n\nexport interface QuestionRequest extends BaseRequest {\n kind: 'question'\n tool: typeof ASK_USER_TOOL\n payload: QuestionPayload\n}\n\nexport type InteractionRequest = PlanRequest | QuestionRequest\n\n// ---------------------------------------------------------------------------\n// Response shapes — what the user gives back. Serialized as JSON below so\n// the model parses a stable, explicit envelope.\n// ---------------------------------------------------------------------------\n\nexport type PlanDecision = 'approve' | 'reject' | 'revise'\n\nexport interface PlanResponse {\n kind: 'plan'\n decision: PlanDecision\n /** Optional free-text feedback — shown to the model verbatim. */\n comment?: string\n}\n\nexport interface QuestionResponse {\n kind: 'question'\n /**\n * Map of `Question.id → AnswerValue` for every question the user\n * answered. Missing keys = question skipped (only possible when\n * `required: false`).\n */\n answers: Readonly<Record<string, AnswerValue>>\n}\n\nexport type InteractionResponse = PlanResponse | QuestionResponse\n\n// ---------------------------------------------------------------------------\n// Serialization — what the model sees as the tool_result `output`.\n// ---------------------------------------------------------------------------\n\n/**\n * Format an {@link InteractionResponse} as the tool_result content. JSON\n * keeps the envelope parseable and explicit. Empty fields are stripped so\n * the model doesn't waste tokens reading absent values.\n */\nexport function serializeInteractionResponse(response: InteractionResponse): string {\n if (response.kind === 'plan') {\n const body: Record<string, unknown> = { decision: response.decision }\n if (response.comment && response.comment.trim().length > 0)\n body.comment = response.comment.trim()\n return JSON.stringify(body)\n }\n // Normalize answer values: trim strings, drop empty / undefined entries\n // so the model sees a tight payload of only the meaningful answers.\n const answers: Record<string, AnswerValue> = {}\n for (const [id, value] of Object.entries(response.answers)) {\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (trimmed.length > 0)\n answers[id] = trimmed\n }\n else if (typeof value === 'boolean') {\n answers[id] = value\n }\n }\n return JSON.stringify({ answers })\n}\n\n// ---------------------------------------------------------------------------\n// Resumed-flow turn builder — batches every pending interaction's response\n// into ONE matched user turn.\n//\n// Why a dedicated builder: the agent loop's history-validation contract\n// requires every `tool_use` block in an assistant turn to be followed by\n// EXACTLY ONE matched `tool_result` block in the very next user turn.\n// When a session was killed mid-batch — say the model emitted both\n// `present_plan` and `ask_user` in parallel and the process died before\n// either Promise resolved — naively writing one `tool_result` per UI\n// submission would persist a user turn missing the sibling's match, and\n// the next `agent.run()` would reject the history on the API side.\n//\n// The wizard UI accumulates answers across submissions; this helper\n// converts the accumulated map back into a protocol-valid user turn\n// containing one `tool_result` per pending request, in pending order.\n// ---------------------------------------------------------------------------\n\n/**\n * Build a protocol-valid user `SessionTurn` that closes a batch of\n * resumed pending interactions.\n *\n * - `requests` MUST be the same list returned by\n * {@link pendingInteractionsFromTurns} for the session, in order.\n * - `responses` MUST contain an entry for every request id; throws\n * otherwise (caller bug — flush before all answers collected).\n *\n * Pure / total. Tested in isolation so the batching invariant doesn't\n * regress without the resumed-flow integration noticing.\n */\nexport function buildResumedToolResultsTurn(\n requests: readonly InteractionRequest[],\n responses: ReadonlyMap<string, InteractionResponse>,\n options: {\n /** Turn id minted by the session store. */\n turnId: string\n /**\n * Run id that emitted the pending tool calls — when known, attaching\n * it ties the resumed turn back to the originating `SessionRun` row\n * so consumers grouping turns by run keep clean ancestry across the\n * close/reopen boundary.\n */\n runId?: string\n /** `createdAt` timestamp. Defaults to `Date.now()`. */\n createdAt?: number\n },\n): SessionTurn {\n if (requests.length === 0)\n throw new Error('buildResumedToolResultsTurn: at least one request is required')\n\n const content: SessionContentBlock[] = requests.map((req) => {\n const response = responses.get(req.id)\n if (!response) {\n throw new Error(\n `buildResumedToolResultsTurn: missing response for request \"${req.id}\". `\n + `All ${requests.length} pending interactions must be resolved before flushing.`,\n )\n }\n return {\n type: 'tool_result',\n callId: req.id,\n output: serializeInteractionResponse(response),\n }\n })\n\n return {\n id: options.turnId,\n role: 'user',\n content,\n createdAt: options.createdAt ?? Date.now(),\n ...(options.runId ? { runId: options.runId } : {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pending-state derivation — pure, store-agnostic.\n//\n// The single source of truth for \"what is the session waiting on?\". Used by\n// the chat layer on session activation to decide whether to render the\n// resumed-interaction UI instead of the prompt textarea.\n// ---------------------------------------------------------------------------\n\n/**\n * Scan `turns` and return every interaction tool call without a matching\n * `tool_result`, ordered by turn appearance. Works equally for live mid-run\n * inspection and reload-from-disk.\n *\n * Malformed payloads (input that doesn't match the tool's schema) are\n * skipped silently — the call still lives in the persisted history, but\n * the renderer wouldn't know how to display it. The next `agent.run()` on\n * the session will see the orphan and synthesize an error tool_result via\n * the loop's normal recovery path.\n */\nexport function pendingInteractionsFromTurns(turns: readonly SessionTurn[]): InteractionRequest[] {\n interface CallInfo {\n callId: string\n tool: string\n input: Record<string, unknown>\n runId?: string\n turnId: string\n createdAt: number\n }\n const calls: CallInfo[] = []\n const resultIds = new Set<string>()\n\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_call' && isInteractionTool(block.name)) {\n calls.push({\n callId: block.id,\n tool: block.name,\n input: block.input,\n ...(turn.runId ? { runId: turn.runId } : {}),\n turnId: turn.id,\n createdAt: turn.createdAt,\n })\n }\n else if (block.type === 'tool_result') {\n resultIds.add(block.callId)\n }\n }\n }\n\n const pending: InteractionRequest[] = []\n for (const call of calls) {\n if (resultIds.has(call.callId))\n continue\n if (call.tool === PRESENT_PLAN_TOOL) {\n const payload = parsePlanPayload(call.input)\n if (!payload)\n continue\n pending.push({\n id: call.callId,\n kind: 'plan',\n tool: PRESENT_PLAN_TOOL,\n payload,\n ...(call.runId ? { runId: call.runId } : {}),\n turnId: call.turnId,\n createdAt: call.createdAt,\n })\n }\n else if (call.tool === ASK_USER_TOOL) {\n const payload = parseQuestionPayload(call.input)\n if (!payload)\n continue\n pending.push({\n id: call.callId,\n kind: 'question',\n tool: ASK_USER_TOOL,\n payload,\n ...(call.runId ? { runId: call.runId } : {}),\n turnId: call.turnId,\n createdAt: call.createdAt,\n })\n }\n }\n return pending\n}\n\n/**\n * Defensive parsers — tools validate via JSON Schema before `execute`, but\n * persisted turns can carry malformed input (model regressions, schema\n * migrations). We accept what we recognize and drop what we don't.\n */\nfunction parsePlanPayload(input: Record<string, unknown>): PlanPayload | null {\n const title = typeof input.title === 'string' ? input.title.trim() : ''\n const plan = typeof input.plan === 'string' ? input.plan : ''\n if (!title || !plan)\n return null\n const stepsRaw = Array.isArray(input.steps) ? input.steps : undefined\n const steps: PlanStep[] = []\n if (stepsRaw) {\n for (const s of stepsRaw) {\n if (!s || typeof s !== 'object')\n continue\n const cast = s as { id?: unknown, title?: unknown, description?: unknown }\n if (typeof cast.id !== 'string' || typeof cast.title !== 'string')\n continue\n steps.push({\n id: cast.id,\n title: cast.title,\n ...(typeof cast.description === 'string' ? { description: cast.description } : {}),\n })\n }\n }\n return {\n title,\n plan,\n ...(steps.length > 0 ? { steps } : {}),\n }\n}\n\n/**\n * Defensive parser for an `ask_user` request. Accepts BOTH the new\n * multi-question shape and the legacy single-question shape (kept so\n * sessions created before the multi-question rework still surface\n * correctly on resume).\n *\n * - New: `{ intro?, questions: [...] }`.\n * - Legacy: `{ question, choices?, allowComment? }` → wrapped into a\n * single `select` (if choices) or `textarea` (otherwise) question\n * keyed by the synthetic id `'answer'`.\n */\nfunction parseQuestionPayload(input: Record<string, unknown>): QuestionPayload | null {\n const intro = typeof input.intro === 'string' && input.intro.trim().length > 0\n ? input.intro\n : undefined\n\n // New shape — explicit `questions` array. Parse each entry; drop\n // malformed ones rather than the whole payload so a single bad\n // question doesn't make the entire batch invisible on reload.\n //\n // Dedup by id (first wins). React would warn on the duplicate `key`\n // anyway, but more importantly the response `answers` map is keyed\n // by id — duplicates would silently collapse to a single value the\n // model would receive for every question that shared the id. First-\n // occurrence-wins keeps the form predictable; the model's own\n // schema validation should have caught it earlier.\n if (Array.isArray(input.questions)) {\n const questions: Question[] = []\n const seen = new Set<string>()\n for (const raw of input.questions) {\n const q = parseQuestion(raw)\n if (!q)\n continue\n if (seen.has(q.id))\n continue\n seen.add(q.id)\n questions.push(q)\n }\n if (questions.length === 0)\n return null\n return { ...(intro ? { intro } : {}), questions }\n }\n\n // Legacy shape — flatten to a one-question payload so resumed sessions\n // created before the multi-question rework still render.\n const question = typeof input.question === 'string' ? input.question.trim() : ''\n if (!question)\n return null\n const choices = parseChoices(input.choices)\n const legacy: Question = choices.length > 0\n ? { id: 'answer', prompt: question, type: 'select', choices }\n : { id: 'answer', prompt: question, type: 'textarea' }\n return { ...(intro ? { intro } : {}), questions: [legacy] }\n}\n\nfunction parseQuestion(raw: unknown): Question | null {\n if (!raw || typeof raw !== 'object')\n return null\n const cast = raw as {\n id?: unknown\n prompt?: unknown\n type?: unknown\n description?: unknown\n required?: unknown\n placeholder?: unknown\n default?: unknown\n choices?: unknown\n affirmLabel?: unknown\n denyLabel?: unknown\n }\n if (typeof cast.id !== 'string' || !cast.id)\n return null\n if (typeof cast.prompt !== 'string' || !cast.prompt.trim())\n return null\n const type = cast.type\n if (type !== 'text' && type !== 'textarea' && type !== 'select' && type !== 'confirm')\n return null\n\n const base = {\n id: cast.id,\n prompt: cast.prompt,\n ...(typeof cast.description === 'string' ? { description: cast.description } : {}),\n ...(typeof cast.required === 'boolean' ? { required: cast.required } : {}),\n }\n\n if (type === 'text' || type === 'textarea') {\n return {\n ...base,\n type,\n ...(typeof cast.placeholder === 'string' ? { placeholder: cast.placeholder } : {}),\n ...(typeof cast.default === 'string' ? { default: cast.default } : {}),\n }\n }\n\n if (type === 'select') {\n const choices = parseChoices(cast.choices)\n if (choices.length === 0)\n return null // a select with zero choices is unusable\n return { ...base, type, choices }\n }\n\n // type === 'confirm'\n return {\n ...base,\n type,\n ...(typeof cast.affirmLabel === 'string' ? { affirmLabel: cast.affirmLabel } : {}),\n ...(typeof cast.denyLabel === 'string' ? { denyLabel: cast.denyLabel } : {}),\n }\n}\n\nfunction parseChoices(raw: unknown): QuestionChoice[] {\n if (!Array.isArray(raw))\n return []\n const choices: QuestionChoice[] = []\n for (const c of raw) {\n if (!c || typeof c !== 'object')\n continue\n const cast = c as { id?: unknown, label?: unknown, description?: unknown }\n if (typeof cast.id !== 'string' || typeof cast.label !== 'string')\n continue\n choices.push({\n id: cast.id,\n label: cast.label,\n ...(typeof cast.description === 'string' ? { description: cast.description } : {}),\n })\n }\n return choices\n}\n\n// ---------------------------------------------------------------------------\n// Tool factory — builds `present_plan` and `ask_user` ToolDefs bound to a\n// host-provided async resolver. Used by `buildAgent` in the TUI; a GUI\n// consumer wires its own React resolver the same way.\n// ---------------------------------------------------------------------------\n\nexport interface CreateInteractionToolsOptions {\n /**\n * Called when the agent invokes one of the interaction tools. Resolves\n * with the user's response (forwarded to the model as the tool_result);\n * reject to signal denial / abort (the model receives\n * `Tool error: <message>` as the result).\n */\n requestInteraction: (request: InteractionRequest) => Promise<InteractionResponse>\n}\n\n/**\n * Create the `present_plan` and `ask_user` tools as a record, ready to\n * spread into `createAgent({ tools })` or composed into a {@link Preset}.\n */\nexport function createInteractionTools(opts: CreateInteractionToolsOptions): Record<string, ToolDef> {\n return {\n [PRESENT_PLAN_TOOL]: createPlanTool(opts),\n [ASK_USER_TOOL]: createQuestionTool(opts),\n }\n}\n\nconst PLAN_DESCRIPTION\n = 'Present a multi-step plan to the user for approval BEFORE executing it. '\n + 'Use this when you have a concrete proposal that materially changes the workspace '\n + 'and want explicit confirmation. The user can approve, reject, or request revisions; '\n + 'revisions come back as a comment alongside a `revise` decision.'\n\nconst QUESTION_DESCRIPTION\n = 'Ask the user one or more clarifying questions and pause until they answer. '\n + 'Batch related questions into a single call instead of asking one at a time — '\n + 'every call pauses the conversation. '\n + 'Each question carries a `type`: '\n + '`text` (single line), `textarea` (multi-line), `select` (one of `choices`), or '\n + '`confirm` (yes/no). Use sparingly: only when explicit user input materially '\n + 'changes the next steps.'\n\nfunction createPlanTool({ requestInteraction }: CreateInteractionToolsOptions): ToolDef {\n return {\n spec: {\n name: PRESENT_PLAN_TOOL,\n description: PLAN_DESCRIPTION,\n inputSchema: {\n type: 'object',\n properties: {\n title: { type: 'string', description: 'Short headline summary of the plan.' },\n plan: { type: 'string', description: 'Full plan body, written as Markdown.' },\n steps: {\n type: 'array',\n description: 'Optional structured step list. Each step has an `id`, `title`, and optional `description`.',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n title: { type: 'string' },\n description: { type: 'string' },\n },\n required: ['id', 'title'],\n },\n },\n },\n required: ['title', 'plan'],\n },\n },\n async execute(input, ctx) {\n const payload = parsePlanPayload(input)\n if (!payload)\n throw new Error('present_plan: invalid input — expected non-empty `title` and `plan` strings.')\n const request: PlanRequest = {\n id: ctx.callId,\n kind: 'plan',\n tool: PRESENT_PLAN_TOOL,\n payload,\n ...(ctx.runId ? { runId: ctx.runId } : {}),\n turnId: ctx.turnId,\n createdAt: Date.now(),\n }\n const response = await requestInteraction(request)\n if (response.kind !== 'plan')\n throw new Error(`present_plan: unexpected response kind \"${response.kind}\".`)\n return serializeInteractionResponse(response)\n },\n }\n}\n\nfunction createQuestionTool({ requestInteraction }: CreateInteractionToolsOptions): ToolDef {\n return {\n spec: {\n name: ASK_USER_TOOL,\n description: QUESTION_DESCRIPTION,\n inputSchema: {\n type: 'object',\n properties: {\n intro: {\n type: 'string',\n description: 'Optional context shown above the questions (Markdown).',\n },\n questions: {\n type: 'array',\n description: 'One or more questions for the user to answer in a single form.',\n minItems: 1,\n items: {\n type: 'object',\n properties: {\n id: {\n type: 'string',\n description: 'Stable identifier — keys the answer back to the model.',\n },\n prompt: {\n type: 'string',\n description: 'The question to display.',\n },\n type: {\n type: 'string',\n enum: ['text', 'textarea', 'select', 'confirm'],\n description: '`text` = single line, `textarea` = multi-line, `select` = pick from `choices`, `confirm` = yes/no.',\n },\n description: {\n type: 'string',\n description: 'Optional helper text shown under the prompt.',\n },\n required: {\n type: 'boolean',\n description: 'Whether a non-empty answer is required.',\n },\n placeholder: {\n type: 'string',\n description: 'For `text` / `textarea`: placeholder shown when the input is empty.',\n },\n default: {\n type: 'string',\n description: 'For `text` / `textarea`: pre-fill the input with this value.',\n },\n choices: {\n type: 'array',\n description: 'For `select`: the available choices. Required.',\n items: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n label: { type: 'string' },\n description: { type: 'string' },\n },\n required: ['id', 'label'],\n },\n },\n affirmLabel: {\n type: 'string',\n description: 'For `confirm`: label for the affirmative option. Default: `\"yes\"`.',\n },\n denyLabel: {\n type: 'string',\n description: 'For `confirm`: label for the negative option. Default: `\"no\"`.',\n },\n },\n required: ['id', 'prompt', 'type'],\n },\n },\n },\n required: ['questions'],\n },\n },\n async execute(input, ctx) {\n const payload = parseQuestionPayload(input)\n if (!payload)\n throw new Error('ask_user: invalid input — expected a non-empty `questions` array.')\n const request: QuestionRequest = {\n id: ctx.callId,\n kind: 'question',\n tool: ASK_USER_TOOL,\n payload,\n ...(ctx.runId ? { runId: ctx.runId } : {}),\n turnId: ctx.turnId,\n createdAt: Date.now(),\n }\n const response = await requestInteraction(request)\n if (response.kind !== 'question')\n throw new Error(`ask_user: unexpected response kind \"${response.kind}\".`)\n return serializeInteractionResponse(response)\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// React context — FIFO queue of pending requests + resolver callbacks.\n//\n// Two contexts on purpose (mirrors `safe-mode-context.tsx`):\n// - `InteractionsQueueContext` carries the live queue. Consumers re-render\n// on every push/pop.\n// - `InteractionsActionsContext` carries `{ enqueue, resolveHead, … }` and\n// never changes identity, so callbacks that depend on these don't\n// re-bind on every queue churn.\n// ---------------------------------------------------------------------------\n\nexport interface PendingInteractionEntry {\n request: InteractionRequest\n /** The user picked something — forward to the live tool Promise or the resumed tool_result writer. */\n resolve: (response: InteractionResponse) => void\n /**\n * The user / system cancelled — distinct from `resolve` so renderers can\n * differentiate \"user picked something\" from \"user gave up / aborted\".\n * Live entries reject the tool's Promise; resumed entries decide what\n * to persist as the cancelled tool_result.\n */\n cancel: (reason?: string) => void\n}\n\nexport interface InteractionsActions {\n /** Add a request to the FIFO queue. */\n enqueue: (entry: PendingInteractionEntry) => void\n /** Resolve the head request with `response`. */\n resolveHead: (response: InteractionResponse) => void\n /**\n * Cancel the head request. Reserved for future per-call cancel\n * affordances (a \"skip this\" button, a GUI's close-modal-X) — the\n * built-in TUI currently cancels via the run-level `cancelAll` path\n * only. Kept on the public API so consumer renderers can wire it\n * without a follow-up surface change.\n */\n cancelHead: (reason?: string) => void\n /** Cancel every queued request. Used by abort / session teardown. */\n cancelAll: (reason?: string) => void\n}\n\nconst InteractionsQueueContext = createContext<readonly PendingInteractionEntry[]>([])\nconst InteractionsActionsContext = createContext<InteractionsActions | null>(null)\n\nexport function InteractionsProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly PendingInteractionEntry[]>([])\n\n const enqueue = useCallback((entry: PendingInteractionEntry) => {\n setQueue(prev => [...prev, entry])\n }, [])\n\n const resolveHead = useCallback((response: InteractionResponse) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(response)\n return rest\n })\n }, [])\n\n const cancelHead = useCallback((reason?: string) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.cancel(reason)\n return rest\n })\n }, [])\n\n const cancelAll = useCallback((reason?: string) => {\n setQueue((prev) => {\n for (const entry of prev) entry.cancel(reason)\n return []\n })\n }, [])\n\n // Stable actions identity — every member is `useCallback`'d with `[]`\n // deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useInteractionsActions()`\n // never re-render purely because of queue state.\n const actionsRef = useRef<InteractionsActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { enqueue, resolveHead, cancelHead, cancelAll }\n\n return (\n <InteractionsActionsContext.Provider value={actionsRef.current}>\n <InteractionsQueueContext.Provider value={queue}>\n {children}\n </InteractionsQueueContext.Provider>\n </InteractionsActionsContext.Provider>\n )\n}\n\n/** Read the live queue (re-renders on every push/pop). */\nexport function useInteractionsQueue(): readonly PendingInteractionEntry[] {\n return useContext(InteractionsQueueContext)\n}\n\n/** Read the stable actions object. */\nexport function useInteractionsActions(): InteractionsActions {\n const ctx = useContext(InteractionsActionsContext)\n if (!ctx)\n throw new Error('useInteractionsActions must be used inside <InteractionsProvider>')\n return ctx\n}\n\n// ---------------------------------------------------------------------------\n// Live-tool binding helper — wraps the queue actions into a Promise-shaped\n// `requestInteraction` that {@link createInteractionTools} expects.\n// ---------------------------------------------------------------------------\n\n/**\n * Build the `requestInteraction` callback for `createInteractionTools` from\n * the React-side {@link InteractionsActions}. Each call adds a fresh entry\n * to the queue whose `resolve` / `cancel` route back to one Promise that\n * the tool's `execute` awaits.\n */\nexport function makeRequestInteraction(\n actions: InteractionsActions,\n): CreateInteractionToolsOptions['requestInteraction'] {\n return request => new Promise<InteractionResponse>((resolve, reject) => {\n actions.enqueue({\n request,\n resolve,\n cancel: reason => reject(new Error(reason ?? 'Interaction cancelled')),\n })\n })\n}\n","// ---------------------------------------------------------------------------\n// Renderer-agnostic markdown text segmenter.\n//\n// `splitMarkdownCodeBlocks` walks a markdown string once and emits an\n// ordered list of prose / fenced-code segments. Useful for any consumer\n// that wants to render fenced code differently from prose (a TUI that\n// wraps fences with a copy header, a GUI that hands fenced bodies to a\n// Monaco-style editor, a renderer that pipes prose through `marked` and\n// fences through Shiki, etc).\n//\n// Lives in `zidane/chat` so future renderer shells get the same\n// segmenter — no OpenTUI dependency, no React dependency, pure\n// string-in / array-out.\n// ---------------------------------------------------------------------------\n\n/**\n * One block returned by {@link splitMarkdownCodeBlocks}. Prose segments\n * carry their original markdown (including blank lines that bracketed\n * the fence) so the surrounding paragraph spacing stays faithful to the\n * original input. Code segments carry the raw body — fence markers and\n * info-string stripped — so a \"copy code\" action lands what the user\n * actually sees, byte-for-byte.\n */\nexport interface MarkdownSegment {\n kind: 'prose' | 'code'\n content: string\n /** Info-string (e.g. `ts`, `python`). Empty when the fence had none. */\n lang?: string\n /**\n * `true` on a code segment whose closing fence hasn't arrived yet —\n * i.e. the trailing block during streaming. Consumers can render this\n * in \"streaming\" mode (no copy affordance, partial-highlight pipeline)\n * until the closer lands and it flips to a regular closed segment.\n */\n open?: boolean\n}\n\n/**\n * Split markdown text into alternating prose / fenced-code segments.\n *\n * Recognizes both ` ``` ` and `~~~` fences, with arbitrary length ≥ 3,\n * matching CommonMark's \"fence indicator must be at least as long to\n * close\" rule. Info-strings (the bit after the opening fence) are kept\n * as the `lang` hint; any trailing whitespace is trimmed. Closing\n * fences are detected on a line of their own (whitespace tolerated).\n *\n * Unclosed fences fall back to emitting the would-be-code body as a\n * trailing prose segment so no content is dropped — finalized markdown\n * isn't expected to ship one, but the fallback keeps the renderer\n * truthful when the model produces malformed output.\n *\n * Exported for unit tests.\n */\nexport function splitMarkdownCodeBlocks(text: string): MarkdownSegment[] {\n const lines = text.split('\\n')\n const segments: MarkdownSegment[] = []\n let prose: string[] = []\n let code: string[] = []\n let inFence = false\n let fenceChar = ''\n let fenceLen = 0\n let lang = ''\n\n // Drop a single trailing/leading blank line off each emitted prose\n // segment. Markdown convention puts a blank line between a paragraph\n // and the fence that follows; without trimming, that blank stacks\n // with the code panel's `marginTop: 1` (and symmetrically with\n // `marginBottom: 1` on the post-fence side), producing a double-blank\n // gap when the renderer adds its own panel margins.\n const flushProse = () => {\n if (prose.length > 0 && prose[prose.length - 1] === '')\n prose.pop()\n if (prose.length > 0) {\n segments.push({ kind: 'prose', content: prose.join('\\n') })\n prose = []\n }\n }\n\n let trimLeadingProseBlank = false\n\n for (const line of lines) {\n if (!inFence) {\n const open = line.match(/^(`{3,}|~{3,})([^\\n`~]*)$/)\n if (open) {\n flushProse()\n inFence = true\n fenceChar = open[1]![0]!\n fenceLen = open[1]!.length\n lang = open[2]!.trim()\n code = []\n continue\n }\n if (trimLeadingProseBlank) {\n trimLeadingProseBlank = false\n if (line === '' && prose.length === 0)\n continue\n }\n prose.push(line)\n }\n else {\n const close = line.match(/^(`{3,}|~{3,})\\s*$/)\n if (close && close[1]![0]! === fenceChar && close[1]!.length >= fenceLen) {\n segments.push({ kind: 'code', content: code.join('\\n'), lang })\n inFence = false\n code = []\n trimLeadingProseBlank = true\n continue\n }\n code.push(line)\n }\n }\n\n if (inFence) {\n prose.push(`${fenceChar.repeat(fenceLen)}${lang}`, ...code)\n }\n flushProse()\n return segments\n}\n","/**\n * Per-server MCP OAuth status tracking, decoupled from the renderer.\n *\n * The TUI keeps a `Record<serverName, McpAuthStatus>` so it can render a\n * \"needs-auth\" / \"authorizing\" / \"error\" badge in the MCP picker. The\n * status map is driven entirely by hooks fired from the agent's MCP\n * bootstrap + the interactive `loginMcpServer` flow — the reducer below\n * is pure, makes no FS or network calls, and is shared between the live\n * TUI store and the test harness.\n *\n * Status lifecycle:\n *\n * idle ──mcp:connect──▶ authed\n * (bootstrap succeeded with stored tokens)\n *\n * idle ──mcp:auth:required──▶ needs-auth\n * (bootstrap saw no/invalid tokens)\n *\n * needs-auth ──login()──▶ authorizing ──mcp:auth:url──▶ authorizing(url)\n * ──mcp:auth:success──▶ authed\n * ──mcp:auth:error──▶ error\n *\n * any ──logout──▶ idle\n *\n * Multiple servers progress through the lifecycle independently; the map\n * is keyed by `McpServerConfig.name`.\n */\n\nexport type McpAuthStatus\n // Default — never heard anything about this server. Includes both pure\n // stdio servers (no auth concept) and HTTP servers we haven't bootstrapped yet.\n = | { kind: 'idle' }\n // OAuth-required, no usable tokens. Reason maps to the bootstrap signal:\n // - 'no-tokens' — explicit `auth: 'oauth'` config + empty store.\n // - 'auto-promoted' — server returned 401 + RFC 9728 metadata, eligible\n // for OAuth because the user did not set a static Authorization header.\n | { kind: 'needs-auth', reason: 'no-tokens' | 'auto-promoted' }\n // Login flow in flight. `url` is populated as soon as the SDK builds the\n // authorization URL — the modal renders it so the user can re-open the\n // browser tab manually if the automatic open failed (no GUI, restricted\n // sandbox).\n | { kind: 'authorizing', url?: string }\n // Tokens are stored and (most recently) usable. Surface as a check mark.\n | { kind: 'authed' }\n // Last login attempt failed. `error` is the user-facing message — the\n // TUI shows it verbatim so a network blip or revoked token is debuggable\n // without `ZIDANE_DEBUG`.\n | { kind: 'error', error: string }\n\n/**\n * Map of server name → status. `idle` is the implicit default for any\n * server NOT present in the map, so the renderer treats `undefined` as\n * `{ kind: 'idle' }` and the map can stay sparse.\n */\nexport type McpAuthStateMap = Readonly<Record<string, McpAuthStatus>>\n\n/**\n * Events the reducer accepts. Names mirror the agent hooks 1:1 plus two\n * UI-only verbs (`login:start` / `login:cancel`) so the renderer can\n * optimistically flip a row to `authorizing` before the SDK has emitted\n * `mcp:auth:url` — useful when the loopback callback is slow to bind.\n */\nexport type McpAuthEvent\n = | { type: 'auth-required', name: string, reason: 'no-tokens' | 'auto-promoted' }\n | { type: 'auth-url', name: string, url: string }\n | { type: 'auth-success', name: string }\n | { type: 'auth-error', name: string, error: string }\n | { type: 'connected', name: string }\n | { type: 'login:start', name: string }\n | { type: 'login:cancel', name: string }\n | { type: 'logout', name: string }\n | { type: 'reset', name: string }\n\n/**\n * Apply one event to the state map. Pure, immutable — returns a new map\n * with only the affected key reassigned.\n *\n * State transitions are simple \"last write wins\"; we don't gate (e.g.) a\n * `login:start` from overriding `authorizing` because the loop layer\n * never doubles those up — interactive login is serialized in the TUI.\n */\nexport function reduceMcpAuth(state: McpAuthStateMap, event: McpAuthEvent): McpAuthStateMap {\n const next = (status: McpAuthStatus): McpAuthStateMap => ({ ...state, [event.name]: status })\n switch (event.type) {\n case 'auth-required':\n return next({ kind: 'needs-auth', reason: event.reason })\n case 'auth-url':\n return next({ kind: 'authorizing', url: event.url })\n case 'auth-success':\n return next({ kind: 'authed' })\n case 'auth-error':\n return next({ kind: 'error', error: event.error })\n case 'connected':\n // Only flip to `authed` when this server was OAuth-tracked. A plain\n // stdio bootstrap shouldn't paint every server with a green badge —\n // `idle` means \"nothing to say about auth here\". This matters for\n // the MCP picker's badge column staying signal-dense.\n if (state[event.name]?.kind === 'authorizing'\n || state[event.name]?.kind === 'needs-auth'\n || state[event.name]?.kind === 'authed') {\n return next({ kind: 'authed' })\n }\n return state\n case 'login:start':\n return next({ kind: 'authorizing' })\n case 'login:cancel':\n // Cancellation is treated as \"back to needs-auth\" — the user dismissed\n // the browser flow but the underlying requirement still stands.\n return next({ kind: 'needs-auth', reason: 'no-tokens' })\n case 'logout':\n case 'reset': {\n const { [event.name]: _, ...rest } = state\n return rest\n }\n }\n}\n\n/** Convenience getter — treats `undefined` as `idle`. */\nexport function getMcpAuthStatus(state: McpAuthStateMap, name: string): McpAuthStatus {\n return state[name] ?? { kind: 'idle' }\n}\n","/**\n * React provider for the MCP per-server OAuth state map.\n *\n * `ModalRoot.open(node)` captures the modal contents as a snapshot — naive\n * prop-passing would freeze the auth badge at whatever status existed when\n * the user opened the picker, and a `mcp:auth:url` arriving mid-login\n * would never reach the rendered modal. Routing the state through a\n * context provider mounted ABOVE `ModalRoot` fixes that: a state update\n * re-renders the provider, which re-renders `ModalRoot`, which re-renders\n * the (referentially-stable) modal child node — and consumers calling\n * `useMcpAuthState()` see the fresh value.\n *\n * Two contexts on purpose:\n *\n * - `StateContext` carries the live map. Consumers re-render on every\n * auth event.\n * - `DispatchContext` carries the reducer entry point. Identity is stable\n * across renders, so AppShell's hook handlers don't re-bind when the\n * state changes — they grab `dispatch` once and fire events for the\n * lifetime of the agent.\n *\n * Actions (login / logout / cancel) deliberately do NOT live in context —\n * they depend on AppShell-scoped resources (credential store, catalog ref,\n * agent ref), close over those via refs, and stay stable across renders.\n * Passing them as modal props at `modal.open` time is fine because the\n * captured reference reads from refs internally; the snapshot doesn't go\n * stale even though it was taken at open time.\n */\n\nimport type { ReactNode } from 'react'\nimport type { McpAuthEvent, McpAuthStateMap } from './mcp-auth-state'\nimport { createContext, useCallback, useContext, useState } from 'react'\nimport { reduceMcpAuth } from './mcp-auth-state'\n\nconst StateContext = createContext<McpAuthStateMap>({})\nconst DispatchContext = createContext<((event: McpAuthEvent) => void) | null>(null)\n\n/**\n * Mount once near the top of the React tree (above `ModalRoot`). Children\n * use `useMcpAuthState()` to read and `useMcpAuthDispatch()` to fire events.\n */\nexport function McpAuthProvider({ children }: { children: ReactNode }) {\n const [state, setState] = useState<McpAuthStateMap>({})\n const dispatch = useCallback((event: McpAuthEvent) => {\n setState(prev => reduceMcpAuth(prev, event))\n }, [])\n return (\n <StateContext.Provider value={state}>\n <DispatchContext.Provider value={dispatch}>\n {children}\n </DispatchContext.Provider>\n </StateContext.Provider>\n )\n}\n\nexport function useMcpAuthState(): McpAuthStateMap {\n return useContext(StateContext)\n}\n\nexport function useMcpAuthDispatch(): (event: McpAuthEvent) => void {\n const dispatch = useContext(DispatchContext)\n if (!dispatch)\n throw new Error('useMcpAuthDispatch must be used inside <McpAuthProvider>')\n return dispatch\n}\n","/**\n * File-backed credential storage for MCP OAuth.\n *\n * Lives at `<dataDir>/mcp-credentials.json` (mode 0o600). Separate from\n * `credentials.json` (which holds AI-provider auth) so MCP server tokens\n * can't collide with provider keys, and so a future \"clear MCP auth\"\n * action can wipe just this file.\n *\n * Schema is `Record<serverName, McpCredentialEntry>`:\n * - `tokens` — current OAuth tokens (access + refresh + expiry).\n * - `clientInformation` — RFC 7591 dynamic client registration response.\n * Saved so subsequent runs don't re-register (most auth servers\n * rate-limit DCR and assign a new client per registration).\n * - `discoveryState` — RFC 9728 + RFC 8414 discovery cache. Avoids two\n * well-known requests on every cold start.\n *\n * All file operations are atomic write-then-rename and tolerate missing /\n * corrupt files (return `undefined` for a missing entry, `{}` for a\n * corrupt root file). The store implements `McpCredentialStore` so the\n * SDK-facing `McpOAuthProvider` stays pure (no FS dependency) — tests use\n * the in-memory store from `src/mcp/oauth-provider`.\n */\n\nimport type { OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js'\nimport type {\n OAuthClientInformationMixed,\n OAuthTokens,\n} from '@modelcontextprotocol/sdk/shared/auth.js'\nimport type { McpCredentialEntry, McpCredentialStore } from '../mcp/oauth-provider'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\n\nconst FILE_MODE = 0o600\n\nexport function mcpCredentialsPath(dataDir: string): string {\n return resolve(dataDir, 'mcp-credentials.json')\n}\n\ntype CredentialsFile = Record<string, McpCredentialEntry>\n\nfunction readAll(dataDir: string): CredentialsFile {\n const path = mcpCredentialsPath(dataDir)\n if (!existsSync(path))\n return {}\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n return parsed as CredentialsFile\n }\n catch {\n // Corrupt file — return empty so the user can re-auth without\n // a hand-edit lockout. (Same defensive pattern as `credentials.ts`.)\n return {}\n }\n}\n\nfunction writeAll(dataDir: string, all: CredentialsFile): void {\n writeFileAtomic(\n mcpCredentialsPath(dataDir),\n `${JSON.stringify(all, null, 2)}\\n`,\n { ensureDir: true, mode: FILE_MODE },\n )\n}\n\n/**\n * Build a file-backed `McpCredentialStore`. All per-server reads / writes\n * funnel through the single `mcp-credentials.json` file — this keeps\n * concurrent flows for different servers from clobbering each other since\n * each save reads-modifies-writes the full file atomically.\n *\n * Two flows for the same server name CAN race (last write wins). MCP OAuth\n * flows are user-driven and serial in practice (one login modal at a time),\n * so this is acceptable; a per-server file split + cross-process lock would\n * be required to handle a multi-process scenario.\n */\nexport function createFileMcpCredentialStore(dataDir: string): McpCredentialStore {\n return {\n load(name: string): McpCredentialEntry | undefined {\n return readAll(dataDir)[name]\n },\n save(name: string, entry: McpCredentialEntry): void {\n const all = readAll(dataDir)\n all[name] = entry\n writeAll(dataDir, all)\n },\n delete(name: string): void {\n const all = readAll(dataDir)\n if (!(name in all))\n return\n delete all[name]\n writeAll(dataDir, all)\n },\n }\n}\n\n/**\n * Convenience patches for individual sub-fields. The provider class uses\n * these so a `saveTokens` call doesn't have to round-trip `clientInformation`\n * + `discoveryState` it never touched.\n */\nexport function patchMcpCredential(\n store: McpCredentialStore,\n name: string,\n patch: Partial<McpCredentialEntry>,\n): void {\n const existing = store.load(name) ?? {}\n store.save(name, { ...existing, ...patch })\n}\n\nexport type { McpCredentialEntry, McpCredentialStore }\nexport type { OAuthClientInformationMixed, OAuthDiscoveryState, OAuthTokens }\n","/**\n * Renderer-agnostic data model for the MCP settings list when it\n * supports per-server expand / collapse with nested tool rows.\n *\n * Lives in `chat/` (not `tui/`) because both the standalone\n * `McpsSettingsModal` and the inline `<McpsList>` inside the unified\n * `SettingsModal` consume the exact same flattening + cursor\n * arithmetic — re-deriving it in two places would let the two\n * surfaces drift on edge cases (empty tool catalogs, freshly-removed\n * servers, stale expansion state).\n *\n * The model is a flat `VisibleMcpRow[]` so renderers can iterate once,\n * and the cursor is a single integer that walks both kinds (server\n * headers AND tool checkboxes) without separate \"which server am I\n * on / which tool inside it\" state.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { DiscoveredMcp } from './mcps-discovery'\n\n/**\n * One row in the flattened MCP settings list. Three kinds:\n *\n * - `'server'` — server header with the chevron + checkbox +\n * transport detail. Always present.\n * - `'tool'` — checkbox + description for a tool advertised\n * by the parent server. Present only when the\n * server is expanded AND its tool cache is\n * populated.\n * - `'tool-empty'` — placeholder shown when an expanded server\n * has no cached tool list yet (first launch\n * before any session has bootstrapped the\n * server). Lets the renderer surface a\n * one-line \"send a prompt to populate\" hint\n * WITHOUT changing the server's expand\n * affordance.\n */\nexport type VisibleMcpRow\n = | { kind: 'server', entry: DiscoveredMcp }\n | { kind: 'tool', serverName: string, tool: McpToolSchema }\n | { kind: 'tool-empty', serverName: string }\n\n/**\n * Flatten a server catalog plus the per-server expanded set into the\n * visible-row list a renderer cycles through.\n *\n * - `catalog` — filtered / ordered server list (post-search,\n * post-allow-list).\n * - `expanded` — names of servers whose tool list is open.\n * Servers not in this set render as a single\n * server-header row.\n * - `toolsByServer` — per-server full tool catalog (from\n * `chat/mcp-tools-cache.ts`). Missing key →\n * `'tool-empty'` placeholder under the\n * expanded server.\n */\nexport function buildVisibleMcpRows(\n catalog: readonly DiscoveredMcp[],\n expanded: ReadonlySet<string>,\n toolsByServer: Record<string, readonly McpToolSchema[]>,\n): VisibleMcpRow[] {\n const out: VisibleMcpRow[] = []\n for (const entry of catalog) {\n out.push({ kind: 'server', entry })\n if (!expanded.has(entry.config.name))\n continue\n const tools = toolsByServer[entry.config.name] ?? []\n if (tools.length === 0) {\n out.push({ kind: 'tool-empty', serverName: entry.config.name })\n continue\n }\n for (const tool of tools)\n out.push({ kind: 'tool', serverName: entry.config.name, tool })\n }\n return out\n}\n\n/**\n * Server name a row \"belongs to\" for catalog / toggle / auth lookups.\n * For a server row that's its own name; for tool / tool-empty rows\n * it's the parent server's name.\n */\nexport function parentServerName(row: VisibleMcpRow): string {\n return row.kind === 'server' ? row.entry.config.name : row.serverName\n}\n\n/**\n * Index in `rows` of the server header for the given parent name. `-1`\n * when not present. Used by collapse handlers to snap the cursor up\n * to the parent BEFORE shrinking the row list — otherwise the cursor\n * would land on whichever sibling row now occupies the same flat\n * index.\n */\nexport function indexOfServerRow(rows: readonly VisibleMcpRow[], parentName: string): number {\n return rows.findIndex(\n r => r.kind === 'server' && r.entry.config.name === parentName,\n )\n}\n","/**\n * Renderer-agnostic per-tool toggle for MCP servers. Sibling of\n * {@link useEnabledToggleSet} — same shape, opposite polarity:\n *\n * - `useEnabledToggleSet` is an **allowlist** (`enabledMcps` / etc.),\n * `undefined` = everything enabled, toggle moves to an explicit list.\n * - `useMcpToolToggleSet` is a **deny-list** (`disabledMcpTools`),\n * missing entry = everything enabled, toggle moves to an explicit\n * list of NAMES TO HIDE.\n *\n * The polarity flip is intentional and documented on the setting itself\n * (`Settings.disabledMcpTools`): when an MCP server ships a new tool in\n * a later release, a user who has already opted out of `big_tool` still\n * gets the new one — opposite preference from server-level toggles\n * where a new server should NOT silently appear in the catalog.\n *\n * Two hook flavors:\n *\n * - {@link useMcpToolToggleSet} — one server. Convenience for hosts\n * that render a single server's tool list at a time.\n * - {@link useMcpToolToggleMap} — every server's toggle handler in\n * one call. Required by hosts that dispatch toggles from a shared\n * keyboard handler at the top of the tree (`mcps-settings.tsx`)\n * where calling the single-server hook in a loop would violate\n * rules-of-hooks the moment a server is added or removed.\n *\n * Both flavors route through the same {@link buildToolToggle} factory\n * so semantics stay byte-identical regardless of entry point.\n *\n * Garbage-collects on every write: stale names (tools that disappeared\n * from the server's catalog between launches) are dropped so the\n * persisted deny-list doesn't grow unbounded. Same invariant as the\n * sibling hook for `enabledX` allowlists.\n */\n\nimport type { McpToolSchema } from '../mcp'\nimport type { Settings } from './types'\nimport { useCallback, useMemo } from 'react'\nimport { useSettings } from './settings-context'\n\nexport interface McpToolToggleSet {\n /** Live set of ENABLED tool names — `Set` for O(1) `has`. */\n enabledSet: ReadonlySet<string>\n /** Flip one tool on/off; persists immediately via `setSetting`. */\n toggle: (toolName: string) => void\n}\n\n/**\n * Bind a single MCP server's per-tool toggle to\n * `Settings.disabledMcpTools[serverName]`.\n *\n * `catalog` is the FULL list of tools the server advertises (i.e. the\n * cached `listTools()` payload from `chat/mcp-tools-cache.ts`). The\n * hook needs it both to render `enabledSet` (every catalog tool not in\n * the persisted deny-list) and to garbage-collect stale deny-list\n * entries on every write.\n */\nexport function useMcpToolToggleSet(opts: {\n serverName: string\n catalog: readonly McpToolSchema[]\n}): McpToolToggleSet {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n\n const catalogNames = useMemo(() => opts.catalog.map(t => t.name), [opts.catalog])\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(\n () => buildToolToggle({\n serverName: opts.serverName,\n catalogNames,\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n }),\n [opts.serverName, catalogNames, persistedDenyAll, setDisabled],\n )\n}\n\n/**\n * Bind every server in `catalogByServer` to its own per-tool toggle in\n * a single hook call. Returns a `serverName → McpToolToggleSet` map.\n *\n * Use this when a host needs handlers for multiple servers at once\n * (the TUI's MCP settings panel keyboard dispatcher is the canonical\n * example) — calling {@link useMcpToolToggleSet} in a `for` / `.map`\n * over a dynamic catalog would change the hook-call order whenever a\n * server is added or removed, violating React's rules-of-hooks. This\n * hook calls `useMemo` exactly once regardless of server count.\n *\n * Servers absent from `catalogByServer` get no entry in the result.\n * The persisted deny-list still keeps their entries on disk — they\n * just stay invisible to this consumer until the catalog re-includes\n * them.\n */\nexport function useMcpToolToggleMap(opts: {\n catalogByServer: Record<string, readonly McpToolSchema[]>\n}): Record<string, McpToolToggleSet> {\n const { settings, setSetting } = useSettings()\n const setDisabled = useCallback(\n (next: Settings['disabledMcpTools']) => setSetting('disabledMcpTools', next),\n [setSetting],\n )\n const persistedDenyAll = settings.disabledMcpTools\n\n return useMemo(() => {\n const out: Record<string, McpToolToggleSet> = {}\n for (const [serverName, catalog] of Object.entries(opts.catalogByServer)) {\n out[serverName] = buildToolToggle({\n serverName,\n catalogNames: catalog.map(t => t.name),\n persistedDenyAll,\n setDisabledMcpTools: setDisabled,\n })\n }\n return out\n }, [opts.catalogByServer, persistedDenyAll, setDisabled])\n}\n\n/**\n * Pure factory shared by both hook flavors. Captures `persistedDenyAll`\n * + `setDisabledMcpTools` at the call site so toggle handlers stay\n * referentially stable across renders that don't touch the underlying\n * settings entry — important for `useKeyboard` to avoid re-binding on\n * every catalog change.\n *\n * Exported for direct use by non-React consumers (CLI, GUI shells)\n * that want the same write semantics without the hook plumbing.\n */\nexport function buildToolToggle(args: {\n serverName: string\n catalogNames: readonly string[]\n persistedDenyAll: Settings['disabledMcpTools']\n setDisabledMcpTools: (next: Settings['disabledMcpTools']) => void\n}): McpToolToggleSet {\n const { serverName, catalogNames, persistedDenyAll, setDisabledMcpTools } = args\n const persistedDeny = persistedDenyAll?.[serverName]\n const catalogSet = new Set(catalogNames)\n\n const enabledSet: ReadonlySet<string> = (() => {\n if (!persistedDeny || persistedDeny.length === 0)\n return new Set(catalogNames)\n const deny = new Set(persistedDeny)\n return new Set(catalogNames.filter(name => !deny.has(name)))\n })()\n\n const toggle = (toolName: string): void => {\n // Compute the next deny-list against the persisted state (NOT\n // `enabledSet`) so we never accidentally promote \"implicit-enabled-\n // because-no-entry\" tools into an explicit list on the first\n // toggle. GC stale names at the same time so the persisted file\n // tracks reality.\n const currentDeny = new Set(persistedDeny ?? [])\n if (currentDeny.has(toolName))\n currentDeny.delete(toolName)\n else\n currentDeny.add(toolName)\n\n const nextForServer = [...currentDeny]\n .filter(n => catalogSet.has(n))\n .sort()\n\n const prevMap = persistedDenyAll ?? {}\n const nextMap: Record<string, readonly string[]> = { ...prevMap }\n if (nextForServer.length === 0) {\n // Empty entry collapses back to \"no customization\" so the\n // persisted file stays tidy when a user re-enables every tool\n // they previously disabled.\n delete nextMap[serverName]\n }\n else {\n nextMap[serverName] = nextForServer\n }\n\n // Top-level `undefined` represents \"no per-tool prefs anywhere\".\n // Restore that when the last server entry empties out so the\n // diff against defaults stays minimal.\n const next: Settings['disabledMcpTools']\n = Object.keys(nextMap).length === 0 ? undefined : nextMap\n setDisabledMcpTools(next)\n }\n\n return { enabledSet, toggle }\n}\n","/**\n * Per-server MCP tool-catalog cache.\n *\n * Stores the raw `tools/list` schemas zidane saw on the most recent\n * successful bootstrap of each server, so the TUI's MCP settings panel\n * (and any other host UI) can display every advertised tool — name,\n * description, input-schema — without forcing a fresh bootstrap on\n * every open. The cache also survives across launches so the very\n * first paint of the settings panel after a process restart isn't\n * blank.\n *\n * Wired via the framework's `mcp:tools:list` hook (see\n * `AgentHooks['mcp:tools:list']`). Hosts call\n * {@link subscribeMcpToolsCache} once per agent and the subscriber\n * persists every successful bootstrap to disk; the settings UI reads\n * via {@link loadMcpToolsCache}.\n *\n * Atomic write + best-effort read: a corrupt cache file is treated as\n * empty (the next bootstrap repopulates it). The cache is purely an\n * accelerator — losing it never changes the agent's runtime behavior.\n *\n * Sits in `chat/` not `mcp/` because it depends on a per-host data\n * directory (`<userDir>/mcp-tools-cache.json`) and pairs with the\n * settings + UI surfaces that consume it. Renderer-agnostic — no React,\n * no OpenTUI — so a future GUI host can subscribe the same way.\n */\n\nimport type { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'\nimport type { Hookable } from 'hookable'\nimport type { AgentHooks } from '../agent'\nimport type { McpServerConfig, McpToolSchema } from '../mcp'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { errorMessage } from '../errors'\nimport { connectMcpServers } from '../mcp'\n\n/**\n * Disk filename. Versioned so a future schema change (e.g. adding\n * provenance / TTL) can ship a parallel `v2` file without colliding.\n */\nconst CACHE_FILENAME = 'mcp-tools-cache.json'\n\n/**\n * One server's cached entry.\n *\n * - `tools` — verbatim copy of the raw `tools/list` payload (pre any\n * `disabledTools` / `toolFilter` filtering). The settings UI ranges\n * over this to render the full server surface; the host applies its\n * own deny-list on top via `Settings.disabledMcpTools` (see\n * `buildMcpServers` in `mcps-discovery.ts`).\n * - `updatedAt` — ms-epoch of the bootstrap that populated this entry.\n * Useful for \"last seen N hours ago\" affordances and for cache\n * eviction policies a host might layer on later.\n * - `transport` — informational only; lets the UI distinguish a stdio\n * server's tool list from an http server with the same name in\n * \"stale catalog\" warnings.\n */\nexport interface CachedMcpToolList {\n tools: McpToolSchema[]\n updatedAt: number\n transport: 'stdio' | 'sse' | 'streamable-http'\n}\n\n/**\n * `serverName → catalog`. Map shape on disk so adding / removing\n * servers is an O(1) JSON edit instead of an array rewrite.\n */\nexport type McpToolsCache = Record<string, CachedMcpToolList>\n\n/**\n * Resolve the on-disk cache path under a host's data directory. Exposed\n * for tests + advanced hosts that want to inspect / delete the file\n * without re-implementing the path convention.\n */\nexport function mcpToolsCachePath(dataDir: string): string {\n return resolve(dataDir, CACHE_FILENAME)\n}\n\n/**\n * Best-effort cache read. Returns `{}` when the file is missing, when\n * JSON.parse rejects, or when the top-level shape isn't a plain object —\n * a corrupt cache must never crash the host on startup.\n *\n * Per-entry shape validation is light (presence of an array `tools`):\n * deeper validation would lock the cache to a single zidane version,\n * which is the opposite of what a long-lived cache wants. Entries that\n * survive the light check feed back through `McpToolSchema`'s `unknown`\n * `inputSchema` so downstream readers stay tolerant of upstream churn.\n */\nexport function loadMcpToolsCache(opts: { dataDir: string }): McpToolsCache {\n const path = mcpToolsCachePath(opts.dataDir)\n if (!existsSync(path))\n return {}\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = JSON.parse(raw) as unknown\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))\n return {}\n const out: McpToolsCache = {}\n for (const [name, entry] of Object.entries(parsed as Record<string, unknown>)) {\n if (isCachedEntry(entry))\n out[name] = entry\n }\n return out\n }\n catch {\n return {}\n }\n}\n\n/**\n * Atomic full-cache write. Used by {@link subscribeMcpToolsCache} after\n * each upsert. Failures bubble up as the underlying `writeFileAtomic`\n * error — the caller logs under `ZIDANE_DEBUG` and swallows.\n */\nexport function saveMcpToolsCache(opts: { dataDir: string, cache: McpToolsCache }): void {\n writeFileAtomic(\n mcpToolsCachePath(opts.dataDir),\n `${JSON.stringify(opts.cache, null, 2)}\\n`,\n { ensureDir: true },\n )\n}\n\n/**\n * Drop entries for one or more servers, or wipe the cache when no\n * server is named. Idempotent — entries that aren't present are\n * silently skipped. Returns the resulting cache so callers can refresh\n * any in-memory mirror without re-reading from disk.\n *\n * Intended for a future \"rescan server\" affordance in the settings UI\n * (clear → trigger a real bootstrap → cache repopulates on the\n * `mcp:tools:list` hook).\n */\nexport function clearMcpToolsCache(opts: { dataDir: string, serverNames?: readonly string[] }): McpToolsCache {\n const current = loadMcpToolsCache(opts)\n if (!opts.serverNames) {\n saveMcpToolsCache({ dataDir: opts.dataDir, cache: {} })\n return {}\n }\n const next: McpToolsCache = { ...current }\n for (const name of opts.serverNames)\n delete next[name]\n saveMcpToolsCache({ dataDir: opts.dataDir, cache: next })\n return next\n}\n\n/**\n * Subscribe to `mcp:tools:list` and persist every successful bootstrap\n * into the on-disk cache. Returns an unsubscribe function — call it\n * during agent teardown so a long-lived host (Settings modal that\n * outlives one agent) doesn't accumulate stale subscribers.\n *\n * Each write loads the current cache, upserts the named entry, then\n * atomic-writes the full file. The read-modify-write is safe under\n * Node single-threaded event loop semantics — concurrent bootstraps in\n * the same process serialize through the hook bus.\n *\n * Optional `onChange` callback fires synchronously after every\n * successful disk write with the entry that was just upserted. Lets a\n * React host trigger a state refresh without polling the disk.\n *\n * Write failures are swallowed (logged under `ZIDANE_DEBUG`): the cache\n * is an accelerator, not a correctness boundary. If the disk is full\n * the agent run still works — the settings UI just shows stale data\n * until the next successful write.\n */\nexport function subscribeMcpToolsCache(\n hooks: Hookable<AgentHooks>,\n opts: {\n dataDir: string\n onChange?: (entry: { name: string } & CachedMcpToolList) => void\n },\n): () => void {\n const unsubscribe = hooks.hook('mcp:tools:list', (ctx) => {\n const entry: CachedMcpToolList = {\n tools: [...ctx.tools],\n updatedAt: Date.now(),\n transport: ctx.transport,\n }\n try {\n const current = loadMcpToolsCache({ dataDir: opts.dataDir })\n const next: McpToolsCache = { ...current, [ctx.name]: entry }\n saveMcpToolsCache({ dataDir: opts.dataDir, cache: next })\n opts.onChange?.({ name: ctx.name, ...entry })\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] mcp-tools-cache write failed for \"${ctx.name}\": ${errorMessage(err)}\\n`)\n }\n })\n return unsubscribe\n}\n\n/**\n * Force-refresh the on-disk tools cache by opening a transient MCP\n * connection to every server, capturing the `mcp:tools:list` hook on\n * the way through, and closing the connection immediately. The\n * underlying `connectMcpServers` runs every server's bootstrap in\n * parallel, so the wall-clock cost collapses to the slowest server.\n *\n * Two design choices worth noting:\n *\n * - **Shared hook bus.** The caller passes the SAME `Hookable`\n * instance that has the cache subscriber wired (typically\n * `agent.hooks` after `subscribeMcpToolsCache(agent.hooks, …)`\n * ran). That way `mcp:tools:list` fires on the bus the\n * subscriber is listening to and the cache lands on disk\n * without a separate write path.\n * - **Transient, NOT shared with the live agent.** The connection\n * opened here is closed immediately. The agent's own MCP\n * connection is untouched, so a refresh never disrupts an\n * in-flight tool call.\n *\n * The function intentionally swallows per-server failures (they're\n * already surfaced via `mcp:bootstrap:end ok: false` for any host\n * that wired the listener). The Promise resolves once every server's\n * bootstrap settles, success or otherwise.\n */\nexport async function refreshMcpToolsCatalog(opts: {\n servers: readonly McpServerConfig[]\n hooks: Hookable<AgentHooks>\n /**\n * Same OAuth-provider factory the agent's bootstrap uses. Required\n * for OAuth-tagged servers — without it those servers fire\n * `mcp:auth:required` and skip, and their tools never refresh.\n */\n buildAuthProvider?: (config: McpServerConfig) => OAuthClientProvider | undefined\n}): Promise<void> {\n if (opts.servers.length === 0)\n return\n const connection = await connectMcpServers(\n opts.servers as McpServerConfig[],\n undefined,\n opts.hooks,\n opts.buildAuthProvider ? { buildAuthProvider: opts.buildAuthProvider } : undefined,\n )\n // `mcp:tools:list` already fired during the bootstrap (see\n // `src/mcp/index.ts:bootstrapServer`) so the cache subscriber has\n // already taken its snapshot. Close fast — leaving the transient\n // connection alive would double the MCP socket count for no gain.\n await connection.close().catch((err) => {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] mcp-tools-cache transient close failed: ${errorMessage(err)}\\n`)\n })\n}\n\n/**\n * Type-narrowing check used by {@link loadMcpToolsCache}. Kept private\n * — callers that want a richer validation surface should layer it on\n * top of the loose disk shape rather than tightening this predicate.\n */\nfunction isCachedEntry(value: unknown): value is CachedMcpToolList {\n if (!value || typeof value !== 'object')\n return false\n const entry = value as Partial<CachedMcpToolList>\n if (!Array.isArray(entry.tools))\n return false\n if (typeof entry.updatedAt !== 'number' || !Number.isFinite(entry.updatedAt))\n return false\n if (entry.transport !== 'stdio' && entry.transport !== 'sse' && entry.transport !== 'streamable-http')\n return false\n // Per-tool shape is intentionally NOT validated — the MCP SDK's tool\n // schema surface is open-ended (`inputSchema: unknown`) and we want\n // the cache to keep working when an upstream tool sprouts a new\n // field zidane doesn't model yet. Downstream readers handle unknowns.\n return true\n}\n","/**\n * Shared search-path builder for project + user config discovery.\n *\n * Both Skills (`skills/<name>/SKILL.md`) and MCP servers (`mcps.json`)\n * follow the same first-found-wins order:\n *\n * 1. `{project-root}/.agents/<sub>` — project, agnostic convention\n * 2. `{project-root}/.{prefix}/<sub>` — project, zidane (`.zidane` default)\n * 3. `~/.agents/<sub>` — user, agnostic\n * 4. `~/.{prefix}/<sub>` — user, zidane\n *\n * `{project-root}` is the git root walked up from `cwd` when present,\n * else `cwd` itself. This pairs with the project-scoped session\n * storage (`resolveStoragePaths`) so all three project-aware surfaces\n * — sessions, mcps, skills — anchor at the same `.{prefix}/`.\n *\n * Centralizing the pattern keeps the four paths in one place — a third\n * discovery surface (hooks, rules, …) gets the same convention for free\n * and stays in lock-step on a prefix bump.\n */\n\nimport { homedir } from 'node:os'\nimport { resolve } from 'node:path'\nimport { findGitRoot } from './project-root'\n\n/** A discovered path tagged with whether it lives in the project or the user dir. */\nexport interface ProjectUserPath {\n path: string\n source: 'project' | 'user'\n}\n\n/**\n * Resolve the four search paths for a given subdirectory (`skills`,\n * `mcps.json`, …). Returns absolute paths regardless of whether they\n * exist — the caller chooses to `existsSync`-filter or feed straight\n * into a tolerant discovery routine.\n */\nexport function projectUserPaths(opts: {\n /** Subdirectory or filename relative to each search root. E.g. `'skills'`, `'mcps.json'`. */\n subPath: string\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n}): ProjectUserPath[] {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n // Anchor the project search at the git root when one exists — so a\n // subdir invocation (e.g. `cd repo/packages/foo`) still finds the\n // repo-level `.zidane/mcps.json`. Falls back to `cwd` outside git.\n const projectRoot = findGitRoot(cwd) ?? cwd\n // `prefix` arrives as `.zidane` in `ResolvedConfig`; trim the leading dot\n // for the symmetric `.agents` / `.zidane` convention.\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n return [\n { path: resolve(projectRoot, `.agents/${opts.subPath}`), source: 'project' },\n { path: resolve(projectRoot, `.${prefix}/${opts.subPath}`), source: 'project' },\n { path: resolve(home, `.agents/${opts.subPath}`), source: 'user' },\n { path: resolve(home, `.${prefix}/${opts.subPath}`), source: 'user' },\n ]\n}\n","/**\n * MCP server discovery from project + user config files.\n *\n * Reads `mcps.json` (zidane's canonical name) AND `mcp.json` (Cursor /\n * common ecosystem alias) from both `.agents/` and `.{prefix}/` in\n * each of the project + user dirs — a list of `McpServerConfig`\n * shapes the SDK accepts. Files are normalized via the same path the\n * agent uses (`normalizeMcpServers`), and the user's enabled-toggle\n * list folds the result into a server array the agent picks up at\n * construction.\n *\n * Co-existing files merge by first-found-per-name: e.g.\n * `.agents/mcps.json` declares `linear`, `.agents/mcp.json` declares\n * `linear` + `notion` → discovery emits the `mcps.json` `linear` plus\n * `mcp.json`'s `notion`. Project always beats user across all files.\n *\n * Pattern mirrors `skills-discovery.ts` — the chat layer owns\n * discovery + UI, the agent receives the same `McpServerConfig[]` it\n * always did.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { errorMessage } from '../errors'\nimport { normalizeMcpServers } from '../mcp'\nimport { projectUserPaths } from './project-user-paths'\n\n/**\n * File names searched in each of the four project/user roots. Order\n * matters within a single directory — `mcps.json` (zidane's canonical\n * name) beats `mcp.json` (Cursor / common ecosystem alias) when both\n * exist side-by-side. Across files we merge per-name with first-found\n * winning, so co-existing `mcp.json` entries are folded in for names\n * not declared in the canonical file.\n */\nconst MCP_CONFIG_FILE_NAMES = ['mcps.json', 'mcp.json'] as const\n\n/**\n * One entry in the discovered list. `path` is the source file (one of\n * the `mcp(s).json` variants under `.agents/` or `.{prefix}/`) so a\n * Settings picker can show \"Defined in ~/.zidane/mcp.json\" tooltips\n * and the user can tell which file declared what.\n */\nexport interface DiscoveredMcp {\n config: McpServerConfig\n source: 'project' | 'user'\n path: string\n}\n\n/**\n * Search order for MCP config files — see {@link projectUserPaths}.\n *\n * Both `mcps.json` (zidane's canonical name) and `mcp.json` (Cursor /\n * common ecosystem alias) are picked up from every search root. The\n * canonical name wins within the same directory; across files the\n * usual first-found-by-name dedup applies, so `mcp.json` effectively\n * merges its non-overlapping entries on top of `mcps.json`.\n *\n * Order per scope (project beats user):\n * 1. `{project}/.agents/mcps.json`\n * 2. `{project}/.agents/mcp.json`\n * 3. `{project}/.{prefix}/mcps.json`\n * 4. `{project}/.{prefix}/mcp.json`\n * 5. `~/.agents/mcps.json`\n * 6. `~/.agents/mcp.json`\n * 7. `~/.{prefix}/mcps.json`\n * 8. `~/.{prefix}/mcp.json`\n *\n * Non-existent files are skipped without error.\n */\nexport function defaultMcpsConfigPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): { path: string, source: 'project' | 'user' }[] {\n // Build the path list per-filename via the shared helper, then\n // interleave so `<dir>/mcps.json` lands immediately before\n // `<dir>/mcp.json` for each scope. Same ordering invariant that\n // `projectUserPaths` already guarantees within a filename — the\n // interleave preserves it across filenames.\n const perName = MCP_CONFIG_FILE_NAMES.map(name => projectUserPaths({ subPath: name, ...opts }))\n const rootCount = perName[0].length\n const out: { path: string, source: 'project' | 'user' }[] = []\n for (let i = 0; i < rootCount; i++) {\n for (const list of perName)\n out.push(list[i])\n }\n return out\n}\n\n/**\n * Parse one `mcps.json` file. Canonical zidane shape is a flat array; the\n * other shapes exist for copy-paste from neighboring tools:\n *\n * - `McpServerConfig[]` — flat array (recommended).\n * - `{ name: {...} }` — name-keyed map.\n * - `{ \"mcpServers\": { name: {...} } }` — Claude Desktop / Claude Code /\n * Cursor wrapper. Unwrapped before normalization (otherwise\n * `normalizeMcpServers` would treat the whole wrapper as a single server).\n *\n * Per-entry `enabled: false` (Cursor runtime state) drops the entry — same\n * intent as the source tool. Use the TUI toggle list for \"show but disabled\".\n *\n * Tolerant to common copy-paste artifacts: when strict `JSON.parse` fails,\n * retries after normalizing invisible whitespace that's valid Unicode but\n * not valid JSON whitespace (BOM, NBSP, zero-width spaces, line/paragraph\n * separators) — only OUTSIDE string literals, so user data isn't mutated.\n * If the sanitized version still fails, throws the original error with a\n * targeted hint when NBSPs are present.\n *\n * Validation flows through `normalizeMcpServers` so the result matches\n * what `createAgent` accepts. Throws on truly malformed JSON; the caller\n * decides whether to surface or swallow.\n */\nexport function parseMcpsFile(text: string): McpServerConfig[] {\n let raw: unknown\n try {\n raw = JSON.parse(text)\n }\n catch (firstErr) {\n const sanitized = sanitizeJsonWhitespace(text)\n if (!sanitized.changed)\n throw firstErr\n try {\n raw = JSON.parse(sanitized.text)\n }\n catch {\n throw enhanceJsonError(firstErr, text)\n }\n }\n const target: unknown = raw && typeof raw === 'object' && !Array.isArray(raw) && 'mcpServers' in raw\n ? (raw as { mcpServers: unknown }).mcpServers\n : raw\n return normalizeMcpServers(dropDisabledEntries(target))\n}\n\n/**\n * Code points that are valid Unicode whitespace but NOT valid JSON whitespace.\n * Copy-paste from rich-text contexts (markdown editors, chat UIs, web pages)\n * frequently slips these in:\n *\n * - `\\uFEFF` BOM — sometimes prepended by Windows editors.\n * - `\\u00A0` NBSP — markdown/HTML smart-replace common culprit.\n * - `\\u200B`-`\\u200D` zero-width family — often invisible in editors.\n * - `\\u2028` / `\\u2029` line/paragraph separators — valid in JS strings\n * pre-ES2019 but never in strict JSON.\n * - `\\u2060` word joiner.\n *\n * Replace with ASCII space (zero-width chars are removed; the others become\n * a single space). Replacements happen ONLY outside string literals so a\n * user's intentional NBSP inside a `url` or display name is left alone.\n */\nconst PROBLEMATIC_WHITESPACE = new Set<number>([\n 0xFEFF,\n 0x00A0,\n 0x200B,\n 0x200C,\n 0x200D,\n 0x2028,\n 0x2029,\n 0x2060,\n])\n\nfunction sanitizeJsonWhitespace(text: string): { text: string, changed: boolean } {\n let changed = false\n let inString = false\n let escaped = false\n const out: string[] = []\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (inString) {\n out.push(ch)\n if (escaped)\n escaped = false\n else if (ch === '\\\\')\n escaped = true\n else if (ch === '\"')\n inString = false\n continue\n }\n if (ch === '\"') {\n inString = true\n out.push(ch)\n continue\n }\n const code = ch.charCodeAt(0)\n if (PROBLEMATIC_WHITESPACE.has(code)) {\n changed = true\n // Zero-width chars (200B-200D, 2060, FEFF) erased; visible whitespace\n // replacements become a single space so token boundaries are preserved.\n if (code === 0x00A0 || code === 0x2028 || code === 0x2029)\n out.push(' ')\n continue\n }\n out.push(ch)\n }\n return { text: out.join(''), changed }\n}\n\nfunction enhanceJsonError(err: unknown, text: string): Error {\n const orig = err instanceof Error ? err : new Error(String(err))\n const hints: string[] = []\n if (text.includes('\\u00A0'))\n hints.push('the file contains non-breaking spaces (U+00A0), likely a copy-paste artifact — replace them with regular spaces')\n if (text.charCodeAt(0) === 0xFEFF)\n hints.push('the file starts with a BOM (U+FEFF), which strict JSON.parse rejects')\n if (hints.length === 0)\n return orig\n return new Error(`${orig.message} — ${hints.join('; ')}`)\n}\n\n/**\n * Strip `enabled: false` entries before normalization — same intent as the\n * source tool that wrote them (Cursor flips this when the user toggles a\n * server off in its UI). One pass over array OR map shape, O(N).\n *\n * Done pre-normalize so we don't have to teach `normalizeMcpServers` about\n * the flag — the canonical `McpServerConfig` has no `enabled` field.\n */\nfunction dropDisabledEntries(target: unknown): unknown {\n if (Array.isArray(target))\n return target.filter(e => !isEntryDisabled(e))\n if (target && typeof target === 'object') {\n const filtered: Record<string, unknown> = {}\n for (const [name, entry] of Object.entries(target as Record<string, unknown>)) {\n if (!isEntryDisabled(entry))\n filtered[name] = entry\n }\n return filtered\n }\n return target\n}\n\nfunction isEntryDisabled(entry: unknown): boolean {\n return !!entry && typeof entry === 'object'\n && (entry as { enabled?: unknown }).enabled === false\n}\n\n/**\n * Per-file failure surfaced from {@link discoverProjectMcps}. The TUI shows\n * these in the MCP settings empty / preamble state so a malformed\n * `mcps.json` doesn't look identical to a missing one.\n */\nexport interface DiscoveryError {\n path: string\n source: 'project' | 'user'\n message: string\n}\n\n/**\n * Result of {@link discoverProjectMcps}. `servers` is the per-source-priority\n * merged list (project beats user, first-found wins on name collision).\n * `errors` is one entry per file that existed but failed to parse — empty\n * on the happy path.\n */\nexport interface DiscoveryResult {\n servers: DiscoveredMcp[]\n errors: DiscoveryError[]\n}\n\n/**\n * Walk the default config paths, parse every file that exists, and return\n * `{ servers, errors }` in source-priority order. Project entries appear\n * before user entries; first occurrence of a `name` wins (later\n * duplicates dropped).\n *\n * Parse failures are captured into `errors` rather than thrown — the picker\n * surfaces them while still showing whatever DID parse. A `ZIDANE_DEBUG`\n * log line is also emitted for ops visibility.\n */\nexport function discoverProjectMcps(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): DiscoveryResult {\n const paths = defaultMcpsConfigPaths(opts).filter(p => existsSync(p.path))\n const seen = new Set<string>()\n const servers: DiscoveredMcp[] = []\n const errors: DiscoveryError[] = []\n for (const { path, source } of paths) {\n let configs: McpServerConfig[]\n try {\n configs = parseMcpsFile(readFileSync(path, 'utf8'))\n }\n catch (err) {\n const message = errorMessage(err)\n errors.push({ path, source, message })\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] failed to parse \"${path}\": ${message}\\n`)\n continue\n }\n for (const config of configs) {\n if (seen.has(config.name))\n continue\n seen.add(config.name)\n servers.push({ config, source, path })\n }\n }\n return { servers, errors }\n}\n\n/**\n * Map a user-toggled enable list onto the `mcpServers` array the agent\n * receives. Conventions match `buildSkillsConfig`:\n *\n * - `enabled === undefined` → every discovered server enabled (default).\n * - `enabled === []` → no servers (the agent runs MCP-less).\n * - `enabled === [names]` → allowlist; servers not in the list are\n * dropped.\n *\n * `disabledTools` is the per-server tool deny-list from\n * `Settings.disabledMcpTools`. For each server that survives the\n * server-level enable filter, the per-tool names are merged into\n * `McpServerConfig.disabledTools` via **union** with anything the JSON\n * file already pinned — a tool listed in either source is dropped.\n * Union (not override) keeps JSON-side policy stable: a server file\n * that pins `disabledTools: ['dangerous_tool']` can't be re-enabled\n * via the UI by accident.\n *\n * `enabledTools` (JSON-side allowlist) takes precedence over the\n * UI-side deny-list when both are set on the same server — the\n * allowlist's \"only these\" semantics would be meaningless if a UI\n * deny-list could subtract from it again. We don't emit `disabledTools`\n * when `enabledTools` is present.\n *\n * Returns a fresh array — callers can mutate without affecting the\n * underlying discovery list. Per-server config objects are also fresh\n * copies (shallow), so the merged `disabledTools` doesn't leak back\n * onto the discovery catalog.\n */\nexport function buildMcpServers(opts: {\n discovered: readonly DiscoveredMcp[]\n enabled?: readonly string[]\n disabledTools?: Record<string, readonly string[]>\n}): McpServerConfig[] {\n const survivors = filterByEnabled(opts.discovered, opts.enabled)\n const deny = opts.disabledTools\n if (!deny || Object.keys(deny).length === 0)\n return survivors.map(d => ({ ...d.config }))\n\n return survivors.map((d) => {\n const next: McpServerConfig = { ...d.config }\n const uiDeny = deny[d.config.name]\n if (!uiDeny || uiDeny.length === 0)\n return next\n // `enabledTools` is JSON-side authority — see JSDoc.\n if (next.enabledTools && next.enabledTools.length > 0)\n return next\n const merged = new Set<string>(next.disabledTools ?? [])\n for (const name of uiDeny)\n merged.add(name)\n next.disabledTools = [...merged].sort()\n return next\n })\n}\n\nfunction filterByEnabled(\n discovered: readonly DiscoveredMcp[],\n enabled: readonly string[] | undefined,\n): readonly DiscoveredMcp[] {\n if (enabled === undefined)\n return discovered\n if (enabled.length === 0)\n return []\n const allow = new Set(enabled)\n return discovered.filter(d => allow.has(d.config.name))\n}\n","/**\n * Cross-provider model catalog — assembly + fuzzy filtering.\n *\n * Renderer-agnostic helpers consumed by `ModelPickerModal` (TUI) and any\n * future UI that wants a unified \"pick a model across all available\n * providers\" experience. Pure: no React, no I/O, no provider construction.\n *\n * The catalog is the flat union of every available provider's models,\n * each entry tagged with the provider it belongs to so the picker can\n * re-issue a `Picked` tuple `{ providerKey, modelId }` on commit.\n */\n\nimport type { ProviderAuth, ProviderKey } from './auth'\nimport type { ModelInfo } from './providers'\n\n/** A model entry as displayed in the cross-provider picker. */\nexport interface CatalogEntry {\n providerKey: ProviderKey\n providerLabel: string\n model: ModelInfo\n /**\n * Pre-computed lowercase corpus for substring search across the\n * provider key, label, model id, and display name. Built once at\n * catalog-assembly time so filtering on every keystroke is\n * O(catalogSize × queryLength), not O(catalogSize × fieldCount ×\n * lowercase-overhead × queryLength).\n */\n searchCorpus: string\n}\n\n/**\n * Build the unified catalog from a list of available providers.\n *\n * Provider order is preserved (callers typically pass the picker order\n * — alphabetical, auth-detection order, etc.); model order inside each\n * provider matches whatever `modelsFor` returns. The current selection\n * (when set) is bubbled to the top of its provider's section so it\n * shows first without disturbing relative ordering elsewhere.\n *\n * `modelsFor` is injected (not imported from `./providers`) so the same\n * helper works with hosts that supply their own model resolver via\n * `ResolvedConfig.modelsFor`.\n */\nexport function buildModelCatalog(opts: {\n providers: readonly ProviderAuth[]\n modelsFor: (key: ProviderKey) => readonly ModelInfo[]\n /** Optional currently-selected pair — promoted to the top of its provider group. */\n current?: { providerKey: ProviderKey, modelId: string } | null\n}): CatalogEntry[] {\n const entries: CatalogEntry[] = []\n for (const provider of opts.providers) {\n const models = opts.modelsFor(provider.key)\n if (models.length === 0)\n continue\n // Promote the currently-selected model to position 0 inside its\n // own provider section so the picker doesn't bury the user's\n // active row at the bottom of a long provider list.\n let ordered: readonly ModelInfo[] = models\n if (opts.current?.providerKey === provider.key) {\n const idx = models.findIndex(m => m.id === opts.current?.modelId)\n if (idx > 0) {\n const next = models.slice()\n const [active] = next.splice(idx, 1)\n next.unshift(active)\n ordered = next\n }\n }\n for (const model of ordered) {\n entries.push({\n providerKey: provider.key,\n providerLabel: provider.label,\n model,\n searchCorpus: buildSearchCorpus(provider, model),\n })\n }\n }\n return entries\n}\n\n/**\n * Filter `catalog` by a user query. Empty / whitespace-only queries\n * pass everything through unchanged (`O(1)` short-circuit). Multi-term\n * queries (space-separated) require EVERY term to appear somewhere in\n * the entry's search corpus — so `\"claude opus\"` matches `claude-opus-4`\n * regardless of how the words are interleaved with provider names.\n *\n * Match is case-insensitive (the corpus is pre-lowercased; the query\n * is lowercased once per call).\n */\nexport function filterModelCatalog(\n catalog: readonly CatalogEntry[],\n query: string,\n): CatalogEntry[] {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return catalog.slice()\n const terms = trimmed.split(/\\s+/)\n return catalog.filter(entry => terms.every(t => entry.searchCorpus.includes(t)))\n}\n\n/**\n * Find a catalog entry's index by its `{providerKey, modelId}` tuple.\n * Returns `-1` when not present. Useful when re-rendering the picker\n * (a query just narrowed the list, where did the selection land?).\n */\nexport function indexOfEntry(\n catalog: readonly CatalogEntry[],\n target: { providerKey: ProviderKey, modelId: string } | null | undefined,\n): number {\n if (!target)\n return -1\n return catalog.findIndex(\n e => e.providerKey === target.providerKey && e.model.id === target.modelId,\n )\n}\n\nfunction buildSearchCorpus(provider: ProviderAuth, model: ModelInfo): string {\n const parts = [\n provider.key,\n provider.label,\n model.id,\n model.name ?? '',\n model.provider ?? '',\n ]\n return parts.join(' ').toLowerCase()\n}\n","/**\n * OAuth login flow exposed to the TUI.\n *\n * Driven by each {@link ProviderDescriptor}'s `oauthProvider` field. Built-in\n * descriptors wire pi-ai's `anthropicOAuthProvider` and `openaiCodexOAuthProvider`,\n * but hosts can pass any object conforming to `OAuthProviderInterface` — the\n * TUI is implementation-agnostic past the descriptor boundary.\n */\n\nimport type {\n OAuthCredentials,\n OAuthDeviceCodeInfo,\n OAuthLoginCallbacks,\n OAuthPrompt,\n OAuthSelectPrompt,\n} from '@earendil-works/pi-ai/oauth'\nimport type { ProviderDescriptor } from './providers'\nimport { tryOpenBrowser } from './browser'\n\nexport type {\n OAuthDeviceCodeInfo,\n OAuthPrompt,\n OAuthSelectOption,\n OAuthSelectPrompt,\n} from '@earendil-works/pi-ai/oauth'\n\nexport function supportsOAuth(descriptor: ProviderDescriptor): boolean {\n return descriptor.oauthProvider !== undefined\n}\n\n/** True when the provider's OAuth flow needs the user to paste a code back into the TUI (no loopback callback). */\nexport function oauthUsesManualCodePaste(descriptor: ProviderDescriptor): boolean {\n return descriptor.oauthProvider?.usesCallbackServer === false\n}\n\nexport interface OAuthFlowOptions {\n /** Called when the provider emits its login URL — typically right after the callback server starts. */\n onUrl: (url: string, instructions?: string) => void\n /**\n * Called when the provider needs a free-form input from the user (paste-the-code flows,\n * step-up confirmations, etc.). Mirrors pi-ai's `OAuthLoginCallbacks.onPrompt` verbatim\n * so callers don't lose `placeholder` / `allowEmpty` hints. Required for any provider\n * with `usesCallbackServer: false` (e.g. Anthropic Claude Pro/Max).\n */\n onPrompt?: (prompt: OAuthPrompt) => Promise<string>\n /**\n * Called when the provider initiates a device-code flow (e.g. GitHub Copilot).\n * Surface `userCode` + `verificationUri` so the user can complete the flow in\n * the browser. Required by pi-ai's `OAuthLoginCallbacks` since 0.75.5 — built-in\n * Anthropic / OpenAI Codex providers don't fire it today, but third-party\n * descriptors can.\n */\n onDeviceCode?: (info: OAuthDeviceCodeInfo) => void\n /**\n * Called when the provider needs the user to pick between multiple flow options\n * (e.g. browser vs device-code). Return the chosen option `id`, or `undefined`\n * to cancel. Required by pi-ai's `OAuthLoginCallbacks` since 0.75.5.\n */\n onSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>\n /** Called with each progress message from the OAuth flow (token exchange, etc.). */\n onProgress?: (message: string) => void\n /** Abort the in-flight login (e.g. user pressed esc). */\n signal?: AbortSignal\n}\n\n/**\n * Run the OAuth login flow for a provider.\n *\n * Returns the OAuth credentials on success; caller persists them via\n * `setProviderCredential(dataDir, descriptor, { kind: 'oauth', ...credentials })`.\n * Throws when the descriptor has no `oauthProvider` configured.\n */\nexport async function runOAuthLogin(\n descriptor: ProviderDescriptor,\n options: OAuthFlowOptions,\n): Promise<OAuthCredentials> {\n if (!descriptor.oauthProvider) {\n throw new Error(\n `OAuth not supported for ${descriptor.label} (${descriptor.key}) — use an API key instead.`,\n )\n }\n\n const callbacks: OAuthLoginCallbacks = {\n onAuth: (info) => {\n options.onUrl(info.url, info.instructions)\n // Best-effort browser launch. If `open`/`xdg-open` isn't available, the\n // user can still click the URL from the TUI — the callback server is\n // already listening either way.\n void tryOpenBrowser(info.url)\n },\n onDeviceCode: (info) => {\n if (options.onDeviceCode) {\n options.onDeviceCode(info)\n return\n }\n // No handler wired — built-in providers (Anthropic, OpenAI Codex) never\n // hit this path, so it's only reachable for third-party descriptors.\n // Throwing here would crash mid-flow; surface to progress + browser\n // launch instead so the user has *some* path forward.\n options.onProgress?.(\n `Device code: ${info.userCode} — open ${info.verificationUri}`,\n )\n void tryOpenBrowser(info.verificationUri)\n },\n onPrompt: async (prompt) => {\n if (!options.onPrompt) {\n throw new Error(\n `OAuth provider \"${descriptor.label}\" requested user input (\"${prompt.message}\") but no onPrompt handler is wired.`,\n )\n }\n return options.onPrompt(prompt)\n },\n onSelect: async (prompt) => {\n if (options.onSelect)\n return options.onSelect(prompt)\n // Same fallback rationale as `onDeviceCode`. Default to the first option\n // so providers that gate the flow behind a choice don't deadlock the TUI.\n return prompt.options[0]?.id\n },\n onProgress: options.onProgress,\n signal: options.signal,\n }\n\n return descriptor.oauthProvider.login(callbacks)\n}\n","/**\n * Manual OAuth redirect-URL handoff.\n *\n * Users who run zidane over SSH (or behind a proxy / firewall that blocks\n * loopback) can't have the local browser hit the in-process callback server.\n * They CAN, however, copy the URL their browser was redirected to —\n * `http://127.0.0.1:<port>/callback?code=...&state=...` — and paste it back\n * into the TUI.\n *\n * The trick: that URL IS the callback our local server is listening on. We\n * just `fetch()` it ourselves. The server runs in the same process; the\n * request hits its handler exactly as if a real browser had arrived, the\n * OAuth promise (`waitForCode` for pi-ai providers, `startOAuthCallback` for\n * MCP) resolves through the normal happy path, and the upstream flow\n * continues uninterrupted. No new code path inside the OAuth state machine.\n *\n * Defense in depth: we reject anything that isn't a loopback URL — fetching\n * an arbitrary user-pasted URL from inside the agent process would be a\n * trivial SSRF.\n */\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1', '[::1]'])\n\nexport interface FetchOAuthRedirectResult {\n /** Status code from the callback server. 2xx = success. */\n status: number\n /** Best-effort message extracted from the response body (HTML title or trimmed text). */\n message?: string\n}\n\n/**\n * Treat `pasted` as a callback-URL paste. Validates it's a loopback URL,\n * fires a GET, returns the status.\n *\n * Throws when:\n * - `pasted` doesn't parse as a URL.\n * - The URL host isn't loopback (rejects SSRF).\n * - The fetch errors out (network, timeout).\n *\n * Returns success/failure for non-2xx responses; the caller decides whether\n * a 4xx is fatal (state mismatch is a 400 the user can retry from).\n */\nexport async function fetchOAuthRedirect(\n pasted: string,\n options: { signal?: AbortSignal, timeoutMs?: number } = {},\n): Promise<FetchOAuthRedirectResult> {\n const trimmed = pasted.trim()\n if (!trimmed)\n throw new Error('Paste the redirect URL from your browser.')\n\n let url: URL\n try {\n url = new URL(trimmed)\n }\n catch {\n throw new Error('That doesn\\'t look like a URL. Paste the full address from your browser.')\n }\n\n // Strip brackets that some browsers add around IPv6 hosts so the\n // comparison hits LOOPBACK_HOSTS regardless of textual form.\n const host = url.hostname.replace(/^\\[|\\]$/g, '')\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n `Expected a loopback URL (127.0.0.1 / localhost), got \"${url.hostname}\". The browser should have redirected to a localhost address.`,\n )\n }\n\n // Short timeout: the callback server is in-process, so any meaningful\n // delay means it's gone (closed by a prior abort) or never started.\n const timeoutMs = options.timeoutMs ?? 5000\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n options.signal?.addEventListener('abort', () => ac.abort(), { once: true })\n\n let response: Response\n try {\n response = await fetch(url.toString(), { signal: ac.signal })\n }\n catch (err) {\n if ((err as Error).name === 'AbortError')\n throw new Error('No response from the local callback server — was the login already cancelled?')\n throw new Error(`Could not reach the local callback server: ${(err as Error).message}`)\n }\n finally {\n clearTimeout(timer)\n }\n\n const bodyText = await response.text().catch(() => '')\n return { status: response.status, message: extractMessage(bodyText) }\n}\n\n/**\n * Pick the most useful one-line message out of the callback server's HTML\n * response. pi-ai uses `<h1>` for the heading; the MCP callback uses\n * a leading `<p>`. We try both, then fall back to a stripped snippet.\n */\nfunction extractMessage(html: string): string | undefined {\n if (!html)\n return undefined\n const h1 = /<h1[^>]*>([\\s\\S]*?)<\\/h1>/i.exec(html)?.[1]\n if (h1)\n return stripTags(h1).trim() || undefined\n const p = /<p[^>]*>([\\s\\S]*?)<\\/p>/i.exec(html)?.[1]\n if (p)\n return stripTags(p).trim() || undefined\n return undefined\n}\n\nfunction stripTags(s: string): string {\n return s.replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#39;/g, '\\'')\n}\n","/**\n * @-completion path display formatter.\n *\n * The `@`-popover walks the project from the git root so the user sees\n * the whole tree regardless of where they launched the TUI. The\n * agent's CWD-resolving tools (`read_file`, `glob`, `edit`, …) read\n * relative paths against `process.cwd()`, which is frequently a\n * subdirectory of that root. Emitting the discovery's raw\n * project-root-relative path into the prompt produces a `<cwd>/<rel>`\n * miss — the bug this helper exists to close.\n *\n * Rule:\n *\n * - File at `cwd` itself → `.`.\n * - File under `cwd` (subtree) → forward-slashed CWD-relative,\n * no `..`.\n * - File above `cwd` but still inside the project → CWD-relative\n * with `..` traversal (e.g. `../EDIT_THIS.md`). Shorter and more\n * scannable than absolute, and the agent's tools resolve both.\n * - File outside the project (input was passed absolute) → returned\n * verbatim. Inputs that arrive as project-root-relative strings\n * are always inside the project by construction, so this branch\n * only kicks in when a direct caller hands the function an\n * absolute target.\n *\n * Pure, no I/O, deterministic.\n */\n\nimport { isAbsolute, posix, relative, resolve, sep } from 'node:path'\n\n/**\n * Convert a project-root-relative path (as emitted by `listProjectFiles`)\n * into the form the agent's tools — and the user — will actually find\n * given the current `cwd`.\n *\n * Inputs:\n * - `projectRelativePath` — forward-slashed, no leading slash. Returned\n * verbatim when it's empty or already absolute (defensive — the\n * absolute-input branch is the de-facto \"outside the project\"\n * escape; the catalog itself never emits absolute paths).\n * - `projectRoot` — absolute path of the discovery anchor.\n * - `cwd` — absolute path of `process.cwd()` at the moment of\n * insertion.\n *\n * Output is always either an absolute path (only when the input arrived\n * absolute) OR a CWD-relative path. The relative form may include\n * `../` segments when the target sits above `cwd` within the project,\n * which keeps cross-tree references short and scannable\n * (e.g. `../EDIT_THIS.md` instead of `/Users/.../zidane/EDIT_THIS.md`).\n */\nexport function formatPathForCwd(\n projectRelativePath: string,\n projectRoot: string,\n cwd: string,\n): string {\n if (projectRelativePath.length === 0)\n return projectRelativePath\n if (isAbsolute(projectRelativePath))\n return projectRelativePath\n const target = resolve(projectRoot, projectRelativePath)\n const here = resolve(cwd)\n if (target === here)\n return '.'\n const rel = relative(here, target)\n if (rel.length === 0)\n return '.'\n return sep === '/' ? rel : rel.split(sep).join(posix.sep)\n}\n","/**\n * Pure string + reference math for rendering a submitted prompt with chip\n * pills around completion references. Renderer-agnostic — the TUI walks\n * the segments into OpenTUI `<text>` nodes, a GUI walks them into JSX\n * spans + `<span class=\"chip\">` pills. No layout engine assumptions.\n */\n\n/**\n * Highlight span — half-open `[start, end)` over the source string.\n *\n * Offsets are JS string indices (UTF-16 code units) — the same convention used\n * everywhere else in the chat layer (`text[i]`, `m.index`, `String.slice`).\n * Surrogate pairs that straddle a span boundary would render malformed, but\n * realistic prompts in this surface (terminal text + ASCII triggers) keep that\n * risk theoretical; callers feeding emoji-heavy content should normalize to\n * codepoint walks upstream.\n */\nexport interface PromptSegmentRef {\n start: number\n end: number\n /** Provider id tagging this span (`'skills'`, `'files'`, …). Free-form. */\n providerId: string\n}\n\n/**\n * One atomic unit emitted by {@link splitPromptSegments}. Plain segments\n * are word-sized so wraps land cleanly between words; chip segments\n * carry the source `providerId` (`'skills'`, `'files'`, …) so renderers\n * can pick per-kind colors without re-walking the ref list.\n */\nexport type PromptSegment\n = | { kind: 'plain', text: string }\n | { kind: 'chip', text: string, providerId: string }\n\n/**\n * Split a prompt buffer into word-sized atomic segments suitable for a\n * flex-row + flex-wrap renderer (TUI) or a `display: inline` flow with\n * inline-block chips (GUI). Each chip becomes one segment (atomic —\n * never broken across rows); each plain run is split into \"word +\n * trailing space\" units so wraps land at clean word boundaries.\n *\n * Robust to:\n * - Overlapping refs — sorted by start; later refs that overlap are\n * dropped via the first-wins rule.\n * - Out-of-bounds refs — dropped entirely when `end > text.length` or\n * `start >= text.length`. Partial clipping would silently truncate\n * a chip's label; the caller is in a better position to surface the\n * mismatch (typically a stale `refs` array referencing a previous text).\n * - Whitespace-only plain runs — emitted as their own plain segment\n * so chip-adjacent-to-chip cases keep the original spacing.\n *\n * Word splitter rationale: `\\S+\\s*` keeps trailing whitespace attached\n * to its preceding word so wrap boundaries land between words (cleanly).\n * A leading-whitespace-only segment is captured by `\\s+` so we don't\n * drop it entirely when the plain run starts with a space.\n */\nexport function splitPromptSegments(\n text: string,\n refs: readonly PromptSegmentRef[],\n): PromptSegment[] {\n const sorted = [...refs]\n .filter(r => r.end > r.start && r.start < text.length && r.end <= text.length)\n .sort((a, b) => a.start - b.start)\n const out: PromptSegment[] = []\n let cursor = 0\n for (const ref of sorted) {\n if (ref.start < cursor)\n continue // overlapping with a previous ref — drop\n if (ref.start > cursor) {\n const matches = text.slice(cursor, ref.start).match(/\\S+\\s*|\\s+/g) ?? []\n for (const m of matches)\n out.push({ kind: 'plain', text: m })\n }\n out.push({ kind: 'chip', text: text.slice(ref.start, ref.end), providerId: ref.providerId })\n cursor = ref.end\n }\n if (cursor < text.length) {\n const matches = text.slice(cursor).match(/\\S+\\s*|\\s+/g) ?? []\n for (const m of matches)\n out.push({ kind: 'plain', text: m })\n }\n return out\n}\n","/**\n * Lightweight shell command parser for safelist enforcement.\n *\n * Extracts the head token (program name) of every command in a bash\n * command string — including chains (`&&`, `||`, `;`), pipes (`|`),\n * subshells (`(…)`), and command substitutions (`$(…)` and backticks).\n *\n * Handles quoting (`'…'`, `\"…\"`, `\\x`), variable assignments\n * (`NAME=val`), I/O redirection, and the `!` negation prefix.\n *\n * Returns `null` when parsing fails so callers can fall back to\n * prompting (safe default).\n */\n\nexport function extractCommandHeads(command: string): string[] | null {\n try {\n const p = new ShellParser(command)\n p.list()\n return p.heads\n }\n catch {\n return null\n }\n}\n\n// Characters that unconditionally end an unquoted word.\nconst META = new Set(' \\t\\n\\r;|&()<>'.split(''))\n\nclass ShellParser {\n readonly heads: string[] = []\n private i = 0\n\n constructor(private readonly s: string) {}\n\n // -- public entry point ------------------------------------------------\n\n /** Command list — pipelines separated by `;`, `&&`, `||`, `\\n`, `&`. */\n list(closer?: string): void {\n for (;;) {\n this.ws()\n if (this.end(closer))\n return\n\n this.pipeline(closer)\n\n this.ws()\n if (this.end(closer))\n return\n const c = this.c()\n if (c === ';' || c === '\\n' || c === '\\r') { this.i++; continue }\n if (this.is('&&') || this.is('||')) { this.i += 2; continue }\n if (c === '&') { this.i++; continue }\n break\n }\n }\n\n // -- grammar -----------------------------------------------------------\n\n /** Pipeline — simple commands separated by `|` (not `||`). */\n private pipeline(closer?: string): void {\n this.cmd(closer)\n for (;;) {\n this.ws()\n if (this.c() === '|' && !this.is('||')) { this.i++; this.cmd(closer) }\n else {\n break\n }\n }\n }\n\n /** Simple command — extract head, skip remaining arguments. */\n private cmd(closer?: string): void {\n this.ws()\n if (this.i >= this.s.length)\n return\n if (closer && this.c() === closer)\n return\n\n // Subshell.\n if (this.c() === '(') {\n this.i++\n this.list(')')\n return\n }\n\n // `!` negation prefix — skip when followed by whitespace.\n if (this.c() === '!' && this.i + 1 < this.s.length\n && (this.s[this.i + 1] === ' ' || this.s[this.i + 1] === '\\t')) {\n this.i++; this.ws()\n }\n\n // Read tokens, skipping leading variable assignments (NAME=val).\n while (this.i < this.s.length && !this.sep(closer)) {\n if (this.c() === '(') {\n this.i++\n this.list(')')\n break\n }\n const w = this.word()\n if (!w)\n break\n if (isAssignment(w)) { this.ws(); continue }\n this.heads.push(w)\n break\n }\n\n this.tail(closer)\n }\n\n // -- word-level --------------------------------------------------------\n\n /** Read one shell word, handling quoting and substitutions. */\n private word(): string {\n let out = ''\n while (this.i < this.s.length) {\n const c = this.c()\n if (META.has(c))\n break\n\n if (c === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (c === '\\'') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] !== '\\'') out += this.s[this.i++]\n if (this.i < this.s.length)\n this.i++\n continue\n }\n if (c === '\"') { out += this.dquote(); continue }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (c === '`') { this.btick(); continue }\n out += c; this.i++\n }\n return out\n }\n\n /** Double-quoted string — returns literal chars, recurses into substitutions. */\n private dquote(): string {\n this.i++\n let out = ''\n while (this.i < this.s.length && this.s[this.i] !== '\"') {\n if (this.s[this.i] === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (this.s[this.i] === '`') { this.btick(); continue }\n out += this.s[this.i++]\n }\n if (this.i < this.s.length)\n this.i++\n return out\n }\n\n /** Backtick substitution — creates a sub-parser for the inner content. */\n private btick(): void {\n this.i++\n const close = this.s.indexOf('`', this.i)\n const inner = close < 0 ? this.s.slice(this.i) : this.s.slice(this.i, close)\n const sub = new ShellParser(inner)\n sub.list()\n this.heads.push(...sub.heads)\n this.i = close < 0 ? this.s.length : close + 1\n }\n\n // -- tail / redirections -----------------------------------------------\n\n /** Skip remaining arguments and redirections until the next command boundary. */\n private tail(closer?: string): void {\n while (this.i < this.s.length) {\n this.ws()\n if (this.i >= this.s.length || this.sep(closer))\n break\n if (this.redir())\n continue\n if (this.c() === '(') {\n this.i++\n this.list(')')\n continue\n }\n const w = this.word()\n if (!w)\n break\n }\n }\n\n /** Try to consume an I/O redirection operator + target. Returns true if consumed. */\n private redir(): boolean {\n const c = this.c()\n let yes = c === '<' || c === '>'\n if (!yes && c >= '0' && c <= '9' && this.i + 1 < this.s.length) {\n const n = this.s[this.i + 1]\n yes = n === '<' || n === '>'\n }\n if (!yes)\n return false\n\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n if (this.c() === '>') {\n this.i++\n if (this.c() === '>')\n this.i++\n if (this.c() === '&') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n return true\n }\n }\n else if (this.c() === '<') {\n this.i++\n if (this.c() === '<') {\n this.i++; if (this.c() === '-')\n this.i++\n }\n }\n this.ws()\n this.word()\n return true\n }\n\n // -- predicates --------------------------------------------------------\n\n /** True at a command boundary (`;`, `\\n`, `&`, `|`, `)`, or closer). */\n private sep(closer?: string): boolean {\n const c = this.c()\n if (c === ';' || c === '\\n' || c === '\\r' || c === '&' || c === ')')\n return true\n if (c === '|')\n return true\n if (closer && c === closer)\n return true\n return false\n }\n\n /** True when past end or at closer (closer is consumed). */\n private end(closer?: string): boolean {\n if (this.i >= this.s.length)\n return true\n if (closer && this.c() === closer) { this.i++; return true }\n return false\n }\n\n private c(): string { return this.s[this.i] ?? '' }\n private is(s: string): boolean { return this.s.startsWith(s, this.i) }\n private ws(): void {\n while (this.i < this.s.length && (this.s[this.i] === ' ' || this.s[this.i] === '\\t')) this.i++\n }\n}\n\n/** `NAME=…` where NAME is a valid shell identifier. */\nfunction isAssignment(w: string): boolean {\n const eq = w.indexOf('=')\n if (eq <= 0)\n return false\n return /^[A-Z_]\\w*$/i.test(w.slice(0, eq))\n}\n","/**\n * Safe-mode storage + matching for the TUI.\n *\n * Lives at `<dataDir>/projects.json` (default `~/.zidane/projects.json`). Each\n * top-level key is an absolute project directory; the value carries that\n * project's persisted tool-call `safelist`.\n *\n * ```json\n * {\n * \"/Users/me/proj-a\": { \"safelist\": [\"read_file\", \"shell:git:*\"] }\n * }\n * ```\n *\n * Two granularities for safelist entries:\n * - **bare tool name** — `\"read_file\"` matches every `read_file` call.\n * - **tool + first-arg token + wildcard** — `\"shell:git:*\"` matches `shell`\n * calls whose primary string argument starts with the token `git`\n * (followed by whitespace or end-of-string). Modelled on Claude Code's\n * `Bash(git:*)` syntax.\n *\n * A short list of read-only tools is **implicitly safe** without being\n * persisted — see {@link IMPLICITLY_SAFE_TOOLS}.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { extractCommandHeads } from './shell-parse'\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\n\nexport interface ProjectEntry {\n safelist?: string[]\n}\n\nexport type ProjectsFile = Record<string, ProjectEntry>\n\n/** Resolve `projects.json`'s on-disk path given the TUI data directory. */\nexport function projectsFilePath(dataDir: string): string {\n return resolve(dataDir, 'projects.json')\n}\n\nexport function readProjects(dataDir: string): ProjectsFile {\n const path = projectsFilePath(dataDir)\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as ProjectsFile\n }\n catch {\n // Corrupt file → treat as empty so the user can re-bootstrap.\n }\n return {}\n}\n\nexport function writeProjects(dataDir: string, file: ProjectsFile): void {\n writeFileAtomic(projectsFilePath(dataDir), JSON.stringify(file, null, 2), { ensureDir: true })\n}\n\n/**\n * Append `entry` to the safelist for `projectDir`, dedup-aware. Returns the\n * updated entry list (post-write) so callers can render it without re-reading.\n */\nexport function addToSafelist(\n dataDir: string,\n projectDir: string,\n entry: string,\n): readonly string[] {\n const file = readProjects(dataDir)\n const existing = file[projectDir]?.safelist ?? []\n if (existing.includes(entry))\n return existing\n const next = [...existing, entry]\n file[projectDir] = { ...file[projectDir], safelist: next }\n writeProjects(dataDir, file)\n return next\n}\n\n/** Read the safelist for one project. Returns `[]` for unknown projects. */\nexport function getSafelist(dataDir: string, projectDir: string): readonly string[] {\n return readProjects(dataDir)[projectDir]?.safelist ?? []\n}\n\n// ---------------------------------------------------------------------------\n// Matching\n// ---------------------------------------------------------------------------\n\n/**\n * Tools that always pass without prompting. Four categories:\n *\n * - **Pure reads** (`read_file`, `list_files`, `glob`, `grep`) — no\n * side effects on disk or the model's own state.\n * - **Interaction tools** (`ask_user`, `present_plan`) — the call\n * itself surfaces a TUI picker the user has to answer, so prompting\n * for approval first would mean \"approve this prompt before you see\n * the prompt\": redundant + breaks the flow. The picker IS the gate.\n * - **Todos** (`todowrite`, `todoread`) — write to a slot on\n * `session.metadata.todos` and nothing else. No shell, no disk, no\n * network. The model uses these for checkpointing every few steps;\n * a per-call approval prompt would drown the conversation in\n * interruptions that buy no safety since the tool's surface is\n * read/write on a metadata bag we already trust the agent with.\n * - **Skill activation** (`skills_use`) — mutates a small per-agent\n * Map of active skills and returns the activated skill's bundled\n * instructions (or releases an entry on `mode: \"deactivate\"`).\n * Pure state on the agent — no shell, no disk writes, no network.\n * Gating it would also defeat the \"model recovers from\n * `AgentToolNotAllowedError` by calling\n * `skills_use({ mode: \"deactivate\", name })`\" path: a stuck model\n * would have to ask the user permission to *unstick* itself on\n * every retry. `skills_read` and `skills_run_script` are\n * intentionally NOT on this list — the former touches disk and\n * the latter executes arbitrary scripts, so both keep going\n * through the regular gate.\n *\n * Users who want to gate any of these must disable safe-mode entirely\n * (or fork this list in their own embedding).\n */\nexport const IMPLICITLY_SAFE_TOOLS: readonly string[] = [\n 'read_file',\n 'list_files',\n 'glob',\n 'grep',\n 'ask_user',\n 'present_plan',\n 'todowrite',\n 'todoread',\n 'skills_use',\n]\n\n/** Common input keys carrying the \"primary argument\" we scope safelists on. */\nconst PRIMARY_ARG_KEYS = ['command', 'path', 'pattern', 'query'] as const\n\nfunction primaryArgValue(input: Record<string, unknown>): string {\n for (const key of PRIMARY_ARG_KEYS) {\n const v = input[key]\n if (typeof v === 'string' && v.length > 0)\n return v\n }\n return ''\n}\n\n/**\n * Extract the first whitespace-delimited token of the primary arg.\n * Leading whitespace is trimmed first so `\" git status\"` tokenizes to\n * `\"git\"`, not `\"\"` (an empty first element from `.split(/\\s+/)`).\n */\nfunction primaryArgToken(input: Record<string, unknown>): string {\n return primaryArgValue(input).trim().split(/\\s+/)[0] ?? ''\n}\n\n/**\n * Test whether a `{ tool, input }` pair is covered by one safelist entry.\n *\n * Supported entry shapes:\n * - `\"<tool>\"` — broad match on tool name.\n * - `\"<tool>:<token>:*\"` — match when the primary arg's first token\n * equals `<token>`.\n *\n * This function matches a **single command** against a **single entry**.\n * For shell commands that chain multiple programs (`&&`, `||`, `;`,\n * `$(…)`, etc.), use {@link isOnSafelist} which parses the full command\n * and checks every head token independently.\n *\n * Entries that don't fit either shape are ignored (forward-compat for\n * future pattern syntax — readers shouldn't choke on entries written\n * by a newer version of the TUI).\n */\nexport function matchesSafelistEntry(\n entry: string,\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (entry === tool)\n return true\n const sep = entry.indexOf(':')\n if (sep <= 0)\n return false\n if (entry.slice(0, sep) !== tool)\n return false\n const scope = entry.slice(sep + 1)\n if (scope.endsWith(':*'))\n return primaryArgToken(input) === scope.slice(0, -2)\n return false\n}\n\n/**\n * True when a call matches ANY entry in the project's safelist (or is\n * implicitly safe).\n *\n * For `shell` commands, the full command string is parsed into individual\n * commands (handling `&&`, `||`, `;`, `|`, `$(…)`, backticks, subshells).\n * Every command head must be covered by at least one safelist entry for\n * the call to pass. If parsing fails, returns `false` (prompt the user).\n */\nexport function isOnSafelist(\n entries: readonly string[],\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (IMPLICITLY_SAFE_TOOLS.includes(tool))\n return true\n\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n const heads = extractCommandHeads(cmd)\n if (heads === null)\n return false\n if (heads.length === 0)\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n return heads.every(head =>\n entries.some(e => matchesSafelistEntry(e, tool, { ...input, command: head })),\n )\n }\n\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n}\n\n/**\n * Suggest the safelist entry to write when the user picks \"accept and\n * remember\" for a `{ tool, input }`. Heuristic:\n *\n * - `shell` → scope by first command token (`shell:git:*`).\n * - anything else → bare tool name (broad).\n *\n * Returning a string ensures the UI always has a concrete entry to display\n * as the button label.\n */\nexport function suggestSafelistEntry(\n tool: string,\n input: Record<string, unknown>,\n): string {\n if (tool === 'shell') {\n const token = primaryArgToken(input)\n if (token)\n return `${tool}:${token}:*`\n }\n return tool\n}\n","/**\n * Safe-mode React context — bridges the agent's `tool:gate` / `mcp:tool:gate`\n * hooks (which run inside the harness loop) with the TUI's React tree (which\n * renders the approval picker).\n *\n * Flow:\n * 1. Hook handler in `app.tsx` calls `requestApproval(tool, input)`.\n * 2. Context appends a pending entry to the queue, returns a Promise.\n * 3. Picker renders the queue's head, calls `resolve(decision)` on pick.\n * 4. Head is shifted off the queue; hook handler proceeds with the decision.\n *\n * The queue is FIFO so parallel tool calls prompt in arrival order.\n *\n * Two contexts on purpose:\n * - `SafeModeQueueContext` carries the live queue. Consumers re-render on\n * every push/pop.\n * - `SafeModeActionsContext` carries `{ requestApproval, resolveHead,\n * denyAll }` and never changes identity, so callbacks that depend on\n * these don't re-bind every time a tool call enters or leaves the queue.\n */\n\nimport type { ReactNode } from 'react'\nimport { createContext, useCallback, useContext, useRef, useState } from 'react'\n\n/**\n * Outcome of an approval prompt. Four bulk decisions match the original\n * single-edit modal contract; `partial` is the multi-edit branch — the\n * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.\n *\n * `partial.mask` is 1:1 with the call's hunks (in input order): `true`\n * means apply, `false` means deny. The gate handler walks the mask to\n * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the\n * approved subset and the renderer reconstructs the full per-hunk view\n * from the `<edit-outcomes>` annotation appended to the tool result.\n */\nexport type ApprovalDecision\n = | 'accept-once'\n | 'accept-session'\n | 'accept-safelist'\n | 'deny'\n | { kind: 'partial', mask: readonly boolean[] }\n\n/**\n * Identifies the caller behind an approval prompt — the parent agent or\n * a specific subagent (`child-N`). The modal's right-side title pins the\n * label so the user knows which agent issued the call when subagents\n * bubble their gates up through the parent's hook bus.\n */\nexport type ApprovalOriginator\n = | { kind: 'parent' }\n | { kind: 'child', label: string }\n\nexport interface ApprovalRequest {\n id: string\n tool: string\n input: Record<string, unknown>\n resolve: (decision: ApprovalDecision) => void\n /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */\n originator?: ApprovalOriginator\n}\n\n/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */\nexport type RequestApproval = (\n tool: string,\n input: Record<string, unknown>,\n originator?: ApprovalOriginator,\n) => Promise<ApprovalDecision>\n\nexport interface SafeModeActions {\n /** Request a decision; resolves once the user picks. */\n requestApproval: RequestApproval\n /** Resolve the head and shift the queue forward. */\n resolveHead: (decision: ApprovalDecision) => void\n /** Resolve all pending with `deny`. Used on abort / hard exit. */\n denyAll: () => void\n}\n\nconst SafeModeQueueContext = createContext<readonly ApprovalRequest[]>([])\nconst SafeModeActionsContext = createContext<SafeModeActions | null>(null)\n\nlet approvalIdCounter = 0\nfunction nextApprovalId(): string {\n // Monotonic id is enough — uniqueness is scoped to a single TUI session\n // and the value never leaves the React tree. Avoids Date.now/Math.random\n // collisions inside tight parallel-tool batches.\n approvalIdCounter += 1\n return `approval-${approvalIdCounter}`\n}\n\n/**\n * Owns the queue + actions. Splits the value across two contexts so a queue\n * change doesn't invalidate every callback memo that closes over the actions.\n */\nexport function SafeModeProvider({ children }: { children: ReactNode }) {\n const [queue, setQueue] = useState<readonly ApprovalRequest[]>([])\n\n // `setQueue` is stable, so `useCallback([])` here gives genuine identity\n // stability — `requestApproval` is the same function reference for the\n // lifetime of the provider, which is what hook handlers need.\n const requestApproval = useCallback<RequestApproval>(\n (tool, input, originator) => new Promise<ApprovalDecision>((resolve) => {\n setQueue(prev => [\n ...prev,\n {\n id: nextApprovalId(),\n tool,\n input,\n resolve,\n ...(originator ? { originator } : {}),\n },\n ])\n }),\n [],\n )\n\n const resolveHead = useCallback((decision: ApprovalDecision) => {\n setQueue((prev) => {\n const [head, ...rest] = prev\n if (head)\n head.resolve(decision)\n return rest\n })\n }, [])\n\n const denyAll = useCallback(() => {\n setQueue((prev) => {\n for (const p of prev) p.resolve('deny')\n return []\n })\n }, [])\n\n // Actions object is stable across renders — its members are all `useCallback`'d\n // with `[]` deps. `useRef` captures the first-render identity and we hand the\n // same object out forever, so consumers calling `useSafeModeActions()` never\n // re-render purely because of safe-mode state.\n const actionsRef = useRef<SafeModeActions | null>(null)\n if (!actionsRef.current)\n actionsRef.current = { requestApproval, resolveHead, denyAll }\n\n return (\n <SafeModeActionsContext.Provider value={actionsRef.current}>\n <SafeModeQueueContext.Provider value={queue}>\n {children}\n </SafeModeQueueContext.Provider>\n </SafeModeActionsContext.Provider>\n )\n}\n\nexport function useSafeModeQueue(): readonly ApprovalRequest[] {\n return useContext(SafeModeQueueContext)\n}\n\nexport function useSafeModeActions(): SafeModeActions {\n const ctx = useContext(SafeModeActionsContext)\n if (!ctx)\n throw new Error('useSafeModeActions must be used inside <SafeModeProvider>')\n return ctx\n}\n","/**\n * Session export — render a `SessionData` as a clean Markdown or JSON\n * document and write it under the project's (or user's) `.{prefix}/sessions/`\n * directory.\n *\n * Anchor resolution mirrors the read-side `projectUserPaths` convention,\n * but for *writes* we pick a single canonical destination instead of a\n * fallback list: the closest enclosing git repository (walking up from\n * `cwd`), and the user home directory as the final fallback. This keeps\n * exports out of an unrelated project's `.{prefix}/` directory when the\n * user runs the TUI from a non-repo folder.\n *\n * Renderer-agnostic by design: every consumer (TUI, future GUI, SDK\n * scripts) can either call `writeSessionExport` for the canned\n * \"render + write\" combo or compose `renderSession` with a different\n * sink (clipboard, network, S3) without dragging in `node:fs`.\n */\n\nimport type { SessionData, SessionRun } from '../session'\nimport type { SessionContentBlock, SessionTurn, ToolResultContent, TurnUsage } from '../types'\nimport { existsSync, mkdirSync } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fmtTokens, shortId } from './format'\nimport { deriveSessionTitle } from './store'\n\n/** File format supported by the exporter. */\nexport type SessionExportFormat = 'markdown' | 'json'\n\n/**\n * Anchor for the resolved destination directory:\n * - `project` — the nearest enclosing git repository (`<repoRoot>/.{prefix}/sessions`)\n * - `home` — fallback when no repo was found (`~/.{prefix}/sessions`)\n */\nexport type SessionExportAnchor = 'project' | 'home'\n\n/** Resolved file destination for a session export. */\nexport interface SessionExportTarget {\n /** Absolute directory holding the file. May not exist yet — `writeSessionExport` creates it on demand. */\n dir: string\n /** Absolute path of the file (`dir/{id}.{ext}`). */\n filepath: string\n /** Where the destination was anchored. */\n anchor: SessionExportAnchor\n}\n\n/** Options shared by every resolve / write entry-point. */\ninterface ResolveOptions {\n /** Discovery cwd. Defaults to `process.cwd()`. */\n cwd?: string\n /** User home directory. Defaults to `os.homedir()`. */\n home?: string\n /**\n * Storage prefix. Leading dot tolerated (`zidane` and `.zidane` are\n * equivalent). Defaults to `'.zidane'`.\n */\n prefix?: string\n}\n\nconst DEFAULT_PREFIX = '.zidane'\n\n/**\n * Resolve the export target for a session id + format. Pure: no fs\n * write happens here, the caller decides whether to `existsSync` the\n * directory or hand the path to `writeSessionExport`.\n *\n * Anchor selection walks upward from `cwd` looking for a `.git` entry;\n * the first hit anchors to that repo's root (`project`). When the walk\n * exits without finding `.git`, the destination anchors to `home`.\n *\n * @throws RangeError when `sessionId` would resolve to an empty / `..`\n * filename — defensive guard against malicious or buggy callers\n * crossing into a sibling directory via the id.\n */\nexport function resolveSessionExportTarget(opts: {\n sessionId: string\n format: SessionExportFormat\n} & ResolveOptions): SessionExportTarget {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n const prefix = normalizePrefix(opts.prefix)\n const filename = exportFilename(opts.sessionId, opts.format)\n\n const repoRoot = findGitRoot(cwd)\n const anchor: SessionExportAnchor = repoRoot ? 'project' : 'home'\n const root = repoRoot ?? home\n const dir = resolve(root, `.${prefix}`, 'sessions')\n return { dir, filepath: join(dir, filename), anchor }\n}\n\n/**\n * Render a session into a string in the requested format.\n *\n * `markdown` produces a human-readable transcript: a YAML-free header\n * block with the title and a one-line `id · created · turns` summary,\n * a stats section (turns / runs / tokens / cost / status), then a\n * `## Conversation` block where each turn renders as `### N. role ·\n * run_X · ISO-date` with text, thinking, tool calls, and tool results\n * formatted as appropriate fenced blocks. Useful for sharing a session\n * with a teammate or pasting into an issue tracker.\n *\n * `json` returns a 2-space-indented, deterministic dump of the full\n * `SessionData` blob. Useful for re-importing into the store or for\n * post-hoc analysis tooling.\n */\nexport function renderSession(session: SessionData, format: SessionExportFormat): string {\n if (format === 'json')\n return `${JSON.stringify(session, null, 2)}\\n`\n return renderMarkdown(session)\n}\n\n/**\n * Render `session` and write the resulting bytes to disk. Returns the\n * resolved target so the caller can show the user where the file\n * landed. The parent directory is created on demand (`recursive: true`)\n * — first-time exports don't need any pre-flight setup.\n */\nexport async function writeSessionExport(\n opts: { session: SessionData, format: SessionExportFormat } & ResolveOptions,\n): Promise<SessionExportTarget> {\n const target = resolveSessionExportTarget({\n sessionId: opts.session.id,\n format: opts.format,\n cwd: opts.cwd,\n home: opts.home,\n prefix: opts.prefix,\n })\n const body = renderSession(opts.session, opts.format)\n // `mkdirSync` is synchronous to avoid two awaits on a hot path that's\n // I/O-bound anyway; `recursive: true` makes it a no-op when the dir\n // already exists, so we don't pay an `existsSync` round-trip.\n mkdirSync(target.dir, { recursive: true })\n await writeFile(target.filepath, body, 'utf8')\n return target\n}\n\n// ---------------------------------------------------------------------------\n// Markdown renderer\n// ---------------------------------------------------------------------------\n\n/**\n * Render a `SessionData` as a clean markdown transcript. Stable across\n * runs (no timestamps or random ids leak into the output beyond what\n * the session itself carries) — diffing two exports of the same\n * session is a no-op.\n */\nfunction renderMarkdown(session: SessionData): string {\n const title = deriveSessionTitle(session.turns, session.metadata)\n const userTurns = session.turns.filter(t => t.role === 'user').length\n const assistantTurns = session.turns.filter(t => t.role === 'assistant').length\n const usage = aggregateUsage(session.runs)\n const lines: string[] = []\n lines.push(`# ${escapeInline(title)}`)\n lines.push('')\n lines.push(`> Session \\`${shortId(session.id)}\\` · ${session.turns.length} turn${session.turns.length === 1 ? '' : 's'} · ${session.runs.length} run${session.runs.length === 1 ? '' : 's'}`)\n lines.push('')\n lines.push('## Metadata')\n lines.push('')\n lines.push(`- **id**: \\`${session.id}\\``)\n lines.push(`- **created**: ${new Date(session.createdAt).toISOString()}`)\n lines.push(`- **updated**: ${new Date(session.updatedAt).toISOString()}`)\n lines.push(`- **status**: ${session.status}`)\n lines.push(`- **turns**: ${session.turns.length} (${userTurns} user · ${assistantTurns} assistant)`)\n lines.push(`- **runs**: ${session.runs.length}`)\n if (usage.total > 0) {\n const tokenBits: string[] = [\n `in ${fmtTokens(usage.input)}`,\n `out ${fmtTokens(usage.output)}`,\n ]\n if (usage.cacheRead > 0)\n tokenBits.push(`cached ${fmtTokens(usage.cacheRead)}`)\n lines.push(`- **tokens**: ${fmtTokens(usage.total)} (${tokenBits.join(' · ')})`)\n if (usage.cost > 0)\n lines.push(`- **cost**: $${usage.cost.toFixed(usage.cost < 0.01 ? 4 : 2)}`)\n }\n lines.push('')\n lines.push('## Conversation')\n lines.push('')\n\n if (session.turns.length === 0) {\n lines.push('_No turns recorded yet._')\n lines.push('')\n return `${lines.join('\\n')}\\n`\n }\n\n session.turns.forEach((turn, idx) => {\n lines.push(renderTurnHeader(turn, idx + 1))\n lines.push('')\n const body = renderTurnBody(turn)\n if (body) {\n lines.push(body)\n lines.push('')\n }\n })\n\n return `${lines.join('\\n').replace(/\\n+$/, '\\n')}\\n`\n}\n\n/** `### 1. user · run_1 · 2026-05-13T01:48:29.895Z` */\nfunction renderTurnHeader(turn: SessionTurn, index: number): string {\n const date = new Date(turn.createdAt).toISOString()\n const runFragment = turn.runId ? ` · \\`${turn.runId}\\`` : ''\n const usageFragment = turn.usage ? ` · ${formatTurnUsage(turn.usage)}` : ''\n return `### ${index}. ${turn.role}${runFragment} · ${date}${usageFragment}`\n}\n\n/** `in 12 · out 48 · cached 1.2k` — only emits non-zero buckets. */\nfunction formatTurnUsage(usage: TurnUsage): string {\n const parts: string[] = []\n if (usage.input > 0)\n parts.push(`in ${fmtTokens(usage.input)}`)\n if (usage.output > 0)\n parts.push(`out ${fmtTokens(usage.output)}`)\n if ((usage.cacheRead ?? 0) > 0)\n parts.push(`cached ${fmtTokens(usage.cacheRead ?? 0)}`)\n return parts.join(' · ')\n}\n\n/**\n * Render all blocks of a single turn into a markdown fragment.\n * Block ordering matches the on-disk turn so resume / replay semantics\n * are preserved: tool_call → tool_result pairs stay adjacent, thinking\n * blocks land right before the assistant text they precede.\n */\nfunction renderTurnBody(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n const chunk = renderBlock(block)\n if (chunk)\n parts.push(chunk)\n }\n return parts.join('\\n\\n')\n}\n\nfunction renderBlock(block: SessionContentBlock): string {\n switch (block.type) {\n case 'text':\n return block.text.trim() ? block.text.trim() : ''\n case 'thinking': {\n const text = block.text.trim()\n if (!text)\n return ''\n // Blockquote prefix on every line so consumers can fold / hide\n // thinking sections without breaking the surrounding markdown.\n const quoted = text.split('\\n').map(l => `> ${l}`).join('\\n')\n return `> **thinking**\\n>\\n${quoted}`\n }\n case 'tool_call': {\n const args = JSON.stringify(block.input, null, 2)\n return [\n `**Tool call** \\`${block.name}\\` · id \\`${block.id}\\``,\n '',\n '```json',\n args,\n '```',\n ].join('\\n')\n }\n case 'tool_result': {\n const header = block.isError\n ? `**Tool result** ✗ error · id \\`${block.callId}\\``\n : `**Tool result** · id \\`${block.callId}\\``\n const output = renderToolOutput(block.output)\n return [header, '', output].join('\\n')\n }\n case 'image':\n // Image data could be megabytes of base64; reference it by media type\n // rather than inline. A future viewer that wants to round-trip can\n // pull the original bytes from the on-disk session.\n return `_[image · ${block.mediaType}]_`\n case 'compact-summary': {\n // Boundary card in the markdown export — mirrors the TUI's\n // CompactSummaryBlock so a reader sees what happened at the\n // compaction point. The detailed turn-by-turn history before the\n // marker is still in the export (compaction never deletes from\n // disk), so the reader can compare summary vs source.\n const count = block.replacesTurnIds.length\n return [\n `**Compaction summary** · ${count} turn${count === 1 ? '' : 's'} compacted · model \\`${block.model}\\``,\n '',\n block.summary.trim(),\n ].join('\\n')\n }\n default:\n return ''\n }\n}\n\n/**\n * Tool output is either a flat string (the common case) or a structured\n * array of `ToolResultContent` (multimodal tools — screenshots, charts).\n * Strings render as a fenced code block; structured arrays render\n * their text blocks inline with `[image · …]` placeholders for the rest.\n */\nfunction renderToolOutput(output: string | ToolResultContent[]): string {\n if (typeof output === 'string') {\n const fence = pickFence(output)\n return `${fence}\\n${output}\\n${fence}`\n }\n const segments: string[] = []\n for (const piece of output) {\n if (piece.type === 'text') {\n const fence = pickFence(piece.text)\n segments.push(`${fence}\\n${piece.text}\\n${fence}`)\n }\n else {\n segments.push(`_[image · ${piece.mediaType}]_`)\n }\n }\n return segments.join('\\n\\n')\n}\n\n/**\n * Pick a fence (`\\`\\`\\`` or longer) that doesn't collide with any\n * backtick run inside `content`. The CommonMark rule is \"a fenced\n * block ends at a fence of the same length or longer made of the same\n * char\" — choosing a fence strictly longer than the longest run inside\n * the body avoids accidental termination.\n */\nfunction pickFence(content: string): string {\n let longestRun = 0\n let current = 0\n for (const ch of content) {\n if (ch === '`') {\n current++\n if (current > longestRun)\n longestRun = current\n }\n else {\n current = 0\n }\n }\n const len = Math.max(3, longestRun + 1)\n return '`'.repeat(len)\n}\n\n/** Strip control characters from a single-line inline (e.g. the title). */\nfunction escapeInline(s: string): string {\n return s.replace(/[\\r\\n]+/g, ' ').trim()\n}\n\n// ---------------------------------------------------------------------------\n// Path resolution helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Walk parents of `start` looking for a `.git` entry (file or\n * directory — `.git` is a file inside git worktrees). Returns the\n * directory holding `.git`, or `null` at the filesystem root.\n */\nfunction findGitRoot(start: string): string | null {\n let dir = resolve(start)\n // Hard cap as an escape hatch on pathological cwd values\n // (`mount --bind` cycles, etc.); 64 levels is well above what any\n // real-world checkout produces.\n for (let i = 0; i < 64; i++) {\n if (existsSync(join(dir, '.git')))\n return dir\n const parent = dirname(dir)\n if (parent === dir)\n return null\n dir = parent\n }\n return null\n}\n\n/** Trim a leading dot from `prefix`. */\nfunction normalizePrefix(prefix: string | undefined): string {\n return (prefix ?? DEFAULT_PREFIX).replace(/^\\./, '')\n}\n\n/**\n * Compose the on-disk filename for `(sessionId, format)`. Rejects\n * empty ids and path-traversal-ish values so the caller can't escape\n * `.{prefix}/sessions/`.\n */\nfunction exportFilename(sessionId: string, format: SessionExportFormat): string {\n const ext = format === 'json' ? 'json' : 'md'\n const cleaned = sessionId.trim()\n if (!cleaned || cleaned.includes('/') || cleaned.includes('\\\\') || cleaned === '.' || cleaned === '..')\n throw new RangeError(`Refusing to export session — invalid id \"${sessionId}\"`)\n return `${cleaned}.${ext}`\n}\n\n// ---------------------------------------------------------------------------\n// Usage aggregation (shared with the details modal in spirit but duplicated\n// here so the renderer stays self-contained — pulling it in from the modal\n// would invert the dependency direction)\n// ---------------------------------------------------------------------------\n\ninterface AggregatedUsage {\n input: number\n output: number\n cacheRead: number\n total: number\n cost: number\n}\n\nfunction aggregateUsage(runs: readonly SessionRun[]): AggregatedUsage {\n const acc = { input: 0, output: 0, cacheRead: 0, cost: 0 }\n for (const run of runs) {\n if (run.totalUsage) {\n acc.input += run.totalUsage.input ?? 0\n acc.output += run.totalUsage.output ?? 0\n acc.cacheRead += run.totalUsage.cacheRead ?? 0\n }\n else if (run.turnUsage) {\n for (const u of run.turnUsage) {\n acc.input += u.input ?? 0\n acc.output += u.output ?? 0\n acc.cacheRead += u.cacheRead ?? 0\n }\n }\n if (run.cost)\n acc.cost += run.cost\n }\n return { ...acc, total: acc.input + acc.output }\n}\n","/**\n * Skills discovery + persisted enabled-state for the chat layer.\n *\n * Walks project + storage-dir scan paths, returns each skill's parsed\n * metadata, and folds the user's enabled-toggle list into a `SkillsConfig`\n * the agent picks up at construction time.\n *\n * Pattern mirrors what `createAgent` already supports (`SkillsConfig.scan`\n * + `enabled`), so the agent receives the same shape it always did — the\n * chat layer just owns the discovery + UI plumbing.\n */\n\nimport type { SkillConfig, SkillsConfig, SourcedScanPath } from '../skills'\nimport { existsSync } from 'node:fs'\nimport { discoverSkills } from '../skills'\nimport { projectUserPaths } from './project-user-paths'\n\n/**\n * Resolve the default skill scan paths for a project. First-found wins on\n * collision; earlier entries take precedence.\n *\n * Search order — see {@link projectUserPaths}. Non-existent paths are\n * returned so downstream code can choose whether to create them;\n * `discoverSkills` itself skips missing dirs without error.\n */\nexport function defaultSkillScanPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): SourcedScanPath[] {\n return projectUserPaths({ subPath: 'skills', ...opts })\n}\n\n/**\n * Discover every skill reachable from the default scan paths. Returns\n * parsed `SkillConfig`s with `name`, `description`, frontmatter, and\n * lenient-load diagnostics. Pure I/O — does not activate or write.\n *\n * `signal` is forwarded to `discoverSkills` so the TUI's directory-watch\n * effect can cancel a long scan when the user switches `cwd` rapidly.\n *\n * Errors during parse are surfaced as `diagnostics` on the returned\n * skill, not thrown — keeps the picker usable even when a single\n * SKILL.md is malformed.\n */\nexport async function discoverProjectSkills(opts: {\n cwd?: string\n home?: string\n prefix?: string\n signal?: AbortSignal\n} = {}): Promise<SkillConfig[]> {\n const paths = defaultSkillScanPaths(opts).filter(p => existsSync(p.path))\n return discoverSkills(paths, opts.signal)\n}\n\n/**\n * Map a user-toggled enable list onto the format `createAgent` expects.\n *\n * Conventions:\n * - `enabled === undefined` → all discovered skills enabled (default).\n * - `enabled === []` → fully off; the agent will not scan or\n * inject the skills tools.\n * - `enabled === [names]` → allowlist; skills not in the list are\n * left out of the catalog.\n *\n * `scan` is the resolved scan-path list (passed through verbatim). Pass\n * `defaultSkillScanPaths()` for the standard project + user paths, or\n * supply a host-specific list.\n */\nexport function buildSkillsConfig(opts: {\n scan: SourcedScanPath[]\n enabled?: readonly string[]\n}): SkillsConfig {\n const scan = opts.scan.map(p => p.path)\n // Empty allowlist must disable the system outright — the loop checks for\n // `enabled === false || enabled.length === 0` to short-circuit catalog\n // assembly. Passing `[]` here used to leak through as \"no allowlist\n // entries, so include nothing\" which the loop treats as \"off\" anyway,\n // but being explicit prevents future loop refactors from drifting.\n if (opts.enabled !== undefined && opts.enabled.length === 0) {\n return { scan, enabled: false }\n }\n return {\n scan,\n ...(opts.enabled ? { enabled: [...opts.enabled] } : {}),\n }\n}\n","import type { Dispatch, SetStateAction } from 'react'\nimport type { TurnUsage } from '../types'\nimport type { Owner, StreamEvent } from './types'\nimport { useCallback, useMemo, useRef } from 'react'\n\n/**\n * Tick interval for the continuous drain loop. ~16ms aligns with the\n * 60fps renderer target so the typewriter cadence reveals 1 portion of\n * content per paint frame. The bucket-derived `chars-per-tick` decides\n * how much actually lands per tick — see {@link smoothCharsForTick}.\n */\nconst TICK_INTERVAL_MS = 16\n\n/**\n * Smooth-streaming display rate, in characters per second.\n *\n * Three-segment continuous ramp (no step functions, no cliff) so the\n * typewriter cadence never visibly \"jumps\" mid-stream:\n *\n * - {@link SMOOTH_BASE_CPS} — calm-state floor. ~200 CPS is a fast\n * typist's pace; comfortably readable without feeling like a\n * deliberate performance.\n * - {@link SMOOTH_BURST_CPS} at {@link SMOOTH_BURST_BACKLOG_CHARS} —\n * moderate catch-up speed once the buffer is one paragraph behind\n * the provider.\n * - {@link SMOOTH_MAX_CPS} at {@link SMOOTH_MAX_BACKLOG_CHARS} —\n * bounded ceiling. Past this we plateau at max-cps rather than\n * instant-draining; max-cps is already well above any human reading\n * rate so even a 10K-char dump completes in ~2.5s of fast typewriter\n * instead of a single-frame teleport.\n *\n * The previous failsafe (instant drain at 2K chars) was a literal\n * step from ~10 chars/frame to \"everything in one frame\" — the worst\n * jump in the smooth pipeline. The continued ramp eliminates it\n * without losing bounded latency, since CPS keeps growing with\n * backlog up to the cap.\n */\nconst SMOOTH_BASE_CPS = 200\nconst SMOOTH_BURST_CPS = 600\nconst SMOOTH_BURST_BACKLOG_CHARS = 400\nconst SMOOTH_MAX_CPS = 4000\nconst SMOOTH_MAX_BACKLOG_CHARS = 4000\n\nconst PARENT_OWNER: Owner = 'parent'\n\n// ---------------------------------------------------------------------------\n// Streaming-delta merging\n//\n// Each event is owned by either the parent agent or a specific subagent\n// (`childId`). Same-owner same-kind text/thinking chunks coalesce into one\n// growing event so React allocates one renderable per stream, not per delta.\n// ---------------------------------------------------------------------------\n\ninterface DeltaBucket {\n markdown: string\n thinking: string\n owner: Owner\n depth: number\n /** Last turnId seen for this bucket. Tagged onto new events when the bucket flushes. */\n turnId?: string\n /**\n * Fractional chars carried over from the previous smooth tick. The\n * tick computes an exact float (`cps * dt`), takes the integer part,\n * and stashes the leftover here so the next tick adds it back in.\n * This makes the long-run rate match the target CPS exactly instead\n * of being inflated by `Math.ceil` rounding on every tick.\n */\n smoothCarry: number\n}\n\nfunction emptyBucket(owner: Owner, depth: number): DeltaBucket {\n return { markdown: '', thinking: '', owner, depth, smoothCarry: 0 }\n}\n\nfunction applyBucket(prev: StreamEvent[], bucket: DeltaBucket): StreamEvent[] {\n let result = prev\n if (bucket.thinking)\n result = appendThinkingLines(result, bucket.thinking, bucket.owner, bucket.depth, bucket.turnId)\n if (bucket.markdown)\n result = appendMarkdownDelta(result, bucket.markdown, bucket.owner, bucket.depth, bucket.turnId)\n return result\n}\n\nfunction appendMarkdownDelta(prev: StreamEvent[], delta: string, owner: Owner, depth: number, turnId: string | undefined): StreamEvent[] {\n const last = prev[prev.length - 1]\n if (last && last.kind === 'markdown' && last.streaming && ownerOf(last) === owner) {\n const next = prev.slice(0, -1)\n next.push({ ...last, text: last.text + delta })\n return next\n }\n return [\n ...prev,\n tagEvent({ kind: 'markdown', text: delta, streaming: true }, owner, depth, turnId),\n ]\n}\n\nfunction appendThinkingLines(prev: StreamEvent[], delta: string, owner: Owner, depth: number, turnId: string | undefined): StreamEvent[] {\n const lines = delta.split('\\n')\n const result = [...prev]\n const last = result[result.length - 1]\n\n if (last && last.kind === 'thinking' && ownerOf(last) === owner) {\n result[result.length - 1] = { ...last, text: last.text + lines[0] }\n }\n else if (lines[0] || lines.length > 1) {\n result.push(tagEvent({ kind: 'thinking', text: lines[0] }, owner, depth, turnId))\n }\n\n for (let i = 1; i < lines.length; i++)\n result.push(tagEvent({ kind: 'thinking', text: lines[i] }, owner, depth, turnId))\n\n return result\n}\n\nfunction ownerOf(evt: StreamEvent): Owner {\n return evt.childId ?? PARENT_OWNER\n}\n\n/**\n * Stamp owner (parent vs subagent) + depth + optional `turnId` onto a\n * freshly-minted event so consumers can identify the producer and the\n * source turn. Parent-owned events leave `childId` / `depth` off to match\n * the prior shape; `turnId` is added whenever the caller provided one.\n */\nfunction tagEvent(evt: StreamEvent, owner: Owner, depth: number, turnId: string | undefined): StreamEvent {\n const withTurn = turnId ? { ...evt, turnId } : evt\n if (owner === PARENT_OWNER)\n return withTurn\n return { ...withTurn, childId: owner, depth }\n}\n\n/** Flip any trailing streaming markdown blocks (any owner) to finalized. */\nexport function finalizeStreamingMarkdown(events: StreamEvent[]): StreamEvent[] {\n let changed = false\n const next = events.map((e) => {\n if (e.kind === 'markdown' && e.streaming) {\n changed = true\n return { ...e, streaming: false }\n }\n return e\n })\n return changed ? next : events\n}\n\n/** Flip the trailing streaming markdown block for one specific owner. */\nexport function finalizeStreamingMarkdownForOwner(events: StreamEvent[], owner: Owner): StreamEvent[] {\n for (let i = events.length - 1; i >= 0; i--) {\n const e = events[i]\n if (e.kind !== 'markdown')\n continue\n if (!e.streaming)\n continue\n if (ownerOf(e) !== owner)\n continue\n const next = events.slice()\n next[i] = { ...e, streaming: false }\n return next\n }\n return events\n}\n\n// ---------------------------------------------------------------------------\n// Context-window math.\n// ---------------------------------------------------------------------------\n\n/**\n * Effective context size for a single turn.\n *\n * `usage.input` is misleading on its own when prompt caching is active: providers\n * (Anthropic, OpenRouter→Anthropic, Gemini) report `input` as the *new uncached*\n * tokens only — the cached prefix shows up in `cacheRead`, and newly-cached\n * tokens in `cacheCreation`. The model still saw all three buckets, so the real\n * context-window utilization is their sum.\n *\n * Non-caching providers leave `cacheRead`/`cacheCreation` undefined, so this\n * collapses to plain `input` for them.\n */\nexport function turnContextSize(usage: TurnUsage | undefined): number {\n if (!usage)\n return 0\n return (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheCreation ?? 0)\n}\n\n// ---------------------------------------------------------------------------\n// useStreamBuffer — per-owner stream-event accumulator with two drain modes.\n//\n// Provider deltas arrive at ~50Hz; flushing each one through React causes the\n// ScrollBox to recompute layout faster than the terminal can clear and\n// repaint, producing overdraw artifacts during scroll. We buffer per owner\n// (parent + each active subagent) and drain via a continuous ticker:\n//\n// - **smooth mode** (default — gated on {@link Settings.smoothStreaming}):\n// drip-feed text character-by-character at {@link SMOOTH_BASE_CPS}, with\n// an adaptive burst when the buffer falls behind. Reads as a typewriter\n// effect, masks bursty provider chunks behind a steady cadence, and\n// always finishes within one tick of the LLM completing because turn\n// boundaries (`flush` / `appendImmediate` / `flushAndUpdate`) drain\n// remaining content unconditionally.\n// - **batched mode** (off): drain everything per tick — equivalent to the\n// legacy \"one 32ms timeout per burst of deltas\" cadence, just expressed\n// against the same ticker.\n//\n// `getSmooth` is called on every tick so toggling the user setting at\n// runtime takes effect on the next drain without rebuilding the buffer.\n// ---------------------------------------------------------------------------\n\nexport interface StreamSource {\n /** Pass `undefined` / omit for parent-agent events. */\n childId?: string\n /** Nesting depth — 0 for parent, ≥ 1 for subagents. */\n depth?: number\n /**\n * `SessionTurn.id` this delta belongs to. Tagged onto new events so the\n * TUI's select-turn mode can group every event from one turn. Stored on\n * the active bucket and refreshed on every delta so a turn boundary\n * (`turn:after` flushes the bucket) doesn't carry stale ids forward.\n */\n turnId?: string\n}\n\nexport interface StreamBuffer {\n /** Queue a streaming delta for the next flush tick. */\n queueStreamDelta: (kind: 'markdown' | 'thinking', delta: string, source?: StreamSource) => void\n /** Drain pending deltas immediately, then append a non-streaming event. */\n appendImmediate: (evt: StreamEvent) => void\n /** Drain pending deltas immediately, then transform the event list. */\n flushAndUpdate: (update: (events: StreamEvent[]) => StreamEvent[]) => void\n /** Drain pending deltas without further transformation. */\n flush: () => void\n /** Cancel any pending flush and drop buffered deltas (on session teardown). */\n reset: () => void\n}\n\nexport interface StreamBufferOptions {\n /**\n * Returns `true` to drip-feed buffered text at the smooth typewriter\n * cadence, `false` to drain everything per tick (batched mode — the\n * legacy behaviour). Called on every tick, so flipping the user setting\n * takes effect on the next drain.\n */\n getSmooth?: () => boolean\n}\n\n/**\n * Target chars-per-second for the next smooth-mode tick given the\n * current bucket backlog. Two linear segments — base→burst over the\n * first {@link SMOOTH_BURST_BACKLOG_CHARS}, burst→max over the rest of\n * the ramp — so the function is continuous everywhere and plateaus\n * (rather than cliffs) once backlog passes {@link SMOOTH_MAX_BACKLOG_CHARS}.\n *\n * Exported for tests; the runtime path goes through\n * {@link smoothCharsForTick} which folds in the per-bucket fractional\n * remainder.\n */\nexport function smoothCpsForBacklog(backlog: number): number {\n if (backlog <= 0)\n return 0\n if (backlog >= SMOOTH_MAX_BACKLOG_CHARS)\n return SMOOTH_MAX_CPS\n if (backlog <= SMOOTH_BURST_BACKLOG_CHARS) {\n const t = backlog / SMOOTH_BURST_BACKLOG_CHARS\n return SMOOTH_BASE_CPS + t * (SMOOTH_BURST_CPS - SMOOTH_BASE_CPS)\n }\n const t = (backlog - SMOOTH_BURST_BACKLOG_CHARS)\n / (SMOOTH_MAX_BACKLOG_CHARS - SMOOTH_BURST_BACKLOG_CHARS)\n return SMOOTH_BURST_CPS + t * (SMOOTH_MAX_CPS - SMOOTH_BURST_CPS)\n}\n\n/**\n * Number of characters to drain on the next smooth-mode tick + the\n * fractional remainder to carry into the next tick.\n *\n * Combining the CPS curve with a fractional accumulator means the\n * effective draining rate matches the target CPS exactly. The legacy\n * `Math.ceil(cps * dt)` rounded UP on every tick — at 200 CPS / 16ms,\n * that's 4 chars/tick (= 250 CPS effective), a 25 % speed-up. Worse,\n * `ceil` produced visible step transitions (4 → 5 → 7 → 8 → 10 as\n * backlog grew) that read as the typewriter cadence jumping.\n */\nfunction smoothCharsForTick(\n backlog: number,\n carry: number,\n dtMs: number,\n): { take: number, carry: number } {\n if (backlog <= 0)\n return { take: 0, carry: 0 }\n const cps = smoothCpsForBacklog(backlog)\n const want = cps * dtMs / 1000 + carry\n const take = Math.floor(want)\n if (take <= 0)\n return { take: 0, carry: want }\n if (take >= backlog) {\n // Drained the bucket — drop the leftover. Holding it across a\n // bucket boundary would invent a head-start for the next stream.\n return { take: backlog, carry: 0 }\n }\n // Don't clamp `take` to 1: with `dtMs * cps / 1000 < 1`, the carry\n // accumulates over the next tick(s) and the char lands when math\n // says it should. The ticker stays alive because the bucket still\n // has content, so the next tick fires regardless.\n return { take, carry: want - take }\n}\n\n/**\n * Slice `n` chars off the front of `buf` without splitting a UTF-16\n * surrogate pair. Most LLM text is ASCII and never trips this; the guard\n * matters for emoji / extended planes where halving the pair would emit\n * an isolated half-character.\n */\nfunction takeChars(buf: string, n: number): { taken: string, rest: string } {\n if (buf.length <= n)\n return { taken: buf, rest: '' }\n let end = n\n const last = buf.charCodeAt(end - 1)\n // High surrogate at the slice boundary → pull the following low\n // surrogate in too. Going one char over budget on a single tick is\n // strictly better than emitting half a code point.\n if (last >= 0xD800 && last <= 0xDBFF && end < buf.length)\n end++\n return { taken: buf.slice(0, end), rest: buf.slice(end) }\n}\n\nexport function useStreamBuffer(\n setEvents: Dispatch<SetStateAction<StreamEvent[]>>,\n options?: StreamBufferOptions,\n): StreamBuffer {\n const bucketsRef = useRef<Map<Owner, DeltaBucket>>(new Map())\n const tickerRef = useRef<ReturnType<typeof setInterval> | null>(null)\n // Real wall-clock timestamp of the last tick. Feeds the dt argument\n // to `smoothCharsForTick` so the drain rate stays accurate even when\n // `setInterval` is late (event-loop congestion, GC pauses); without\n // this a 50ms-late tick would only commit ~16ms-worth of chars and\n // the visible cadence would stall.\n const lastTickMsRef = useRef<number>(0)\n // Pin the latest `getSmooth` callback inside a ref so the ticker closure\n // always sees the current setting without recreating the timer.\n const getSmoothRef = useRef<(() => boolean) | undefined>(options?.getSmooth)\n getSmoothRef.current = options?.getSmooth\n\n /**\n * Updaters queued while smooth-streaming was still draining backlog —\n * tool-call appends, finalize-markdown transforms, error events.\n * Applied in FIFO order at the END of `tick()` once every bucket is\n * empty, so the trailing typewriter characters land BEFORE the\n * follow-up event instead of being clobbered by an immediate drain.\n *\n * The buffer treats markdown deltas as the authoritative ordering\n * source: a tool call queued behind 100 buffered chars surfaces after\n * those 100 chars have typed out, preserving the visual stream order\n * the user is reading.\n */\n const pendingUpdatersRef = useRef<((events: StreamEvent[]) => StreamEvent[])[]>([])\n\n const stopTicker = useCallback(() => {\n if (tickerRef.current) {\n clearInterval(tickerRef.current)\n tickerRef.current = null\n }\n // Forget the last-tick stamp so the next ticker run starts from\n // the nominal interval instead of a stale (potentially seconds-old)\n // delta that would burst-drain the first frame.\n lastTickMsRef.current = 0\n }, [])\n\n /**\n * Has at least one bucket got unflushed *markdown* content?\n *\n * Thinking content is excluded: it always flushes in full on every\n * tick (see `tick()`), so its presence in a bucket only matters until\n * the next 16 ms cycle — never long enough to justify deferring an\n * end-of-turn updater.\n */\n const hasMarkdownBacklog = useCallback((): boolean => {\n for (const bucket of bucketsRef.current.values()) {\n if (bucket.markdown.length > 0)\n return true\n }\n return false\n }, [])\n\n /**\n * Drain every bucket in full and synchronously run any queued\n * post-drain updaters plus the caller-provided one. Used by the\n * fast paths below — `reset`, batched-mode `appendImmediate` /\n * `flushAndUpdate`, and smooth-mode calls where no markdown\n * backlog remains.\n */\n const drainNow = useCallback((updater?: (events: StreamEvent[]) => StreamEvent[]) => {\n stopTicker()\n const buckets = Array.from(bucketsRef.current.values())\n bucketsRef.current.clear()\n const queued = pendingUpdatersRef.current\n pendingUpdatersRef.current = []\n const hasDeltas = buckets.some(b => b.markdown.length > 0 || b.thinking.length > 0)\n if (!hasDeltas && !updater && queued.length === 0)\n return\n setEvents((prev) => {\n let merged = prev\n for (const bucket of buckets)\n merged = applyBucket(merged, bucket)\n for (const u of queued)\n merged = u(merged)\n if (updater)\n merged = updater(merged)\n return merged\n })\n }, [setEvents, stopTicker])\n\n /**\n * One tick of the continuous drain loop. Walks every live bucket and\n * commits a portion of its content based on the current mode. Once\n * every bucket empties out, fires any post-drain updaters that were\n * queued while smooth streaming was still in progress (turn\n * finalize, tool calls, errors) and stops the ticker.\n *\n * `thinking` content always flushes immediately even in smooth mode —\n * it's an internal-reasoning surface, surfaced for transparency rather\n * than for reading flow, and users expect it to keep up with the model.\n */\n const tick = useCallback(() => {\n const smooth = getSmoothRef.current?.() ?? true\n const buckets = bucketsRef.current\n const portions: DeltaBucket[] = []\n let stillHasContent = false\n\n // Real elapsed time since the last tick. Falls back to the nominal\n // interval on the first tick (when there's nothing to measure\n // against) and clamps to a sane upper bound so a one-off 500ms\n // event-loop stall doesn't burst-drain the entire backlog.\n const now = Date.now()\n const dtMs = lastTickMsRef.current === 0\n ? TICK_INTERVAL_MS\n : Math.min(TICK_INTERVAL_MS * 4, Math.max(1, now - lastTickMsRef.current))\n lastTickMsRef.current = now\n\n for (const bucket of buckets.values()) {\n const thinking = bucket.thinking\n bucket.thinking = ''\n\n let taken = ''\n if (bucket.markdown.length > 0) {\n if (smooth) {\n const { take, carry } = smoothCharsForTick(\n bucket.markdown.length,\n bucket.smoothCarry,\n dtMs,\n )\n bucket.smoothCarry = carry\n if (take > 0) {\n const result = takeChars(bucket.markdown, take)\n taken = result.taken\n bucket.markdown = result.rest\n if (bucket.markdown.length === 0)\n bucket.smoothCarry = 0\n }\n }\n else {\n taken = bucket.markdown\n bucket.markdown = ''\n bucket.smoothCarry = 0\n }\n }\n\n if (taken || thinking) {\n portions.push({\n markdown: taken,\n thinking,\n owner: bucket.owner,\n depth: bucket.depth,\n turnId: bucket.turnId,\n smoothCarry: 0,\n })\n }\n if (bucket.markdown.length > 0)\n stillHasContent = true\n }\n\n // Fold post-drain updaters into the same commit as the trailing\n // portions so the resulting transcript has at most one paint per\n // tick — and so the LAST portion of a smooth stream and its\n // companion finalize-markdown / tool-call event land atomically.\n const drainQueued = !stillHasContent && pendingUpdatersRef.current.length > 0\n const queued = drainQueued\n ? pendingUpdatersRef.current.splice(0, pendingUpdatersRef.current.length)\n : []\n\n if (portions.length > 0 || queued.length > 0) {\n setEvents((prev) => {\n let next = prev\n for (const portion of portions)\n next = applyBucket(next, portion)\n for (const u of queued)\n next = u(next)\n return next\n })\n }\n\n if (!stillHasContent)\n stopTicker()\n }, [setEvents, stopTicker])\n\n const ensureTicker = useCallback(() => {\n if (tickerRef.current)\n return\n tickerRef.current = setInterval(tick, TICK_INTERVAL_MS)\n }, [tick])\n\n /**\n * Decide between fast-drain and deferred-drain for an\n * updater-bearing call (`appendImmediate`, `flushAndUpdate`).\n *\n * Smooth mode + markdown backlog → ENQUEUE the updater behind the\n * trailing characters; the ticker picks it up once buckets empty.\n * Anything else → fall through to `drainNow` so the call retains\n * its synchronous semantics.\n */\n const drainOrDefer = useCallback((updater: (events: StreamEvent[]) => StreamEvent[]) => {\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n pendingUpdatersRef.current.push(updater)\n ensureTicker()\n return\n }\n drainNow(updater)\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flush = useCallback(() => {\n // In smooth mode with backlog, the ticker is already draining at\n // the typewriter cadence — explicit `flush()` shouldn't yank the\n // remaining characters into a single frame. The ticker is\n // self-stopping once empty, so this becomes a no-op there.\n const smooth = getSmoothRef.current?.() ?? true\n if (smooth && hasMarkdownBacklog()) {\n ensureTicker()\n return\n }\n drainNow()\n }, [drainNow, ensureTicker, hasMarkdownBacklog])\n\n const flushAndUpdate = useCallback(\n (update: (events: StreamEvent[]) => StreamEvent[]) => drainOrDefer(update),\n [drainOrDefer],\n )\n\n const appendImmediate = useCallback(\n (evt: StreamEvent) => drainOrDefer(events => [...events, evt]),\n [drainOrDefer],\n )\n\n const queueStreamDelta = useCallback((\n kind: 'markdown' | 'thinking',\n delta: string,\n source?: StreamSource,\n ) => {\n if (!delta)\n return\n const owner: Owner = source?.childId ?? PARENT_OWNER\n const depth = source?.depth ?? 0\n let bucket = bucketsRef.current.get(owner)\n if (!bucket) {\n bucket = emptyBucket(owner, depth)\n bucketsRef.current.set(owner, bucket)\n }\n bucket[kind] += delta\n // Refresh on every delta — `turn:after` drains the bucket between turns,\n // so a turnId only ever applies to the deltas of one turn. Keeping the\n // latest also lets a turn that starts mid-bucket (rare but theoretically\n // possible if two hooks race the flush ticker) tag the new event.\n if (source?.turnId)\n bucket.turnId = source.turnId\n ensureTicker()\n }, [ensureTicker])\n\n const reset = useCallback(() => {\n stopTicker()\n bucketsRef.current.clear()\n pendingUpdatersRef.current = []\n }, [stopTicker])\n\n // CRITICAL: stabilize the returned object. All five callbacks are useCallback'd\n // (stable across renders), but a bare object-literal return would be a fresh\n // reference every call, which cascades through every `useCallback`/`useEffect`\n // that depends on `stream` and turns the resume effect into an unbounded\n // destroy/rebuild loop — the exact source of the terminal-host memory leak.\n return useMemo(\n () => ({ queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset }),\n [queueStreamDelta, appendImmediate, flushAndUpdate, flush, reset],\n )\n}\n","import type { ReactNode } from 'react'\nimport type { SyntaxStyles, Theme, ThemeColors, ThemeSelect, ThemeSurfaces } from './theme'\nimport { createContext, useContext } from 'react'\nimport { DEFAULT_THEME } from './theme'\n\n// ---------------------------------------------------------------------------\n// ThemeContext\n//\n// The TUI / GUI mounts `<ThemeProvider theme={…}>` near the root of its tree,\n// resolving the active theme from `Settings.theme`. Every component reads\n// theme values through a hook so a runtime theme switch (Settings → Theme)\n// triggers an immediate re-paint of the whole tree.\n//\n// Sub-hooks (`useColors`, `useSelectStyle`, …) exist so consumers can grab\n// just the slice they need — the rename-friendly shape lets a component\n// keep its body unchanged when migrating from the static `COLOR.brand`\n// pattern to `const COLOR = useColors(); COLOR.brand`.\n// ---------------------------------------------------------------------------\n\nconst ThemeContext = createContext<Theme>(DEFAULT_THEME)\n\nexport function ThemeProvider({ theme, children }: { theme: Theme, children: ReactNode }) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>\n}\n\nexport function useTheme(): Theme {\n return useContext(ThemeContext)\n}\n\n/** Color palette only — equivalent to `useTheme().colors`. */\nexport function useColors(): ThemeColors {\n return useContext(ThemeContext).colors\n}\n\n/** Select-row styling — equivalent to `useTheme().select`. */\nexport function useSelectStyle(): ThemeSelect {\n return useContext(ThemeContext).select\n}\n\n/** Panel / surface backgrounds — equivalent to `useTheme().surfaces`. */\nexport function useSurfaces(): ThemeSurfaces {\n return useContext(ThemeContext).surfaces\n}\n\n/** Raw syntax style table — `useTheme().syntax`. Renderer converts to its native style type. */\nexport function useSyntaxStyles(): SyntaxStyles {\n return useContext(ThemeContext).syntax\n}\n","/**\n * Per-tool display metadata + one-line formatters consumed by any\n * surface that renders a `tool` event in `'formatted'` mode (see\n * `Settings.toolCallDisplay`).\n *\n * Each native tool gets a curated entry — a `displayName` verb that\n * reads in sentence case (e.g. \"Read\", \"Shell\") and a `format` callback\n * that pulls the most informative bits out of the model's raw input to\n * a single scannable line. Unknown tools (MCP servers, host-added\n * tools, future zidane additions) fall back to {@link formatToolCall}\n * returning `null` — the renderer then shows a minimal `↳ <name>` line.\n *\n * Renderer-agnostic: returns plain data (`{ target, meta }`) so the\n * TUI's React/OpenTUI surface and any future GUI consumer can paint\n * the same shape in their own style. Lives in `zidane/chat` because\n * it has no rendering concerns; the TUI just consumes it.\n */\n\nexport interface ToolFormatLine {\n /**\n * Primary target — typically a path, command, pattern, or\n * task description. Renderer paints this in the model accent color\n * so the eye lands on it first.\n */\n target?: string\n /**\n * Secondary annotations rendered after the target, joined with\n * ` · ` separators. Use for line ranges (`L10-25`), limits, flags,\n * or any short suffix that adds context without bloating the line.\n */\n meta?: readonly string[]\n}\n\nexport interface ToolDisplayMeta {\n /**\n * Title-case display verb (e.g. `Read`, `Shell`, `Edit`). When the\n * label depends on an input field (e.g. `skills_use`'s\n * `mode: 'activate' | 'deactivate'` → `Enable` / `Disable`), supply\n * a function instead and read the field defensively. The function\n * form is called whenever {@link displayNameFor} has the call's\n * input in scope; callers without input fall back to the function's\n * `input: undefined` branch, which should return a stable default.\n */\n displayName: string | ((input: Record<string, unknown> | undefined) => string)\n /**\n * Pull a {@link ToolFormatLine} out of the raw model input. Returns\n * `null` when the shape isn't what the tool expects (typed defensively\n * — we never want a malformed call to crash the transcript).\n */\n format: (input: Record<string, unknown>) => ToolFormatLine | null\n}\n\n// ---------------------------------------------------------------------------\n// Tool registry\n// ---------------------------------------------------------------------------\n\nexport const TOOL_DISPLAY: Readonly<Record<string, ToolDisplayMeta>> = {\n // ---- Pure reads ----\n read_file: {\n displayName: 'Read',\n format: (input) => {\n const path = stringField(input, 'path')\n if (!path)\n return null\n const meta: string[] = []\n const offset = numberField(input, 'offset')\n const limit = numberField(input, 'limit')\n if (offset !== undefined && limit !== undefined && limit > 0)\n meta.push(`L${offset}–${offset + limit - 1}`)\n else if (offset !== undefined)\n meta.push(`from L${offset}`)\n else if (limit !== undefined && limit > 0)\n meta.push(`${limit} lines`)\n return { target: path, meta }\n },\n },\n list_files: {\n displayName: 'List',\n format: (input) => {\n const path = stringField(input, 'path') ?? '.'\n return { target: path }\n },\n },\n glob: {\n displayName: 'Glob',\n format: (input) => {\n const pattern = stringField(input, 'pattern')\n if (!pattern)\n return null\n const meta: string[] = []\n const limit = numberField(input, 'limit')\n if (limit !== undefined)\n meta.push(`limit ${limit}`)\n return { target: pattern, meta }\n },\n },\n grep: {\n displayName: 'Grep',\n format: (input) => {\n const pattern = stringField(input, 'pattern')\n if (!pattern)\n return null\n const target = `/${pattern}/`\n const meta: string[] = []\n const path = stringField(input, 'path')\n if (path && path !== '.')\n meta.push(`in ${path}`)\n const glob = stringField(input, 'glob')\n if (glob)\n meta.push(glob)\n const type = stringField(input, 'type')\n if (type)\n meta.push(`type:${type}`)\n if (input['-i'] === true)\n meta.push('case-insensitive')\n const mode = stringField(input, 'output_mode')\n if (mode && mode !== 'files_with_matches')\n meta.push(mode)\n return { target, meta }\n },\n },\n\n // ---- Shell ----\n shell: {\n // Verb switches when the model dispatches a background task — same\n // pattern `skills_use` uses for activate/deactivate. The transcript\n // reads as `↳ Shell (background): npm run dev` so the user can spot\n // backgrounded invocations at a glance without reading the args.\n displayName: input => input?.run_in_background === true ? 'Shell (background)' : 'Shell',\n format: (input) => {\n const command = stringField(input, 'command')\n if (!command)\n return null\n return { target: truncate(command, 200) }\n },\n },\n shell_kill: {\n displayName: 'Kill task',\n format: (input) => {\n const taskId = stringField(input, 'task_id')\n if (!taskId)\n return null\n return { target: taskId }\n },\n },\n\n // ---- Edits (renderer falls through to EditDiffBlock when showEditDiffs is on) ----\n edit: {\n displayName: 'Edit',\n format: (input) => {\n const path = stringField(input, 'path')\n if (!path)\n return null\n return {\n target: path,\n meta: input.replace_all === true ? ['replace all'] : [],\n }\n },\n },\n multi_edit: {\n displayName: 'Multi-edit',\n format: (input) => {\n const path = stringField(input, 'path')\n if (!path)\n return null\n const edits = Array.isArray(input.edits) ? input.edits.length : 0\n return {\n target: path,\n meta: edits > 0 ? [`${edits} hunk${edits === 1 ? '' : 's'}`] : [],\n }\n },\n },\n write_file: {\n displayName: 'Write',\n format: (input) => {\n const path = stringField(input, 'path')\n if (!path)\n return null\n const content = stringField(input, 'content')\n const meta: string[] = []\n if (content !== undefined) {\n // Show byte count instead of line count — write_file's main\n // failure mode is \"did you mean to overwrite a 10KB file with\n // a 4-byte one?\", so bytes catch it faster than lines do.\n const bytes = byteLengthUtf8(content)\n meta.push(`${formatBytes(bytes)}`)\n }\n return { target: path, meta }\n },\n },\n\n // ---- Spawn / orchestration ----\n spawn: {\n displayName: 'Agent',\n format: (input) => {\n const task = stringField(input, 'task')\n if (!task)\n return null\n return { target: truncate(task, 120) }\n },\n },\n\n // ---- Tool search (lazy-tool discovery) ----\n tool_search: {\n displayName: 'Search tools',\n format: (input) => {\n const query = stringField(input, 'query')\n const names = Array.isArray(input.names) ? input.names.length : 0\n if (query)\n return { target: `“${query}”` }\n if (names > 0)\n return { target: `${names} tool${names === 1 ? '' : 's'}` }\n return null\n },\n },\n\n // ---- Skills ----\n skills_use: {\n // `skills_use` straddles two intents — activation (default) and\n // deactivation (`mode: 'deactivate'`, the zidane extension that lets\n // the model recover from `AgentToolNotAllowedError`). Showing \"Activate\"\n // for both modes hid the deactivation from the transcript and made\n // the model's recovery flow harder to follow. We pick the verb from\n // `input.mode`; absent or unexpected values fall back to \"Enable\" to\n // match the spec's activate-only semantics.\n displayName: (input) => {\n const mode = input ? stringField(input, 'mode') : undefined\n return mode === 'deactivate' ? 'Disable skill' : 'Enable skill'\n },\n format: (input) => {\n const name = stringField(input, 'name')\n if (!name)\n return null\n return { target: name }\n },\n },\n skills_read: {\n displayName: 'Read skill',\n format: (input) => {\n const name = stringField(input, 'name')\n const path = stringField(input, 'path')\n if (!name)\n return null\n return { target: path ? `${name}/${path}` : name }\n },\n },\n skills_run_script: {\n displayName: 'Run script',\n format: (input) => {\n const name = stringField(input, 'name')\n const script = stringField(input, 'script')\n if (!name || !script)\n return null\n const meta: string[] = [`skill ${name}`]\n const args = Array.isArray(input.args) ? input.args : null\n if (args && args.length > 0)\n meta.push(truncate(args.map(String).join(' '), 80))\n return { target: script, meta }\n },\n },\n\n // ---- Todos ----\n todowrite: {\n displayName: 'Todos',\n format: (input) => {\n const todos = Array.isArray(input.todos) ? input.todos : null\n if (!todos)\n return null\n // Show item count + per-status tally — same shape the tool's\n // `tool_result` summary line uses, so the call line and result\n // line read consistently in the transcript.\n const counts = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 }\n for (const t of todos) {\n if (!t || typeof t !== 'object')\n continue\n const status = (t as Record<string, unknown>).status\n if (typeof status === 'string' && status in counts)\n counts[status as keyof typeof counts] += 1\n }\n const meta: string[] = []\n if (counts.completed)\n meta.push(`${counts.completed} done`)\n if (counts.in_progress)\n meta.push(`${counts.in_progress} in progress`)\n if (counts.pending)\n meta.push(`${counts.pending} pending`)\n if (counts.cancelled)\n meta.push(`${counts.cancelled} cancelled`)\n return { target: `${todos.length} item${todos.length === 1 ? '' : 's'}`, meta }\n },\n },\n todoread: {\n displayName: 'Todos',\n format: () => ({ target: 'read' }),\n },\n\n // ---- Interactions (already surfaced as wizards, but the call line still renders) ----\n ask_user: {\n displayName: 'Ask user',\n format: (input) => {\n const questions = Array.isArray(input.questions) ? input.questions.length : 0\n if (questions === 0)\n return null\n return { target: `${questions} question${questions === 1 ? '' : 's'}` }\n },\n },\n present_plan: {\n displayName: 'Present plan',\n format: (input) => {\n const title = stringField(input, 'title')\n if (!title)\n return null\n return { target: title }\n },\n },\n}\n\n// ---------------------------------------------------------------------------\n// Public helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the display verb for a tool. Native tools use their curated\n * entry from {@link TOOL_DISPLAY}; everything else gets a sentence-case\n * version of the raw name (`my_host_tool` → `My host tool`) so an MCP /\n * host tool still reads cleanly in the transcript without shouting\n * Title Case at every word.\n *\n * MCP convention: every tool surfaced by `mcp/connectMcpServers` is\n * namespaced as `mcp_<server>_<tool>` (see `src/mcp/index.ts`). The\n * `mcp_` prefix is plumbing — strip it before casing so the label\n * reads as `Github create issue` instead of `Mcp github create issue`.\n * The server name leads, which doubles as a free visual grouping\n * affordance (\"everything starting with `Github` came from the github\n * MCP server\").\n */\nexport function displayNameFor(\n name: string,\n input?: Record<string, unknown>,\n): string {\n const entry = TOOL_DISPLAY[name]\n if (entry) {\n return typeof entry.displayName === 'function'\n ? entry.displayName(input)\n : entry.displayName\n }\n const stripped = name.startsWith('mcp_') ? name.slice(4) : name\n return sentenceCase(stripped)\n}\n\n/**\n * Run a tool's curated formatter and return the result, or `null` when\n * no formatter is registered / the input shape doesn't match. Renderer\n * decides what to do with `null` — typically: show `↳ <displayName>`\n * with no target / meta tail.\n */\nexport function formatToolCall(name: string, input: Record<string, unknown>): ToolFormatLine | null {\n const entry = TOOL_DISPLAY[name]\n if (!entry)\n return null\n try {\n return entry.format(input)\n }\n catch {\n // Defensive — a misshapen `input` should never crash the\n // transcript. Fall back to the unformatted line.\n return null\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction stringField(input: Record<string, unknown>, key: string): string | undefined {\n const v = input[key]\n return typeof v === 'string' && v.length > 0 ? v : undefined\n}\n\nfunction numberField(input: Record<string, unknown>, key: string): number | undefined {\n const v = input[key]\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined\n}\n\n/** `snake_case` / `kebab-case` / lowercase → `Sentence case`. */\nfunction sentenceCase(s: string): string {\n const words = s.split(/[-_\\s]+/).filter(Boolean).map(w => w.toLowerCase())\n if (words.length === 0)\n return ''\n words[0] = (words[0][0]?.toUpperCase() ?? '') + words[0].slice(1)\n return words.join(' ')\n}\n\n/**\n * Collapse internal whitespace (including newlines) to single spaces\n * and clip to `max` columns with a trailing `…`. The whitespace\n * normalisation is the load-bearing bit — tool input strings like a\n * shell heredoc or a multi-line Python `-c` script otherwise render\n * across several rows in the transcript even though the `↳ Tool …`\n * line is meant to be a single-line scannable summary.\n */\nfunction truncate(s: string, max: number): string {\n const clean = s.replace(/\\s+/g, ' ').trim()\n return clean.length <= max ? clean : `${clean.slice(0, max - 1)}…`\n}\n\nfunction byteLengthUtf8(s: string): number {\n // Cheap UTF-8 byte count without pulling Buffer at this layer (renderer\n // -agnostic file). Mirrors what Buffer.byteLength returns for typical\n // inputs; off by a handful of bytes on exotic codepoints, which is fine\n // since the value is displayed rounded via formatBytes.\n let bytes = 0\n for (let i = 0; i < s.length; i++) {\n const code = s.charCodeAt(i)\n if (code < 0x80) {\n bytes += 1\n }\n else if (code < 0x800) {\n bytes += 2\n }\n else if (code >= 0xD800 && code <= 0xDBFF) {\n bytes += 4\n i++ // skip the low surrogate\n }\n else {\n bytes += 3\n }\n }\n return bytes\n}\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024)\n return `${bytes} B`\n if (bytes < 1024 * 1024)\n return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n","// ---------------------------------------------------------------------------\n// Transcript scroll-anchor computation.\n//\n// Walks a list of partitioned transcript items (one entry per row that\n// will be rendered) and assigns a stable scroll-anchor id to the FIRST\n// event of each turn — `turn-anchor-<turnId>`. The renderer applies\n// these ids to whichever container hosts the first event of the turn;\n// scroll-into-view (TUI scrollbox or GUI element.scrollIntoView) then\n// resolves the id to a known target.\n//\n// Pure data-in / data-out so the same logic powers the TUI today and\n// any future GUI without dragging OpenTUI specifics into the chat\n// layer.\n// ---------------------------------------------------------------------------\n\nimport type { StreamEvent } from './types'\n\n/**\n * One entry in the partitioned transcript-row list. Single events\n * render as a normal row; `child-run`s group consecutive subagent\n * events into one bordered box (the TUI renders one outer container\n * for the whole run, hence the inner-events array).\n *\n * Currently produced by the TUI's `partitionTranscript`; a GUI shell\n * grouping subagent runs the same way can produce the same shape.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order\n * and, for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scroll target).\n * - `undefined` — later event of an already-tagged turn (or a\n * synthetic event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N\n * for subagent runs (one entry per inner event). `idByTurn` is the\n * inverse lookup used by the scroll effect. `lastTurnId` is the\n * most-recently-rendered turn — the scroll effect special-cases it to\n * snap to bottom rather than scroll the anchor into view (which would\n * stop short of the actual tail).\n *\n * Exported so the anchor-tagging matrix can be unit-tested without\n * rendering anything.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n","/**\n * Pure transforms on `SessionTurn[]` used by the TUI's turn-selection\n * modal actions (fork / delete / copy). Each helper is renderer- and\n * store-agnostic so it composes the same in TUI, SDK, or test code.\n *\n * Tool-call protocol invariant the helpers preserve:\n *\n * Every `tool_call` block emitted by an assistant turn MUST be matched\n * by a `tool_result` block (same `callId`) in a later turn before the\n * next assistant turn. Providers reject histories that violate this —\n * so any operation that mutates the turn list must either keep pairs\n * intact or strip both sides.\n */\n\nimport type { SessionContentBlock, SessionTurn } from '../types'\n\n/**\n * Fork — keep every turn up to and including `turnId`, then strip any\n * `tool_call` blocks left without a matching `tool_result` in the slice.\n *\n * Semantics:\n * - Include the selected turn (\"branch from HERE\" mental model — the\n * user wants the selected message to be the latest in the fork).\n * - If the selected turn is an assistant turn with unresolved\n * `tool_call` blocks (their `tool_result`s live in turns AFTER the\n * slice), strip those calls. Otherwise the fork would post an\n * assistant turn with no matching tool results, breaking the next\n * provider call.\n * - Drop turns that become empty (all blocks stripped).\n *\n * Returns `null` when `turnId` doesn't exist in `turns` — caller should\n * surface a \"turn not found\" error rather than silently no-op.\n */\nexport function truncateTurnsAt(turns: readonly SessionTurn[], turnId: string): SessionTurn[] | null {\n const idx = turns.findIndex(t => t.id === turnId)\n if (idx === -1)\n return null\n const slice = turns.slice(0, idx + 1)\n return stripOrphanToolBlocks(slice)\n}\n\n/**\n * Delete — remove the turn with `turnId` and any tool blocks left\n * orphaned by the removal. Returns `null` when `turnId` doesn't exist.\n *\n * Strategy:\n * 1. Drop the target turn.\n * 2. Scan the remaining turns for `tool_call`s without a matching\n * `tool_result` (orphaned by removing the user turn that carried\n * the result), and `tool_result`s without a matching `tool_call`\n * (orphaned by removing the assistant turn that issued the call).\n * Strip both sides.\n * 3. Drop turns whose content is now empty.\n *\n * This guarantees the resulting history is protocol-clean — a follow-up\n * `agent.run()` against the modified session can post turns without the\n * provider rejecting the history.\n */\nexport function deleteTurnSafely(turns: readonly SessionTurn[], turnId: string): SessionTurn[] | null {\n const idx = turns.findIndex(t => t.id === turnId)\n if (idx === -1)\n return null\n const without = [...turns.slice(0, idx), ...turns.slice(idx + 1)]\n return stripOrphanToolBlocks(without)\n}\n\n/**\n * Walk a turn list and remove any tool blocks whose counterpart is\n * missing. Drops turns left empty. Used by `truncateTurnsAt` (which can\n * leave `tool_call`s orphaned when their results are past the cut) and\n * `deleteTurnSafely` (which can orphan either side of a pair).\n *\n * Pure / total: returns a new array; never throws.\n */\nfunction stripOrphanToolBlocks(turns: readonly SessionTurn[]): SessionTurn[] {\n const callIds = new Set<string>()\n const resultIds = new Set<string>()\n for (const turn of turns) {\n for (const block of turn.content) {\n if (block.type === 'tool_call')\n callIds.add(block.id)\n else if (block.type === 'tool_result')\n resultIds.add(block.callId)\n }\n }\n const result: SessionTurn[] = []\n for (const turn of turns) {\n const filtered: SessionContentBlock[] = []\n for (const block of turn.content) {\n if (block.type === 'tool_call') {\n if (!resultIds.has(block.id))\n continue // call lost its result\n }\n else if (block.type === 'tool_result') {\n if (!callIds.has(block.callId))\n continue // result lost its call\n }\n filtered.push(block)\n }\n if (filtered.length === 0)\n continue // turn is now empty — drop it\n result.push(filtered.length === turn.content.length ? turn : { ...turn, content: filtered })\n }\n return result\n}\n\n/**\n * Serialize a turn's content to a clean text representation suited for\n * the clipboard. Joins text + thinking blocks verbatim; tool calls and\n * tool results get bracketed labels so the user can paste a readable\n * record of what happened without losing structure.\n *\n * Empty turns return `''`.\n */\nexport function turnAsText(turn: SessionTurn): string {\n const parts: string[] = []\n for (const block of turn.content) {\n if (block.type === 'text' && block.text.trim())\n parts.push(block.text)\n else if (block.type === 'thinking' && block.text.trim())\n parts.push(`[thinking]\\n${block.text}`)\n else if (block.type === 'tool_call')\n parts.push(`[tool call · ${block.name}]\\n${stringifyArgs(block.input)}`)\n else if (block.type === 'tool_result')\n parts.push(`[tool result]\\n${typeof block.output === 'string' ? block.output : JSON.stringify(block.output, null, 2)}`)\n else if (block.type === 'compact-summary')\n parts.push(`[compaction summary · ${block.replacesTurnIds.length} turn${block.replacesTurnIds.length === 1 ? '' : 's'}]\\n${block.summary}`)\n }\n return parts.join('\\n\\n')\n}\n\nfunction stringifyArgs(input: Record<string, unknown>): string {\n try {\n return JSON.stringify(input, null, 2)\n }\n catch {\n return String(input)\n }\n}\n\n/**\n * Count turns before / after the one identified by `turnId` in the\n * given list. Returns `null` when the id is missing. Used to label the\n * turn-details modal with `N before · M after`.\n */\nexport function countNeighbors(\n turnIds: readonly string[],\n turnId: string,\n): { before: number, after: number } | null {\n const idx = turnIds.indexOf(turnId)\n if (idx === -1)\n return null\n return { before: idx, after: turnIds.length - 1 - idx }\n}\n"],"mappings":"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"}