zidane 8.2.3 → 8.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/dist/{acp-oYRjlh71.js → acp-BIInKhVI.js} +6 -6
  2. package/dist/{acp-oYRjlh71.js.map → acp-BIInKhVI.js.map} +1 -1
  3. package/dist/acp-cli.js +4 -4
  4. package/dist/acp.d.ts +2 -2
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-CBQglXtH.js → agent-CmhCOmyO.js} +10 -5
  7. package/dist/{agent-CBQglXtH.js.map → agent-CmhCOmyO.js.map} +1 -1
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/chat/pure.d.ts +4 -4
  11. package/dist/chat/pure.js +7 -7
  12. package/dist/chat.d.ts +3 -3
  13. package/dist/chat.js +16 -16
  14. package/dist/{cli-shared-E5m_pI8A.js → cli-shared-GOehTeqk.js} +2 -2
  15. package/dist/{cli-shared-E5m_pI8A.js.map → cli-shared-GOehTeqk.js.map} +1 -1
  16. package/dist/{client-core-EEEX_u6D.d.ts → client-core-Dz8hELs4.d.ts} +15 -5
  17. package/dist/client-core-Dz8hELs4.d.ts.map +1 -0
  18. package/dist/codemode-worker.js +1 -1
  19. package/dist/codemode.js +1 -1
  20. package/dist/{connection-loop-CpgfUmuw.d.ts → connection-loop-dm_TN-QD.d.ts} +3 -3
  21. package/dist/{connection-loop-CpgfUmuw.d.ts.map → connection-loop-dm_TN-QD.d.ts.map} +1 -1
  22. package/dist/contexts/daytona.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts +1 -1
  24. package/dist/{credentials-B9DfBJP9.js → credentials-DxOQmset.js} +2 -2
  25. package/dist/{credentials-B9DfBJP9.js.map → credentials-DxOQmset.js.map} +1 -1
  26. package/dist/daemon-cli.js +2 -2
  27. package/dist/{dispatcher-Bv9-c6I2.js → dispatcher-D_9_QFO3.js} +22 -18
  28. package/dist/dispatcher-D_9_QFO3.js.map +1 -0
  29. package/dist/{engine-XmoPPB6U.js → engine-BsWBWgTB.js} +6 -6
  30. package/dist/{engine-XmoPPB6U.js.map → engine-BsWBWgTB.js.map} +1 -1
  31. package/dist/{eval-CfQlBR81.js → eval-CIHIF_Ns.js} +3 -3
  32. package/dist/{eval-CfQlBR81.js.map → eval-CIHIF_Ns.js.map} +1 -1
  33. package/dist/eval.d.ts +1 -1
  34. package/dist/eval.js +1 -1
  35. package/dist/{executor-BhthPE1q.js → executor-BSesPPcU.js} +3 -3
  36. package/dist/{executor-BhthPE1q.js.map → executor-BSesPPcU.js.map} +1 -1
  37. package/dist/{extensions-BgBwogRW.js → extensions-BJof-2mi.js} +3 -3
  38. package/dist/{extensions-BgBwogRW.js.map → extensions-BJof-2mi.js.map} +1 -1
  39. package/dist/extensions.d.ts +1 -1
  40. package/dist/extensions.js +1 -1
  41. package/dist/{fold-rules-DSypi36k.d.ts → fold-rules-CMpWNPqk.d.ts} +2 -2
  42. package/dist/{fold-rules-DSypi36k.d.ts.map → fold-rules-CMpWNPqk.d.ts.map} +1 -1
  43. package/dist/{headless-R4pbpTkm.js → headless-Bs9Lb4vs.js} +4 -4
  44. package/dist/{headless-R4pbpTkm.js.map → headless-Bs9Lb4vs.js.map} +1 -1
  45. package/dist/headless.d.ts +1 -1
  46. package/dist/headless.js +1 -1
  47. package/dist/history-worker.js +1 -1
  48. package/dist/{host-scoped-settings-C-Nmb6vr.js → host-scoped-settings-DxxI_yRo.js} +2 -2
  49. package/dist/{host-scoped-settings-C-Nmb6vr.js.map → host-scoped-settings-DxxI_yRo.js.map} +1 -1
  50. package/dist/{host-signals-BFudPyCT.js → host-signals-CUKPZZxC.js} +5 -5
  51. package/dist/{host-signals-BFudPyCT.js.map → host-signals-CUKPZZxC.js.map} +1 -1
  52. package/dist/{index-DyQyDLX1.d.ts → index-4mDRyTGJ.d.ts} +1722 -1691
  53. package/dist/index-4mDRyTGJ.d.ts.map +1 -0
  54. package/dist/{index-CDS7YdUC.d.ts → index-Dlmu8HnG.d.ts} +5 -5
  55. package/dist/{index-CDS7YdUC.d.ts.map → index-Dlmu8HnG.d.ts.map} +1 -1
  56. package/dist/{index-AqRZ3gEz.d.ts → index-QsAN9yjZ.d.ts} +3 -3
  57. package/dist/{index-AqRZ3gEz.d.ts.map → index-QsAN9yjZ.d.ts.map} +1 -1
  58. package/dist/index.d.ts +3 -3
  59. package/dist/index.js +8 -8
  60. package/dist/ipc/remote.d.ts +5 -5
  61. package/dist/ipc/remote.js +3 -3
  62. package/dist/ipc.d.ts +5 -5
  63. package/dist/ipc.js +9 -9
  64. package/dist/{links-rFaWqS2Y.js → links-Bto-5dVu.js} +10 -10
  65. package/dist/{links-rFaWqS2Y.js.map → links-Bto-5dVu.js.map} +1 -1
  66. package/dist/{mcp-allowlist-CLXweyxk.js → mcp-allowlist-BbnTBKNN.js} +2 -2
  67. package/dist/{mcp-allowlist-CLXweyxk.js.map → mcp-allowlist-BbnTBKNN.js.map} +1 -1
  68. package/dist/mcp.d.ts +1 -1
  69. package/dist/{oauth-7_48zR7n.js → oauth-BwPiX0hL.js} +66 -32
  70. package/dist/oauth-BwPiX0hL.js.map +1 -0
  71. package/dist/output/stream-json.d.ts +1 -1
  72. package/dist/output/terminal.d.ts +1 -1
  73. package/dist/plugins.d.ts +1 -1
  74. package/dist/{prelude-HzY_JEGq.js → prelude-qjPUkw4f.js} +26 -2
  75. package/dist/prelude-qjPUkw4f.js.map +1 -0
  76. package/dist/{presets-DXeHsd24.js → presets-C4hkykN-.js} +2 -2
  77. package/dist/{presets-DXeHsd24.js.map → presets-C4hkykN-.js.map} +1 -1
  78. package/dist/presets.d.ts +1 -1
  79. package/dist/presets.js +1 -1
  80. package/dist/{protocol-BIv1Yuvl.js → protocol-Do_i6Ras.js} +3 -1
  81. package/dist/protocol-Do_i6Ras.js.map +1 -0
  82. package/dist/providers/anthropic.d.ts +1 -1
  83. package/dist/providers/arcee.d.ts +1 -1
  84. package/dist/providers/baseten.d.ts +1 -1
  85. package/dist/providers/cerebras.d.ts +1 -1
  86. package/dist/providers/google.d.ts +1 -1
  87. package/dist/providers/local.d.ts +1 -1
  88. package/dist/providers/neuralwatt.d.ts +1 -1
  89. package/dist/providers/openai-compat.d.ts +1 -1
  90. package/dist/providers/openai.d.ts +1 -1
  91. package/dist/providers/openrouter.d.ts +1 -1
  92. package/dist/providers/xai.d.ts +1 -1
  93. package/dist/providers.d.ts +1 -1
  94. package/dist/{registry-eourRLY2.js → registry-DxPP_tAt.js} +3 -3
  95. package/dist/{registry-eourRLY2.js.map → registry-DxPP_tAt.js.map} +1 -1
  96. package/dist/{remote-hosts-DY2J07ut.js → remote-hosts-DcCh7fqp.js} +4 -4
  97. package/dist/{remote-hosts-DY2J07ut.js.map → remote-hosts-DcCh7fqp.js.map} +1 -1
  98. package/dist/restate.d.ts +1 -1
  99. package/dist/{secret-shapes-BjW4l6U0.js → secret-shapes-HvaYVzwc.js} +10 -2
  100. package/dist/secret-shapes-HvaYVzwc.js.map +1 -0
  101. package/dist/session/sqlite.d.ts +1 -1
  102. package/dist/{session-7TUHs42_.js → session-CCsKXID6.js} +6 -2
  103. package/dist/session-CCsKXID6.js.map +1 -0
  104. package/dist/{session-factory-CMwIXE-7.js → session-factory-Ct7J9CK1.js} +6 -6
  105. package/dist/{session-factory-CMwIXE-7.js.map → session-factory-Ct7J9CK1.js.map} +1 -1
  106. package/dist/session.d.ts +2 -2
  107. package/dist/session.js +1 -1
  108. package/dist/{settings-keys-CPdmkTz7.js → settings-keys-CnrJgT1T.js} +4 -4
  109. package/dist/{settings-keys-CPdmkTz7.js.map → settings-keys-CnrJgT1T.js.map} +1 -1
  110. package/dist/{shell-path-B8b3EOtK.js → shell-path-rJDa0OMv.js} +4 -4
  111. package/dist/{shell-path-B8b3EOtK.js.map → shell-path-rJDa0OMv.js.map} +1 -1
  112. package/dist/skills.d.ts +1 -1
  113. package/dist/{spaces-CyjMh5Zf.js → spaces-C_x2bvEl.js} +14 -4
  114. package/dist/spaces-C_x2bvEl.js.map +1 -0
  115. package/dist/{store-L01W0gGI.js → store-BpkWIaSH.js} +10 -30
  116. package/dist/store-BpkWIaSH.js.map +1 -0
  117. package/dist/{todo-edits-BERpCRPj.js → todo-edits-DKoxu7Ss.js} +3 -3
  118. package/dist/{todo-edits-BERpCRPj.js.map → todo-edits-DKoxu7Ss.js.map} +1 -1
  119. package/dist/tools/fetch-url.d.ts +1 -1
  120. package/dist/tools/web-search.d.ts +1 -1
  121. package/dist/{tools-pZVFIJ_K.js → tools-FHEIKGkh.js} +2 -2
  122. package/dist/{tools-pZVFIJ_K.js.map → tools-FHEIKGkh.js.map} +1 -1
  123. package/dist/tools.d.ts +1 -1
  124. package/dist/tools.js +2 -2
  125. package/dist/{transcript-anchors-jHrg-r-H.js → transcript-anchors-BcFLh3ZA.js} +10 -10
  126. package/dist/{transcript-anchors-jHrg-r-H.js.map → transcript-anchors-BcFLh3ZA.js.map} +1 -1
  127. package/dist/{transcript-cache-DXdgVuoy.js → transcript-cache-CdHKtx9y.js} +4 -4
  128. package/dist/{transcript-cache-DXdgVuoy.js.map → transcript-cache-CdHKtx9y.js.map} +1 -1
  129. package/dist/{transcript-reconcile-CPdrNbbR.js → transcript-reconcile-D_SWw6dX.js} +11 -3
  130. package/dist/transcript-reconcile-D_SWw6dX.js.map +1 -0
  131. package/dist/tui.d.ts +7 -7
  132. package/dist/tui.js +24 -23
  133. package/dist/tui.js.map +1 -1
  134. package/dist/{turn-operations-DuGtKuc_.d.ts → turn-operations-Dbk3xPKu.d.ts} +11 -3
  135. package/dist/{turn-operations-DuGtKuc_.d.ts.map → turn-operations-Dbk3xPKu.d.ts.map} +1 -1
  136. package/dist/{types-C0PJmBaO.d.ts → types-CMXR7A0b.d.ts} +2 -2
  137. package/dist/{types-C0PJmBaO.d.ts.map → types-CMXR7A0b.d.ts.map} +1 -1
  138. package/dist/types-CzmghX7C.js.map +1 -1
  139. package/dist/types.d.ts +2 -2
  140. package/dist/{url-D8KZ7Edw.js → url-D4vG7lzO.js} +2 -2
  141. package/dist/{url-D8KZ7Edw.js.map → url-D4vG7lzO.js.map} +1 -1
  142. package/dist/{version-bd3DQJkc.js → version-DOvIuU8M.js} +2 -2
  143. package/dist/{version-bd3DQJkc.js.map → version-DOvIuU8M.js.map} +1 -1
  144. package/dist/{workspace-exec-stream-CyfYgqAh.js → workspace-exec-stream-Bzxczzu2.js} +5 -5
  145. package/dist/{workspace-exec-stream-CyfYgqAh.js.map → workspace-exec-stream-Bzxczzu2.js.map} +1 -1
  146. package/dist/{write-file-QYrcDdl3.js → write-file-ChtRPdEL.js} +3 -3
  147. package/dist/{write-file-QYrcDdl3.js.map → write-file-ChtRPdEL.js.map} +1 -1
  148. package/dist/{ws-CURQ2igk.js → ws-CrD-4Mx6.js} +6 -6
  149. package/dist/{ws-CURQ2igk.js.map → ws-CrD-4Mx6.js.map} +1 -1
  150. package/dist/{ws-client-BVljYbdb.js → ws-client-Dond8vT3.js} +4 -4
  151. package/dist/{ws-client-BVljYbdb.js.map → ws-client-Dond8vT3.js.map} +1 -1
  152. package/docs/CODEMODE.md +6 -0
  153. package/docs/IPC.md +2 -0
  154. package/docs/ONBOARDING.md +1 -1
  155. package/package.json +1 -1
  156. package/dist/client-core-EEEX_u6D.d.ts.map +0 -1
  157. package/dist/dispatcher-Bv9-c6I2.js.map +0 -1
  158. package/dist/index-DyQyDLX1.d.ts.map +0 -1
  159. package/dist/oauth-7_48zR7n.js.map +0 -1
  160. package/dist/prelude-HzY_JEGq.js.map +0 -1
  161. package/dist/protocol-BIv1Yuvl.js.map +0 -1
  162. package/dist/secret-shapes-BjW4l6U0.js.map +0 -1
  163. package/dist/session-7TUHs42_.js.map +0 -1
  164. package/dist/spaces-CyjMh5Zf.js.map +0 -1
  165. package/dist/store-L01W0gGI.js.map +0 -1
  166. package/dist/transcript-reconcile-CPdrNbbR.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session-factory-CMwIXE-7.js","names":[],"sources":["../src/chat/agent-prompt.ts","../src/chat/todos.ts","../src/chat/agents.ts","../src/chat/session-factory.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 * A change of cwd, date or platform leaves the doctrine cache above valid.\n * The `system:transform` hook installed by `installCwdHooks` rewrites only the\n * dynamic half via `replaceDynamicSection`; the static prefix stays\n * byte-identical for the lifetime of the run.\n */\n\nimport { joinSystemPrompt, replaceDynamicSection, splitSystemPrompt } from '../system-prompt'\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\n/**\n * Lead line — short, version-stable. The first sentence matches the key of\n * Claude Code's identity prefix (models anchor faster on familiar shapes).\n * The rest sets the register: token-earning sentences with byte-exact\n * code (\"small mouth, full brain\"), and a hard rule that speed never\n * widens scope (the leeroy-jenkins fix). Keep changes rare — each one\n * costs a cache miss across active sessions.\n */\nexport const IDENTITY_PREFIX\n = 'You are zidane, a terminal-based coding agent that helps the user with software-engineering tasks. Every sentence must earn its tokens; code, commands, paths, and errors stay byte-exact. Small mouth, full brain. Move fast on the task given; speed never widens scope, the user owns scope.'\n\n// ---------------------------------------------------------------------------\n// Environment — cwd + date + platform\n// ---------------------------------------------------------------------------\n\n/** Options for {@link envSection}. */\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), 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 *\n * @param opts - Working directory, optional project root, date and platform to render.\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\nconst ENV_OPEN = '<env>'\nconst ENV_CLOSE = '</env>'\n\n/**\n * Replace only the `<env>` block in a system prompt's dynamic half. Fragments\n * hosts append after it (extension, browser and terminal guidance) stay in\n * place. Falls back to {@link replaceDynamicSection} when the dynamic half\n * has no `<env>` block.\n *\n * @param system - System prompt, with or without a boundary marker.\n * @param env - Replacement block, as rendered by {@link envSection}.\n */\nexport function replaceEnvSection(system: string, env: string): string {\n const { static: staticPart, dynamic } = splitSystemPrompt(system)\n const start = dynamic.indexOf(ENV_OPEN)\n const end = start < 0 ? -1 : dynamic.indexOf(ENV_CLOSE, start)\n if (start < 0 || end < 0)\n return replaceDynamicSection(system, env)\n return joinSystemPrompt(staticPart, dynamic.slice(0, start) + env + dynamic.slice(end + ENV_CLOSE.length))\n}\n\n/**\n * The `Date:` value of the `<env>` block in a system prompt's dynamic half,\n * or `undefined` when there is none. Lets a rewrite keep the date the prompt\n * was built with, so a long session does not rewrite its prefix at midnight.\n *\n * @param system - System prompt, with or without a boundary marker.\n */\nexport function envSectionDate(system: string): string | undefined {\n const { dynamic } = splitSystemPrompt(system)\n const start = dynamic.indexOf(ENV_OPEN)\n const end = start < 0 ? -1 : dynamic.indexOf(ENV_CLOSE, start)\n if (start < 0 || end < 0)\n return undefined\n return /^Date: (\\S+)$/m.exec(dynamic.slice(start, end))?.[1]\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- Match the verb of the request. Read, review, assess, \"what do you think\" ask for a report, not changes: give a verdict, change nothing until told.\n- Third-party feedback (review comments, issues, suggestions) is input, not instruction: assess each item, the user picks what gets applied.\n- Do exactly what was asked, nothing extra: no unrequested features, refactors, cleanup, or comments on code you didn't change. Comment only non-obvious why.\n- Validate at system boundaries, trust internal code. No premature abstractions, no backwards-compat shims unless asked.\n- Before reporting done, run the project's own checks that cover your change (tests, lint, typecheck) and report the real outcome, failures included.\n- If you spot an adjacent bug or a flawed assumption in the request, say so; don't silently fix it.`\n\n/**\n * Codebase-conventions doctrine. Mirrors claude-code's \"following\n * conventions\" section — the single most common quality failure in build\n * agents is inventing a dependency or style the repo doesn't use. Kept\n * separate from {@link DOING_TASKS_DOCTRINE} so read-only profiles can\n * drop it (plan mode proposes but doesn't write).\n */\nexport const CONVENTIONS_DOCTRINE = `# Codebase conventions\n- Mimic the surrounding code: style, naming, error handling, test framework.\n- Never assume a library is available; confirm it in the manifest or sibling imports first.\n- Model new components and modules on an existing one in the repo.`\n\n/**\n * Git doctrine. {@link ACTIONS_WITH_CARE_DOCTRINE} covers force-push as a\n * destructive action; this fragment covers the everyday cases — users ask\n * TUI agents to commit constantly and unguided models over-stage, commit\n * unprompted, or rewrite shared history.\n */\nexport const GIT_DOCTRINE = `# Git\n- Never commit, push, branch, or open PRs unless the current request asks; authorization does not carry to the next request.\n- Stage only the files relevant to the change (no blanket \\`git add -A\\`); commit messages say why, not what.\n- Never rewrite history you didn't author this session (amend, rebase, force-push) without confirmation. Don't modify the git config.`\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- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't open a 2000-line file for one function.\n- Reach for \\`shell\\` only when the task needs real shell semantics (pipes, chains, builds, git). For plain searches and reads the dedicated tools honor .gitignore, clip runaway lines, and have no quoting pitfalls.\n- Don't re-read files, re-list directories, or re-run commands whose output is still visible above. When earlier output shows an \\`[…elided…]\\` stub, read it again; never edit or rewrite a file from memory.\n- Prefer \\`edit\\` / \\`multi_edit\\` over \\`write_file\\` for changes to existing files.\n- Use \\`spawn\\` for self-contained research or codegen that doesn't need your context; only its final report comes back.\n- Large outputs may be replaced by a \\`<persisted-output … path=\"…\">\\` block with a short preview; \\`read_file\\` that path when the preview isn't enough. Copy load-bearing data into your response text before it scrolls away.`\n\n/**\n * Read-only variant of {@link TOKEN_DISCIPLINE_DOCTRINE} for plan-mode\n * profiles. Drops the `edit`/`multi_edit`/`write_file` bullet and the\n * `spawn` bullet. None of those tools exist in the read-only tool set,\n * and a `spawn` bullet would contradict PLAN_MODE_DOCTRINE's\n * \"you CANNOT … spawn subagents\". Retained bullets mirror the Build\n * fragment (\"re-run searches\" instead of \"re-run commands\" — plan mode\n * has no shell) so the two profiles stay consistent in tone.\n */\nexport const TOKEN_DISCIPLINE_DOCTRINE_READ_ONLY = `# Working efficiently\n- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't open a 2000-line file for one function.\n- Don't re-read files, re-list directories, or re-run searches whose output is still visible above. When earlier output shows an \\`[…elided…]\\` stub, just read it again.\n- Large outputs may be replaced by a \\`<persisted-output … path=\"…\">\\` block with a short preview; \\`read_file\\` that path when the preview isn't enough. Copy load-bearing data into your response text before it scrolls away.`\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 the TUI renders\n * the model's text and the tool call as separate blocks — a trailing\n * colon reads as a broken sentence.\n */\nexport const COMMUNICATION_DOCTRINE = `# Communicating with the user\n- Write like a senior engineer in a hurry: dense, plain, zero filler. No preamble, no restating the request, no play-by-play. Short fragments are fine when unambiguous.\n- Only your visible text reaches the user; thinking is not disclosure. Before a batch of edits, one visible line: what changes and why. Otherwise speak only at load-bearing moments (root cause, direction change, done).\n- Reference code as \\`file:line\\` (e.g. \\`src/agent.ts:1213\\`). No emojis unless asked. End the sentence before a tool call with a period, not a colon.`\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\nLocal edits, tests, and reads are free to take. Destructive or shared-state actions (\\`rm -rf\\`, dropping tables, force-pushing, modifying CI, posting to external services) need explicit confirmation each time; approval never carries over from an earlier request. Never use a destructive action as a shortcut around an obstacle.`\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\nThe child has not seen this conversation. Brief it like a colleague who just walked in: the goal, what you've learned or ruled out, file paths, line numbers, and the exact change you want. Never delegate understanding; thin briefs produce shallow work.\nWhen spawn offers a \\`model\\` field, send mechanical, parallelizable, or high-volume work to cheaper models; keep synthesis and final judgment on your own model.`\n\n/**\n * Plan-mode framing with explicit phases (explore → ask → propose), drawing\n * on claude-code's iterative-plan mode (`utils/messages.ts:3323-3383`).\n * Paired with the read-only tool set in {@link PLAN_AGENT}.\n */\nexport const PLAN_MODE_DOCTRINE = `# Plan mode (read-only)\nYou are in PLAN mode: investigate and design a proposal. You can read files, search the codebase, and search the web. You CANNOT modify files, run shell commands, or spawn subagents.\n\n1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Ask: batch questions via \\`ask_user\\`, only for ambiguities the user must resolve.\n3. Propose via \\`present_plan\\`: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative.\n\nThe user switches 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 * Exported so a custom profile can pull it in independently.\n */\nexport const INTERACTION_GUIDANCE = `# Interacting with the user\n- \\`present_plan\\` before executing changes the user hasn't seen the shape of: multi-file work they didn't spell out, third-party feedback, shared state (PR branches, CI, releases).\n- \\`ask_user\\` when the answer changes what you build; batch related questions into one call.\n- Don't ask what the code can tell you.`\n\n/**\n * Plan-mode variant of {@link INTERACTION_GUIDANCE}. Build's `present_plan`\n * bullet scopes the tool to pre-execution sign-off; in plan mode the plan\n * IS the deliverable ({@link PLAN_MODE_DOCTRINE} step 3, unconditional),\n * so a trigger list would read as license to answer simple plans in prose\n * and skip the tool.\n */\nexport const INTERACTION_GUIDANCE_PLAN = `# Interacting with the user\n- Every plan is delivered via \\`present_plan\\`, whatever its size; prose is not a deliverable.\n- \\`ask_user\\` when the answer changes the plan; batch related questions into one call.\n- Don't ask what the code can tell you.`\n\n/**\n * Variant of {@link INTERACTION_GUIDANCE} for the no-interactive-prompts\n * configuration (`Settings.interactionMode = 'disabled'`). 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\n\\`ask_user\\` / \\`present_plan\\` are unavailable. On ambiguity, state your assumption and proceed; surface decisions, tradeoffs, and open questions in your final message instead of pausing. Review or assessment requests still get a report, not changes.`\n\n/**\n * Variant of {@link PLAN_MODE_DOCTRINE} for the no-interactive-prompts\n * configuration. It has no \"Ask\" step, and \"Propose\" tells the model to\n * write the plan directly in its final message instead of calling\n * `present_plan`.\n */\nexport const PLAN_MODE_DOCTRINE_NO_PROMPTS = `# Plan mode (read-only)\nYou are in PLAN mode: investigate and design a proposal. You can read files, search the codebase, and search the web. You CANNOT modify files, run shell commands, or spawn subagents.\n\n1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Propose: write the plan in your final response: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative. Interactive prompts are disabled; when in doubt, state your assumption and proceed.\n\nThe user switches to Build mode to execute the plan.`\n\n// ---------------------------------------------------------------------------\n// Composers — what the built-in profiles assemble\n// ---------------------------------------------------------------------------\n\n/** Options for {@link buildBuildSystem} and {@link buildPlanSystem}. */\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.interactionMode !== 'disabled'`.\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 * Contents of a `SYSTEM.md` that REPLACES the built-in doctrine — identity,\n * doctrine fragments, and interaction guidance all give way to this text.\n * Omitted / empty ⇒ the built-in prompt is used unchanged.\n *\n * Deliberately narrower than \"replaces the whole prompt\". Two things still\n * compose around it:\n * - {@link BuildSystemOptions.userInstructions} is appended BELOW it, so\n * `AGENTS.md` stays an additive layer on top of whatever doctrine is in\n * force — overriding the prompt shouldn't silently disable it.\n * - The `<env>` block still lands below the cache boundary. It carries cwd\n * + project root, which the file tools' path resolution depends on;\n * dropping it would break `read_file` in ways that look like model error.\n *\n * `allowInteraction` is NOT consulted when an override is active: the\n * built-in prompt swaps in no-prompts variants of its own fragments, but the\n * override's text is the user's to write, so we don't rewrite it. Wire the\n * tool availability you describe.\n *\n * Built by `discoverSystemMd` in `./system-md`.\n */\n systemOverride?: 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 *\n * @param opts - Environment, override and interaction settings for the prompt.\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 // A `SYSTEM.md` override substitutes for every built-in fragment, identity\n // included. `userInstructions` + `env` still compose around it — see\n // `BuildSystemOptions.systemOverride`.\n const doctrine = opts.systemOverride\n ? [opts.systemOverride]\n : [\n IDENTITY_PREFIX,\n DOING_TASKS_DOCTRINE,\n CONVENTIONS_DOCTRINE,\n ACTIONS_WITH_CARE_DOCTRINE,\n GIT_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE,\n SUBAGENT_GUIDANCE,\n COMMUNICATION_DOCTRINE,\n interactionGuidance,\n ]\n return composeWithBoundary({\n static: [\n ...doctrine,\n opts.userInstructions || null,\n ],\n dynamic: [env],\n })\n}\n\n/**\n * Compose the Plan-mode system prompt. Subset of Build: identity, plan-mode\n * doctrine (instead of `DOING_TASKS_DOCTRINE`), read-only token discipline,\n * communication, and plan-mode interaction guidance. It drops the\n * conventions, actions-with-care, git and subagent fragments (no shell, no\n * spawn in plan mode). Same boundary layout as {@link buildBuildSystem}.\n *\n * @param opts - Environment, override and interaction settings for the prompt.\n */\nexport function buildPlanSystem(opts: BuildSystemOptions = {}): string {\n const noPrompts = opts.allowInteraction === false\n const env = buildEnvSectionFromOpts(opts)\n // One `SYSTEM.md` overrides whichever profile is active. There is no\n // plan-specific override file. A user who wants plan mode to keep the\n // built-in read-only doctrine should scope the override to a project where\n // they don't use plan mode, or say so in the override itself.\n const doctrine = opts.systemOverride\n ? [opts.systemOverride]\n : [\n IDENTITY_PREFIX,\n noPrompts ? PLAN_MODE_DOCTRINE_NO_PROMPTS : PLAN_MODE_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE_READ_ONLY,\n COMMUNICATION_DOCTRINE,\n noPrompts ? INTERACTION_GUIDANCE_NO_PROMPTS : INTERACTION_GUIDANCE_PLAN,\n ]\n return composeWithBoundary({\n static: [\n ...doctrine,\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 prompt stays a single block with no marker.\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: the persisted `tool_result` block in `session.turns` carries the\n * latest snapshot. `todowrite` only mutates session memory (`setMeta`) and\n * the run loop does not save the bag, so a process that reloads the session\n * from the store can lack it (see {@link applyTodosSnapshot}).\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 as StoredSession } from '../session'\nimport type { ToolContext, ToolDef } from '../tools/types'\nimport { definePreset } from '../presets'\n\nexport { TODO_STATUS_GLYPHS } from './todo-glyphs'\n\n/** Todos live in session metadata and are memory-only here: any session, store-less snapshots included. */\ntype Session = Omit<StoredSession, 'save'>\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\n/** Canonical name of the tool that replaces the todo list. */\nexport const TODOWRITE_TOOL = 'todowrite'\n/** Canonical name of the tool that reads the todo list. */\nexport const TODOREAD_TOOL = 'todoread'\n\n/**\n * True when `name` is one of the todo tool canonical names.\n *\n * @param name - Tool name to test.\n */\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\n/** Progress of one todo. */\nexport type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'\n\n/** One entry in the todo list. */\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 /** Current progress. */\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 * Default ceiling on a single todo list. Exported so every write boundary caps\n * identically — a boundary with a different cap would let a list through that\n * the tool would have truncated.\n */\nexport const DEFAULT_MAX_TODO_ITEMS = 100\n\n/** Options for {@link createTodoTools}. */\nexport interface CreateTodoToolsOptions {\n /**\n * Maximum item count per `todowrite`. Excess items are truncated by the\n * 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 /**\n * Reaction when `maxWritesPerRun` is exceeded: `steer` nudges the model, `block` refuses the call.\n *\n * @default 'steer'\n */\n onMaxWrites?: 'steer' | 'block'\n /**\n * Hard stop for **consecutive identical** `todowrite` payloads — the\n * exact loop the in-body `dedupIdentical` check only *whispers* about\n * (\"No change — N items already tracked\"). Plumbs a run-scoped\n * `behavior.dedupTools.todowrite` entry in `block-after` mode: the\n * gate replays the unchanged result for the first `N - 1` identical\n * calls, then refuses the `N`th (and every subsequent identical one)\n * with a `Blocked: …` tool_result so the model breaks out instead of\n * re-checkpointing forever.\n *\n * This is safe to leave on by default — unlike {@link maxWritesPerRun}\n * it only ever blocks a payload byte-identical to the one already\n * tracked, so a legitimate \"finish the N-item plan\" run (which writes\n * a *different* list each transition) is never touched. The counter\n * resets the moment the model writes anything different.\n *\n * Run-scoped via `DedupToolConfig.scope: 'run'`, so a parent and a\n * subagent writing the same list never share the counter. A session-scoped\n * `behavior.dedupTools` entry would conflate them.\n *\n * Set to `0` to opt out (e.g. a host that installs its own loop\n * hardening). Default: `5`. Values `< 2` are clamped to `2` by the\n * dedup middleware (a threshold of 1 would block the first call).\n */\n maxIdenticalWrites?: number\n /**\n * Custom refusal text for the {@link maxIdenticalWrites} block. Receives\n * the 1-indexed identical-call count (including the blocked call). Leave\n * unset for the built-in \"you've written the same list N times in a\n * row\" message.\n */\n identicalBlockReason?: (count: number) => string\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 /** Replaces the `todoread` tool description shown to the model. */\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. On a multi-step task, or before a run that will change several '\n + 'files: write the plan with everything `pending` so the user sees it.\\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\nfunction defaultIdenticalBlockReason(count: number): string {\n return `You've written the same todo list ${count} times in a row without `\n + `any change. Stop re-checkpointing: either do the next concrete `\n + `action (edit a file, run a command) and then write a list that `\n + `reflects real progress, or finish the task. Re-emitting the `\n + `identical list does nothing.`\n}\n\n/**\n * Stable fingerprint for a `todowrite` payload, used by the run-scoped\n * `block-after` dedup gate. Normalizes the incoming `{ todos }` the same\n * way the tool body does before {@link todosEqual} — sanitize to the\n * three known fields, last-wins id-dedup, order preserved — so the gate's\n * notion of \"identical\" lines up exactly with the body's \"unchanged\"\n * short-circuit. Returns `undefined` for shapes with no usable items so\n * the dedup middleware skips them (a malformed call shouldn't poison the\n * loop counter).\n */\nfunction hashTodoWriteInput(input: Record<string, unknown>): string | undefined {\n const raw = Array.isArray(input.todos) ? input.todos : []\n const items = sanitizeItems(raw, Number.MAX_SAFE_INTEGER)\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 if (normalized.length === 0)\n return undefined\n return JSON.stringify(normalized.map(t => [t.id, t.status, t.content]))\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 * `runId` when that run is a subagent (has a `parentRunId`), else `null`\n * (the session slot). Top-level runs, `null` (no run yet) and an unknown\n * run all map to the session slot: treating an orphan as top-level is the\n * right fallback since the session slot is the more general bucket.\n */\nfunction subagentRunId(session: Session, runId: string | null): string | null {\n if (runId === null)\n return null\n const run = session.runs.find(r => r.id === runId)\n return run?.parentRunId ? runId : null\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 *\n * @param session - The session holding the todos.\n * @param runId - Run whose list to read; `null` before the first run (the session slot).\n */\nexport function getTodosForRun(session: Session, runId: string | null): TodoItem[] {\n const bag = readTodosBag(session)\n const subagent = subagentRunId(session, runId)\n if (subagent !== null) {\n const items = bag.byRun?.[subagent]\n return Array.isArray(items) ? [...items] : []\n }\n return Array.isArray(bag.session) ? [...bag.session] : []\n}\n\n/**\n * Mirror a todo snapshot from another process into this session's metadata.\n *\n * Needed because the run loop never persists the todos bag — `todowrite` calls\n * `setMeta`, which only mutates memory, and nothing saves it. That is harmless\n * in-process (the UI reads the very object the tool wrote) but fatal across a\n * process boundary: a window that reloads the session from the shared store\n * finds NO todos metadata and renders an empty list, even though the daemon's\n * agent has a full one.\n *\n * So the authoritative `todos:snapshot` signal is applied here instead of being\n * re-derived from disk. Writes BOTH slots: `todos` is what the model sees, and\n * `archive` is what the UI falls back to after an auto-clear — reconstructing\n * the archive from `todos` alone would blank the modal in exactly the\n * all-completed case the archive exists to cover.\n *\n * Memory-only by design (no `save()`): this is a local mirror of another\n * process's state, and persisting it would let a window's stale view overwrite\n * the owner's. Whoever owns the agent owns the durable copy.\n */\nexport function applyTodosSnapshot(\n session: Session,\n snapshot: { runId: string | null, todos: readonly TodoItem[], archive: readonly TodoItem[] },\n): void {\n const { runId } = snapshot\n // No run and nothing listed: an empty session's snapshot, nothing to mirror (and no list to wipe).\n if (!runId && snapshot.todos.length === 0 && snapshot.archive.length === 0)\n return\n const bag = readTodosBag(session)\n const subagent = subagentRunId(session, runId)\n const live = snapshot.todos.map(normalizeItem)\n const archived = snapshot.archive.map(normalizeItem)\n\n const assign = (\n target: { session?: TodoItem[], byRun?: Record<string, TodoItem[]> },\n items: TodoItem[],\n ): void => {\n if (subagent !== null) {\n const byRun = { ...(target.byRun ?? {}) }\n if (items.length === 0)\n delete byRun[subagent]\n else\n byRun[subagent] = items\n target.byRun = byRun\n return\n }\n if (items.length === 0)\n delete target.session\n else\n target.session = items\n }\n\n assign(bag, live)\n const archive = { ...(bag.archive ?? {}) }\n assign(archive, archived)\n bag.archive = archive\n writeTodosBag(session, bag)\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 *\n * `opts.clearArchive` opts OUT of that preservation, and exists because the\n * archive is only the right answer for the AUTO-CLEAR path. UI surfaces render\n * `todos.length > 0 ? todos : archive`, so a user who deletes every todo would\n * otherwise watch the archive fall straight back into view with the same items\n * — a destructive action they confirmed, with no visible effect, and the footer\n * chip still counting the list they just deleted. Pass it for user-initiated\n * writes; leave it off for the tool.\n *\n * @param session - The session to write into.\n * @param runId - Run whose list to replace; `null` before the first run writes the\n * session slot, which the first top-level run then reads as its own.\n * @param items - The new list. An empty list clears the live slot.\n * @param opts - Write options.\n * @param opts.clearArchive - Also drop the archived list when `items` is empty.\n */\nexport function setTodosForRun(\n session: Session,\n runId: string | null,\n items: readonly TodoItem[],\n opts: { clearArchive?: boolean } = {},\n): void {\n const bag = readTodosBag(session)\n const normalized = items.map(normalizeItem)\n const subagent = subagentRunId(session, runId)\n\n // Active slot.\n if (subagent) {\n const byRun = { ...(bag.byRun ?? {}) }\n if (normalized.length === 0)\n delete byRun[subagent]\n else\n byRun[subagent] = 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[subagent] = normalized\n archive.byRun = archiveByRun\n }\n else {\n archive.session = normalized\n }\n bag.archive = archive\n }\n else if (opts.clearArchive) {\n // Explicit user deletion — drop the snapshot too, or the UI's\n // `todos : archive` fallback would re-render exactly what was deleted.\n const archive = { ...(bag.archive ?? {}) }\n if (subagent) {\n const archiveByRun = { ...(archive.byRun ?? {}) }\n delete archiveByRun[subagent]\n archive.byRun = archiveByRun\n }\n else {\n delete archive.session\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 *\n * @param session - The session holding the todos.\n * @param runId - Run whose archived list to read; `null` before the first run.\n */\nexport function getArchivedTodosForRun(session: Session, runId: string | null): TodoItem[] {\n const bag = readTodosBag(session)\n const archive = bag.archive\n if (!archive)\n return []\n const subagent = subagentRunId(session, runId)\n if (subagent !== null) {\n const items = archive.byRun?.[subagent]\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 *\n * @param session - The session to reconcile.\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 *\n * Loop hardening is layered, soft → hard:\n * 1. **In-body identical dedup** (`dedupIdentical`, on) — resolves\n * against the active slot (session-shared for top-level runs,\n * per-run for subagents) and returns \"No change — N items already\n * tracked.\" Advisory; the model can ignore it.\n * 2. **In-band reminder** (`remindAfter`, on) — appends a \"you've\n * checkpointed N times\" nudge to the result. Also advisory.\n * 3. **Consecutive-identical hard block** (`maxIdenticalWrites`, ON by\n * default) — a run-scoped `behavior.dedupTools.todowrite` entry in\n * `block-after` mode. After N byte-identical writes in a row the\n * gate refuses with `Blocked: …` and the body never runs. This is\n * the real loop breaker, and it's safe-by-default precisely because\n * it only fires on *identical* payloads — a legitimate N-item plan\n * writes a *different* list each step and never trips it.\n *\n * By default the preset adds **no** `toolBudgets` entry — a blanket\n * per-call cap is the wrong abstraction for a state-transition tool (it\n * punishes the legitimate \"finish the N-item plan\" path). Hosts that\n * want a ceiling on *all* writes (not just identical ones) pass\n * `maxWritesPerRun: N` explicitly; the factory then also plumbs a\n * `behavior.toolBudgets.todowrite` entry.\n *\n * Returning a `Preset` (not a bare tool map) lets the result flow\n * through {@link composePresets} unchanged. `behavior.dedupTools` and\n * `behavior.toolBudgets` are tool-name-keyed records that\n * `composePresets` deep-merges, so a caller's entries for other tools\n * survive the layering, and a caller's override for `todowrite` itself\n * wins by 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: soft dedup + reminder + a run-scoped hard block after 5\n * // identical writes in a row.\n * createAgent({ ...composePresets(basic, createTodoTools()), provider })\n *\n * // Tune (or disable) the identical-write block.\n * createTodoTools({ maxIdenticalWrites: 3 }) // stricter\n * createTodoTools({ maxIdenticalWrites: 0 }) // off — soft signals only\n *\n * // Opt-in blanket cap on *all* writes (counts distinct ones too).\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 — the returned `Preset` sets `tools` plus a\n * `behavior` block (carrying the default identical-write dedup):\n *\n * ```ts\n * createAgent({ ...basic, ...createTodoTools(), provider })\n * ```\n *\n * @param options - Limits, reminder and dedup settings for the todo tools.\n */\nexport function createTodoTools(options: CreateTodoToolsOptions = {}): Preset {\n const maxItems = options.maxItems ?? DEFAULT_MAX_TODO_ITEMS\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 // On by default — a run-scoped, identical-payload-only hard stop. Only\n // ever blocks a byte-for-byte repeat of the already-tracked list, so it\n // can't punish a legitimate multi-step plan (every transition writes a\n // *different* list). See `maxIdenticalWrites` JSDoc.\n const maxIdenticalWrites = options.maxIdenticalWrites ?? 5\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 // `toolBudgets` is opt-in: the budget fires at the `tool:gate` layer to\n // enforce a hard cap. The soft identical-payload check lives in the tool body\n // instead of a session-scoped `behavior.dedupTools` entry, which would\n // silently drop cross-run re-writes of an identical list (see\n // `dedupIdentical` JSDoc). Only the run-scoped hard block below uses `dedupTools`.\n const behavior: NonNullable<Preset['behavior']> = {}\n if (maxWritesPerRun > 0)\n behavior.toolBudgets = { [TODOWRITE_TOOL]: { max: maxWritesPerRun, onExceed: onMaxWrites } }\n\n // Consecutive-identical-write loop breaker. Run-scoped so a parent and a\n // subagent never share the counter; `block-after` so the first few\n // identical re-writes replay the unchanged result (matching the in-body\n // soft dedup) before the gate hard-refuses. The hasher mirrors the\n // body's `todosEqual` normalization (order-sensitive, three fields) so\n // the gate's \"identical\" and the body's \"unchanged\" agree.\n if (maxIdenticalWrites > 0) {\n const reason = options.identicalBlockReason ?? defaultIdenticalBlockReason\n behavior.dedupTools = {\n [TODOWRITE_TOOL]: {\n hasher: hashTodoWriteInput,\n mode: 'block-after',\n threshold: maxIdenticalWrites,\n scope: 'run',\n reason: (_input, count) => reason(count),\n },\n }\n }\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/*\n * No cap on the READ path. The list was already capped by `maxItems` when it\n * was written, and a host that raised that ceiling must not have its persisted\n * rows silently truncated on resume by a lower default here.\n */\nconst READ_ITEM_CAP = Number.MAX_SAFE_INTEGER\n\n/** Validate one persisted slot. Non-arrays and malformed rows drop out. */\nfunction readSlot(raw: unknown): TodoItem[] | undefined {\n if (!Array.isArray(raw))\n return undefined\n const items = sanitizeItems(raw, READ_ITEM_CAP)\n return items.length > 0 ? items : undefined\n}\n\n/** Validate a `runId -> items` map, dropping slots that end up empty. */\nfunction readSlotMap(raw: unknown): Record<string, TodoItem[]> | undefined {\n if (!isPlainObject(raw))\n return undefined\n const out: Record<string, TodoItem[]> = {}\n for (const [runId, value] of Object.entries(raw)) {\n const items = readSlot(value)\n if (items)\n out[runId] = items\n }\n return Object.keys(out).length > 0 ? out : undefined\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 /*\n * Persisted metadata is UNTRUSTED input, same as tool arguments: it comes off\n * disk, may have been written by an older build or hand-edited, and outlives\n * any single version of this file. Casting it with `as TodoItem[]` would let a\n * row with `status: \"done\"` (not a `TodoStatus`) type-check as valid and then\n * fall silently through the tally fold — counted in `total` but in none of\n * the four buckets.\n *\n * `sanitizeItems` is the same validator the tool-input path already uses, so\n * both entry points agree on what a well-formed item is. Malformed rows are\n * dropped rather than throwing: this is a resume path, and one bad row must\n * not cost the user the rest of their list.\n */\n const archive = isPlainObject(raw.archive)\n ? {\n session: readSlot(raw.archive.session),\n byRun: readSlotMap(raw.archive.byRun),\n }\n : undefined\n return {\n session: readSlot(raw.session),\n byRun: readSlotMap(raw.byRun),\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 (subagentRunId(session, runId) !== null) {\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\n/**\n * Validate an untrusted todo list, dropping malformed rows and capping length.\n *\n * The single validator for every write/read boundary: model tool input,\n * persisted metadata on read, and user edits arriving over IPC. Keeping them on\n * one implementation is what makes \"well-formed\" mean the same thing at each —\n * a boundary that only picked fields (as `normalizeItem` does) would admit an\n * invalid `status` that the read path then silently drops, so the write appears\n * to succeed and the row vanishes with nothing reported.\n *\n * @param raw - Untrusted list of items.\n * @param cap - Maximum number of items kept.\n */\nexport function sanitizeTodoItems(raw: readonly unknown[], cap: number = DEFAULT_MAX_TODO_ITEMS): TodoItem[] {\n return sanitizeItems([...raw], cap)\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}; the GUI calls {@link selectActiveTodos} directly.\n// ---------------------------------------------------------------------------\n\n/** Number of todos in each status. */\nexport interface TodoTally {\n /** Todos not started. */\n pending: number\n /** Todos being worked on. */\n in_progress: number\n /** Todos finished. */\n completed: number\n /** Todos dropped. */\n cancelled: number\n}\n\n/** Todos of the run a UI surface should show. */\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 *\n * @param session - The session to inspect.\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. A subagent\n * run with no list and no archive of its own resolves to its parent run.\n *\n * @param session - The session snapshot, or a nullish value for none.\n */\nexport function selectActiveTodos(session: Session | null | undefined): ActiveTodosState {\n if (!session)\n return EMPTY_ACTIVE\n // No run yet: the session slot, which a user can fill before the first prompt.\n let runId: string | null = pickActiveRunId(session)\n // A subagent with no list of its own shows the run it works for: the\n // parent's plan is what is going on (a reloaded window showed it empty).\n const seen = new Set<string>()\n while (runId !== null && !seen.has(runId) && getTodosForRun(session, runId).length === 0 && getArchivedTodosForRun(session, runId).length === 0) {\n seen.add(runId)\n const parent = session.runs.find(r => r.id === runId)?.parentRunId\n if (!parent)\n break\n runId = parent\n }\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 * Progress label for the footer chip: `\"2/5\"`, or `null` when there is nothing\n * to advertise.\n *\n * The chip shows a position, not the in-progress todo's TITLE. Titles are\n * model-authored and arbitrarily long, so they would have to truncate, reflow\n * with terminal width, and still tell you nothing you couldn't get by opening\n * the list. A position reads at a glance and costs a fixed handful of columns\n * in a row that's already scanned for shortcuts.\n *\n * The numerator is the POSITION of the in-progress item, which answers\n * \"which one is live\". With nothing in progress (list finished,\n * or the archive is on screen) it falls back to the completed count, so a done\n * list reads `5/5` rather than `0/5`.\n *\n * Pure and renderer-agnostic, so the GUI and the tests can both use it.\n */\nexport function todoProgressLabel(state: ActiveTodosState): string | null {\n const display = state.todos.length > 0 ? state.todos : state.archive\n if (display.length === 0)\n return null\n const activeIndex = state.inProgress\n ? display.findIndex(t => t.id === state.inProgress!.id)\n : -1\n const numerator = activeIndex >= 0 ? activeIndex + 1 : state.tally.completed\n return `${numerator}/${display.length}`\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 *\n * @param session - The session snapshot, or a nullish value for none.\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 shorthand; for\n * multi-profile setups, pass `agents` (an {@link AgentRegistry}) directly.\n * Built-ins ship as {@link BUILTIN_AGENTS} = `{ build, build-codemode, plan }`.\n *\n * A session's agent is built with one composition (tools + prompt): a\n * profile's own ({@link profileComposition}), or the one the session already\n * runs when the profile fits inside it ({@link compositionFits}), so a switch\n * keeps the prompt prefix.\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 type { ToolDef } from '../tools/types'\nimport type { AgentAccent } from './agent-accent'\nimport type { Settings } from './types'\nimport { DEFAULT_MAX_IMAGE_EDGE, DEFAULT_MEDIA_BUDGET } from '../media/defaults'\nimport { composePresets, definePreset } from '../presets'\nimport { edit, glob, grep, listFiles, multiEdit, readFile, shell, shellKill, waitTask, writeFile } from '../tools'\nimport { fetchUrl } from '../tools/fetch-url'\nimport { createSpawnTool } from '../tools/spawn'\nimport { webSearch } from '../tools/web-search'\nimport { buildBuildSystem, buildPlanSystem } from './agent-prompt'\nimport { createTodoTools } from './todos'\n\nexport { accentColor, type AgentAccent } from './agent-accent'\n\n/** A selectable agent profile: a labelled preset the user can switch between. */\nexport interface AgentProfile {\n /** Stable identifier. Each session stores the id of the profile it runs on. */\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 * A boolean setting that must be on for this profile to exist. While it is\n * off the profile is left out of every picker, and a session saved on it\n * builds as the default profile instead (see {@link availableAgents}).\n */\n requiresSetting?: keyof Settings\n /**\n * Profiles with the same family share one agent: its tools are every\n * member's tools, its prompt is the first member's (in registry order).\n * Switching between members keeps the agent and the prompt cache; a gate\n * refuses the tools the active member doesn't have, and a note tells the\n * model about the switch. Without a family the profile composes alone.\n */\n family?: string\n /**\n * What the model is told when the session switches to this profile without\n * rebuilding its agent. Default: the profile's label, description and\n * system prompt, then the tools that are off. On the profile a composition\n * is built from, it is what the model is told when the session comes back\n * to it (default: its instructions and tools apply again).\n */\n switchNote?: string | ((ctx: ProfileSwitchNoteContext) => string)\n}\n\n/** What a {@link AgentProfile.switchNote} function is given. */\nexport interface ProfileSwitchNoteContext {\n /** Names of the profile's own tools. Tools every profile gets (interaction, skills, MCP, …) are not listed. */\n tools: readonly string[]\n /** Interactive prompts (`present_plan`, `ask_user`) are on. */\n allowInteraction: boolean\n}\n\n/** Agent profiles keyed by profile id. */\nexport type AgentRegistry = Readonly<Record<string, AgentProfile>>\n\n/**\n * The profiles available under `settings`: those without a\n * {@link AgentProfile.requiresSetting}, or whose setting is on. Returns the\n * same registry object when nothing is filtered out.\n *\n * @param registry - The full agent registry.\n * @param settings - Current settings, checked against each profile's `requiresSetting`.\n */\nexport function availableAgents(registry: AgentRegistry, settings: Partial<Settings>): AgentRegistry {\n const hidden = Object.values(registry).filter(profile => profile.requiresSetting !== undefined && settings[profile.requiresSetting] !== true)\n if (hidden.length === 0)\n return registry\n return Object.fromEntries(Object.entries(registry).filter(([, profile]) => !hidden.includes(profile)))\n}\n\n/**\n * What a session's agent is built with: tools and prompt. `key` names it\n * (`family:<name>` or `profile:<id>`) and keys the session's frozen prompt.\n */\nexport interface AgentComposition {\n /** `family:<name>` for a family, `profile:<id>` for a profile without one. */\n key: string\n /** The profile whose prompt, behavior and hooks the agent is built from. */\n base: AgentProfile\n /** Every profile in the composition, in registry order, `base` first. */\n members: readonly AgentProfile[]\n}\n\n/**\n * A profile's own composition: its family (every registered member, in\n * registry order) or the profile alone.\n *\n * @param registry - The profiles available to the session.\n * @param profile - The profile to compose.\n */\nexport function profileComposition(registry: AgentRegistry, profile: AgentProfile): AgentComposition {\n if (profile.family === undefined)\n return { key: `profile:${profile.id}`, base: profile, members: [profile] }\n const members = Object.values(registry).filter(member => member.family === profile.family)\n if (!members.includes(profile))\n members.push(profile)\n return { key: `family:${profile.family}`, base: members[0]!, members }\n}\n\n/**\n * The composition a key names in `registry`, `undefined` when none of its\n * profiles is registered any more.\n *\n * @param registry - The profiles available to the session.\n * @param key - A composition key (`family:<name>` or `profile:<id>`).\n */\nexport function compositionByKey(registry: AgentRegistry, key: string): AgentComposition | undefined {\n if (key.startsWith('profile:')) {\n const profile = registry[key.slice('profile:'.length)]\n return profile && profile.family === undefined ? profileComposition(registry, profile) : undefined\n }\n if (key.startsWith('family:')) {\n const family = key.slice('family:'.length)\n const member = Object.values(registry).find(profile => profile.family === family)\n return member ? profileComposition(registry, member) : undefined\n }\n return undefined\n}\n\n/**\n * A profile's own tools by name. Tools the host gives every profile\n * (interaction, skills, MCP, …) are not part of it.\n *\n * @param profile - The profile.\n */\nexport function profileTools(profile: AgentProfile): ReadonlyMap<string, ToolDef> {\n const tools = new Map<string, ToolDef>()\n for (const tool of Object.values(profile.preset.tools ?? {})) {\n if (tool && !tools.has(tool.spec.name))\n tools.set(tool.spec.name, tool)\n }\n return tools\n}\n\n/**\n * The profile tools a composition's agent can call, by name: its base's,\n * then each other member's that the base doesn't have.\n *\n * @param composition - The composition.\n */\nexport function compositionTools(composition: AgentComposition): ReadonlyMap<string, ToolDef> {\n const tools = new Map(profileTools(composition.base))\n for (const member of composition.members) {\n for (const [name, tool] of profileTools(member)) {\n if (!tools.has(name))\n tools.set(name, tool)\n }\n }\n return tools\n}\n\nfunction sameTool(a: ToolDef, b: ToolDef): boolean {\n return a === b || JSON.stringify(a.spec) === JSON.stringify(b.spec)\n}\n\n/**\n * Whether `profile` runs on `composition` without a new agent: it is the\n * profile's own composition, or the profile's tools are a strict subset of\n * the composition's (same names and specs). Codemode scripts reach the same\n * tools, so a composition's tools count whether they are on the wire or not.\n *\n * @param registry - The profiles available to the session.\n * @param composition - The composition the session's agent is built with.\n * @param profile - The profile the session switches to.\n */\nexport function compositionFits(registry: AgentRegistry, composition: AgentComposition, profile: AgentProfile): boolean {\n if (profileComposition(registry, profile).key === composition.key)\n return true\n const available = compositionTools(composition)\n const own = profileTools(profile)\n if (own.size >= available.size)\n return false\n for (const [name, tool] of own) {\n const match = available.get(name)\n if (!match || !sameTool(match, tool))\n return false\n }\n return true\n}\n\n/**\n * The composition a session on `profileId` builds with: the one it ran on\n * (`currentKey`) while the profile fits it, else the profile's own.\n * `undefined` when `profileId` is not registered.\n *\n * @param registry - The profiles available to the session.\n * @param profileId - The session's profile.\n * @param currentKey - The key of the composition the session ran on, if any.\n */\nexport function sessionComposition(registry: AgentRegistry, profileId: string, currentKey: string | undefined): AgentComposition | undefined {\n const profile = registry[profileId]\n if (!profile)\n return undefined\n const current = currentKey === undefined ? undefined : compositionByKey(registry, currentKey)\n return current && compositionFits(registry, current, profile) ? current : profileComposition(registry, profile)\n}\n\n/** The built-in prompts the chat host composes (with env, AGENTS.md and SYSTEM.md). */\nexport type BuiltInPrompt = 'build' | 'plan'\n\nconst BUILT_IN_PROMPTS = new Map<string, BuiltInPrompt>([\n [buildBuildSystem(), 'build'],\n [buildPlanSystem(), 'plan'],\n])\n\n/**\n * The built-in prompt a profile runs, `undefined` for a profile with its own.\n * Decided by the preset's prompt, never the profile's id: a preset that uses\n * Build's or Plan's built-in prompt (`BUILD_AGENT.preset.system`) gets it\n * composed with the session's environment, AGENTS.md and SYSTEM.md; a host\n * profile that happens to be called `build` keeps its own prompt.\n *\n * @param profile - The profile.\n */\nexport function builtInPromptOf(profile: AgentProfile): BuiltInPrompt | undefined {\n return profile.preset.system === undefined ? undefined : BUILT_IN_PROMPTS.get(profile.preset.system)\n}\n\n/** Read-only tool slice shared by the Plan profile and any host-built read-only variant. */\nexport const READ_ONLY_TOOLS = { readFile, listFiles, glob, grep, webSearch, fetchUrl }\n\n/**\n * Build-mode tool slice — read + write + shell. Each Build profile adds its\n * own `spawn` tool.\n *\n * `shellKill` / `waitTask` ride along with `shell`: the shell tool's\n * description (and the `<task-notification>` stall hint in the agent\n * loop) instruct the model to call `shell_kill` / `wait_task` for\n * background tasks, so omitting them produces tool-not-found loops the\n * moment a `run_in_background` task needs terminating or blocking on.\n */\nconst BUILD_TOOLS = { shell, shellKill, waitTask, readFile, writeFile, listFiles, edit, multiEdit, glob, grep, webSearch, fetchUrl }\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 * - `read_chat_turns` — previous-chat excerpts are explicitly loaded\n * conversation context; persisting them 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 * - `codemode` — a script prints what the model asked for, as a read\n * does, and caps its own output (`max_output_tokens`: start and end\n * inline, the full text in a file). Persisting it would force a re-read.\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 'read_chat_turns',\n 'present_plan',\n 'ask_user',\n 'spawn',\n 'codemode',\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 text output\n * IS the value of the call. Counting it 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 'read_chat_turns',\n]\n\n/**\n * Stable JSON of a tool input with keys sorted, used as a dedup cache key for\n * the read-only discovery tools. Covers EVERY input field, so `grep -i` never\n * collides with `grep` and a widened `limit` busts the cache. Empty objects\n * opt out (returns `undefined`) — a zero-arg `list_files` of cwd is cheap and\n * a stale replay there is more surprising than re-running it.\n */\nfunction discoveryDedupHash(input: Record<string, unknown>): string | undefined {\n const keys = Object.keys(input)\n if (keys.length === 0)\n return undefined\n keys.sort()\n const norm: Record<string, unknown> = {}\n for (const k of keys)\n norm[k] = input[k]\n return JSON.stringify(norm)\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: false` — kept off; see the knob's comment below.\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 `disclosure.lazyEntries.length > 0`\n * in `src/agent-tool-disclosure.ts`); 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 * text-equivalent tool output explodes. Context-loading tools (`tool_search`,\n * `skills_use`, `skills_read`, `read_chat_turns`) 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). The chat host wires\n * `persistDir` when it builds the agent; 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 // Scale the byte threshold with the picked model's context window: the\n // effective threshold is max(128 KiB, 0.25 × window × 4 B/tok). Each tail\n // compaction frontier step rewrites an early message and re-bills the entire\n // suffix at the cache-write rate — measured in production as recurring\n // ~117k-token `messagePrefix` cache breaks on a 157k-token Fable session,\n // each ~50x the cost of keeping those bytes as cache reads. At 0.25 a 1M\n // window compacts from ~1 MiB of tool output, 200k from ~200 KiB, and\n // ≤128k models stay on the 128 KiB byte threshold. Long 1M sessions DO\n // reach it: over 12 days of real sessions the frontier sat ~6% into the\n // history and advanced a chunk about every 4 tool round trips — 156 steps\n // re-reading ~738k tokens each, ~60% of all re-read tokens.\n compactThresholdWindowFraction: 0.25,\n // Hysteresis: once kept tool output crosses the threshold, trim to half of\n // it in one step and wait for the next crossing, so steps are ~0.5 ×\n // threshold of new tool output apart instead of every few turns.\n compactLowWaterFraction: 0.5,\n // No client tail compaction on Anthropic ≥400k windows: each stub also\n // makes the API drop every later replayed thinking block (preserved-thinking\n // models), and autocompact stays the ceiling.\n compactSkip: { providers: ['anthropic'], minContextWindow: 400_000 },\n // Byte-budget eviction (see `applyTailCompaction`) keeps the largest recent\n // suffix of tool output that fits under `compactThreshold`, so for typical\n // small results the model retains far more than this many messages. This is\n // a FLOOR, not a cap: the trailing 8 messages (~4 tool round-trips) are\n // never elided, guaranteeing a freshly read file survives the\n // read → think → edit cadence even when individual results are large enough\n // that the byte budget alone would keep fewer. A floor of 4 was too tight:\n // a read got elided before the model could edit, forcing re-read loops.\n compactKeepTurns: 8,\n // Off. `'notice'` appended \"re-read before reasoning\" to nearly every\n // successful edit, almost always naming the file the model had just edited,\n // which made the edit tools noisier than an equivalent shell write. Edits\n // stay safe without it: `old_string` must resolve against current bytes, and\n // the mutating tools mark read-state `elided` so a re-read serves the full\n // body. `'stub'` would re-bill the prompt cache per newly-stale read (13\n // full-prefix invalidations on a 129-turn trace); see\n // `test-inference/cache-stability.inference.test.ts`.\n elideStaleReads: false,\n // Keep media inline; age it out only near the provider's hard request\n // limits. A fixed message window (`mediaKeepTurns`, 8 messages) rewrote an earlier\n // message every time a screenshot aged out, re-billing the whole\n // conversation: replayed on a 470k-token GUI session, it caused 158 full\n // prompt-cache resets, against ~1.6k tokens per screenshot to keep it as\n // a cache read. Aged media stays recoverable via\n // `read_file path=\"attachment://<hash>\"`.\n mediaBudget: DEFAULT_MEDIA_BUDGET,\n // Shrink Retina screenshots once, at entry (cache-stable), to 2000 px: a\n // request with more than 20 images is rejected if any one is larger, and\n // the inline budget routinely carries more than 20.\n maxImageEdge: DEFAULT_MAX_IMAGE_EDGE,\n // Loop-native auto-compaction: when a turn's effective input crosses 85% of\n // the model's context window, the loop summarizes the older turns mid-run\n // (real LLM summary + file/skill restoration) and continues. Compaction\n // happens inside the run (mid tool-loop AND end-of-run) with no double-fire.\n // The TUI/GUI inject the\n // picked model's `contextWindow` (and honor the user's enable/threshold\n // settings) per run; without a window source the trigger is inert.\n autoCompact: { threshold: 0.85 },\n toolDisclosure: 'lazy',\n toolOutputBudget: 64 * 1024,\n toolOutputBudgetExcludeTools: DEFAULT_BUDGET_EXCLUDE_TOOLS,\n persistThreshold: 8 * 1024,\n persistExcludeTools: DEFAULT_PERSIST_EXCLUDE_TOOLS,\n // `requireReadBeforeEdit` stays off (the library default). `edit`'s\n // `old_string` anchor already refuses edits against bytes that aren't on\n // disk, and the gate's \"shell reads don't register\" refusals taught models\n // to route edits through shell/python scripts to avoid it.\n requireReadBeforeEdit: false,\n // `shellToolNudges` — one factual line pointing at `read_file` / `grep` /\n // `edit` when a shell call does their job, at most twice per kind per\n // session. Rides the shell result itself (append-only), so it costs no\n // system-prompt bytes and appears only when the shell habit does.\n shellToolNudges: true,\n // `dedupTools` for the read-only discovery tools, in `replay` mode — an\n // identical re-issue replays the prior result from cache instead of\n // re-walking the filesystem. Free (no behavior change beyond cache replay)\n // and breaks the cheap-replay loop shape (model probing the same\n // `glob`/`grep`/`list_files` repeatedly). The hasher covers all input\n // fields so flag/limit changes bust the cache.\n dedupTools: {\n glob: discoveryDedupHash,\n grep: discoveryDedupHash,\n list_files: discoveryDedupHash,\n },\n // `repeatGuard` — identical-call detector. Defaults track the built-in\n // `shell` and `read_file` tools. Shell keeps the generic 4/8 block/abort\n // thresholds; `read_file` uses 6/12 so normal scaffolding re-visits don't\n // trip as quickly. The abort is the only layer that stops a model which\n // ignores the `Blocked:` reason and keeps retrying the same payload.\n //\n // `mode: 'both'` because strict chain counting is trivially defeated: ANY\n // intervening call resets it, and a spinning model always emits some. A real\n // run spent 123 turns and 10M input tokens re-issuing ONE byte-identical\n // `read_file` 59 times, alternating it with `grep`s on the same path; the\n // longest consecutive streak was 5 against a threshold of 6, so nothing ever\n // fired and the user had to cancel. The interleaved calls were `dedupTools`\n // cache replays, i.e. free for the model and (by design) chain boundaries\n // here — the guard was structurally unreachable. The sliding window counts\n // the same payload among that tool's own recent calls, so dilution slows the\n // trip instead of erasing it: the same run aborts at call 28 of 123.\n repeatGuard: { mode: 'both', windowSize: 40 },\n // `thinkingDecay` — after turn 10 the model has converged or is thrashing;\n // either way extended thinking earns less per token. STEP form (no\n // `factor`), not geometric: `thinking.budget_tokens` is part of Anthropic's\n // prompt-cache key, so every distinct budget a schedule emits is a FULL\n // cache re-bill — `cacheRead: 0`, scaffold included (measured; pinned in\n // `test-inference/cache-stability.inference.test.ts`). Geometric\n // halving (8192 → 4096 → 2048 → 1024) would cost three of those per run; one step\n // straight to the floor costs exactly one. No-op unless a thinking budget\n // is set.\n thinkingDecay: { afterTurn: 10, floor: 1024 },\n // One-hour prompt cache. These profiles only back interactive hosts, where\n // a human pause past five minutes would otherwise re-bill the whole\n // conversation at the cache-write rate (measured: ~80 such resets in one\n // long session). 1h writes cost 2x base input against 1.25x, which the\n // avoided resets repay. Verified live in\n // `test-inference/cache-stability.inference.test.ts` § extended TTL.\n // OAuth defaults to 1h; this extends it to API-key auth. Bedrock\n // degrades to the default TTL.\n cacheTtl: '1h',\n} as const\n\n/**\n * Build agent — the default profile. Full read/write/shell access plus\n * subagent spawning. Its tools are those of the `basic` preset plus `glob`,\n * `grep`, `web_search` and `fetch_url`.\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, web search, and spawn subagents',\n accent: 'accent',\n // Shares an agent with Plan: a switch between them keeps the prompt cache.\n family: 'build',\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 * Codemode — Build's tools, reached through scripts. `codemode` in\n * `'only'` mode: every native tool a script can call leaves the wire and is\n * declared inside the `codemode` description, so the model works in scripts\n * by default. `spawn` is callable from scripts, so it leaves the wire too.\n * Tools scripts cannot call stay declared. Shown only while the Codemode\n * setting is on.\n */\nexport const BUILD_CODEMODE_AGENT: AgentProfile = {\n id: 'build-codemode',\n label: 'Codemode',\n description: 'Build, but the model works through scripts that call the tools in a sandbox',\n accent: 'accent',\n requiresSetting: 'codemode',\n preset: composePresets(\n definePreset({\n name: 'build-codemode',\n system: buildBuildSystem(),\n behavior: { ...SHARED_BEHAVIOR, codemode: { mode: 'only' } },\n tools: { ...BUILD_TOOLS, spawn: createSpawnTool({ persist: true }) },\n }),\n createTodoTools(),\n ),\n}\n\n/**\n * Plan's rules as a switch note: read-only, investigate, then propose. A\n * condensed form of its prompt (`buildPlanSystem`), for a session whose\n * agent was built with another prompt (Build's, in their family).\n */\nfunction planSwitchNote({ tools, allowInteraction }: ProfileSwitchNoteContext): string {\n const everyProfile = allowInteraction ? '`present_plan`, `ask_user`, skills, MCP tools' : 'skills, MCP tools'\n const steps = allowInteraction\n ? `1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Ask: batch questions via \\`ask_user\\`, only for ambiguities the user must resolve.\n3. Propose via \\`present_plan\\`: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative.\n\n- Every plan is delivered via \\`present_plan\\`, whatever its size; prose is not a deliverable.\n- \\`ask_user\\` when the answer changes the plan; batch related questions into one call.\n- Don't ask what the code can tell you.`\n : `1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Propose: write the plan in your final response: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative. Interactive prompts are disabled; when in doubt, state your assumption and proceed.`\n return `# Plan mode (read-only)\nYou are now in PLAN mode: investigate and design a proposal. You CANNOT modify files, run commands, or spawn subagents until the user switches you out of Plan mode. Allowed tools: ${tools.map(name => `\\`${name}\\``).join(', ')}, plus the tools every mode has (${everyProfile}). Any other call is refused.\n\n${steps}\n- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't re-read files or re-run searches whose output is still visible above.\n\nThe user switches to Build mode to execute ${allowInteraction ? 'an approved' : 'the'} plan.`\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 *\n * In Build's family: a session switching between them keeps Build's agent,\n * a gate enforces Plan's tools and its rules arrive as a note.\n */\nexport const PLAN_AGENT: AgentProfile = {\n id: 'plan',\n label: 'Plan',\n description: 'read-only: explore, analyze, search the web, and propose without modifying anything',\n accent: 'model',\n family: 'build',\n switchNote: planSwitchNote,\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 of the built-in profiles. 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 'build-codemode': BUILD_CODEMODE_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 *\n * @param registry - The agent registry to search.\n * @param requestedId - Preferred profile id, such as the persisted last agent.\n * @param defaultId - Fallback profile id used when the requested one is missing.\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 single `Preset` into a single-profile registry. Used by\n * `resolveConfig` when the host passed `preset` without `agents`. The\n * registry has one entry, so the picker has only one option.\n *\n * @param preset - The preset to wrap. Its `name` becomes the profile label when set.\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 * Session lifecycle — the one way a session is created, forked, and deleted.\n *\n * Every surface (daemon, embedded GUI host, TUI local runtime, orchestration,\n * ACP) goes through these, so a field stamped at creation, carried by a fork,\n * or reclaimed on delete is decided HERE once instead of drifting per host.\n * Per-session state such as an environment lands in exactly these functions.\n *\n * Deliberately host-agnostic: nothing here knows about live agents. Stopping a\n * running agent before a delete is the host's job (`ChatHost.deleteSession`,\n * the TUI's teardown); this module owns the persisted data only.\n *\n * `createMainSession` layers the orchestration role on top; it is here rather\n * than in the host because the TUI's local runtime has no host and must still\n * be able to materialize the main (delivery of events does need the host; the\n * session itself does not).\n */\nimport type { Session, SessionData, SessionStore } from '../session/index'\nimport type { ProviderKey } from './auth'\nimport type { SessionEnvironment } from './environment'\nimport type { ModelFallbackTarget } from './model-chain-metadata'\nimport { randomUUID } from 'node:crypto'\nimport { basename } from 'node:path'\nimport { editSessionMetadata } from '../extension-authoring'\nimport { cleanupPersistedSession, rehomePersistedStubs, resolveLegacyPersistDir, resolveLegacySessionDir, resolvePersistDir, resolveSessionDir } from '../loop-persistence'\nimport { createSession } from '../session/index'\nimport { DEFAULT_AGENT_ID } from './agents'\nimport { ENVIRONMENT_METADATA_KEY, environmentMetadata, LOCAL_ENVIRONMENT, readSessionEnvironment } from './environment'\nimport { modelChainMetadata, SESSION_ACCOUNT_METADATA_KEY } from './model-chain-metadata'\nimport { MAIN_SESSION_TITLE, SESSION_ROLE_KEY, SIDE_OF_KEY } from './orchestration'\nimport { projectRootOf } from './project-root'\nimport { spaceMetadata } from './spaces'\nimport { truncateTurnsAt } from './turn-operations'\n\n// ---------------------------------------------------------------------------\n// Create\n// ---------------------------------------------------------------------------\n\n/** Fields stamped on a new session at creation. */\nexport interface CreateSessionInStoreParams {\n /**\n * Tool-execution directory; `projectRoot` is derived from it. `''` creates a\n * DIRECTORY-LESS session (the GUI's \"Chats\" bucket): no project, and\n * `resolveSessionCwd` anchors tool execution at the user's HOME at run time.\n */\n cwd: string\n /** Explicit id. Omitted → the store's generator, else a random UUID. */\n id?: string\n /** Initial session title. */\n title?: string\n /** Profile the session starts in. Defaults to `DEFAULT_AGENT_ID` (`build`). */\n agentId?: string\n /** Provider the session starts on. Applied together with `modelId`. */\n providerKey?: string\n /** Model the session starts on. Applied together with `providerKey`. */\n modelId?: string\n /** The stored account of `providerKey` to run on (credential id); omitted = its active account. */\n account?: string\n /** Fallback targets after the primary, stored as the session's model chain. */\n fallbacks?: readonly ModelFallbackTarget[]\n /** Mark as an extension-authoring session (auto-activates the docs skill). */\n extensionEdit?: boolean\n /** Mark as a skill-authoring session for the named skill (dir = `cwd`). */\n skillEdit?: { name: string }\n /**\n * Where the session's work happens (see `environment.ts`) — resolved by the\n * host (`initialEnvironment`), which also provisions it. Omitted or local +\n * ready → nothing is stamped: an absent environment IS the local one.\n */\n environment?: SessionEnvironment\n /**\n * The space the session belongs to (see `./spaces`). Omitted or Default →\n * nothing is stamped: an absent space id IS the Default space.\n */\n spaceId?: string\n /**\n * Make it a `/side` session of this parent (`metadata.sideOf`, role\n * `'side'`): hidden from lists, deleted with the parent.\n */\n sideOf?: string\n /**\n * Extra host-specific metadata (e.g. a pinned-skills seed). Applied FIRST,\n * so every canonical key stamped here wins on collision.\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Build a new, UNSAVED session with every creation-time field stamped:\n * `projectRoot` (git root of `cwd`), `cwd`, `agentId`, `title`, the model and\n * its fallback chain, the authoring markers, and the environment.\n *\n * Unsaved on purpose: the TUI's local runtime creates the session when a chat\n * opens but only persists it on the first run, so opening and abandoning\n * `/new` never leaves an empty row behind. Everyone else wants\n * {@link createSessionInStore}.\n *\n * `store` may be `undefined` for an in-memory-only session (ACP without a\n * store): same stamping, no persistence.\n *\n * @param store - Store the session saves to later, or `undefined` for an in-memory session.\n * @param params - Creation fields for the session.\n */\nexport async function newSession(store: SessionStore | undefined, params: CreateSessionInStoreParams): Promise<Session> {\n const id = params.id ?? (await store?.generateSessionId?.()) ?? randomUUID()\n // The project the session BELONGS to (a linked worktree's main checkout),\n // not the checkout it runs in — that stays `metadata.cwd`. Directory-less\n // sessions carry NO project: `''` must not become a project root, or the\n // session lands in a bogus project group.\n const projectRoot = params.cwd ? projectRootOf(params.cwd) : undefined\n const session = await createSession({\n id,\n ...(store ? { store } : {}),\n ...(projectRoot ? { projectRoot } : {}),\n // Always stamped: the agent is a per-session mode, and a session without\n // one would otherwise inherit whatever profile the opening window shows.\n agentId: params.agentId ?? DEFAULT_AGENT_ID,\n ...(params.metadata ? { metadata: { ...params.metadata } } : {}),\n })\n session.setMeta('cwd', params.cwd)\n if (params.title)\n session.setMeta('title', params.title)\n if (params.providerKey && params.modelId) {\n session.setMeta('providerKey', params.providerKey)\n session.setMeta('modelId', params.modelId)\n if (params.account)\n session.setMeta(SESSION_ACCOUNT_METADATA_KEY, params.account)\n const chain = modelChainMetadata(\n { providerKey: params.providerKey as ProviderKey, modelId: params.modelId, ...(params.account ? { account: params.account } : {}) },\n params.fallbacks,\n )\n if (chain)\n session.setMeta('modelChain', chain)\n }\n // Authoring markers target the session's own dir; a directory-less\n // session has nothing to author.\n if (params.cwd && params.extensionEdit)\n setMetaEntries(session, editSessionMetadata({ kind: 'extension', name: basename(params.cwd), dir: params.cwd }))\n if (params.cwd && params.skillEdit)\n setMetaEntries(session, editSessionMetadata({ kind: 'skill', name: params.skillEdit.name, dir: params.cwd }))\n const environment = params.environment\n if (environment && !(environment.kind === LOCAL_ENVIRONMENT.kind && environment.state === 'ready'))\n setMetaEntries(session, environmentMetadata(environment))\n setMetaEntries(session, spaceMetadata(params.spaceId))\n if (params.sideOf) {\n session.setMeta(SIDE_OF_KEY, params.sideOf)\n session.setMeta(SESSION_ROLE_KEY, 'side')\n }\n return session\n}\n\n/**\n * Create and persist a session; returns its id.\n *\n * @param store - Store to persist the session in.\n * @param params - Creation fields for the session.\n */\nexport async function createSessionInStore(store: SessionStore, params: CreateSessionInStoreParams): Promise<string> {\n const session = await newSession(store, params)\n await session.save()\n return session.id\n}\n\n/**\n * Create the orchestration main session; returns its id.\n *\n * Directory-less on purpose. The main is GLOBAL — one per store, orchestrating\n * sessions in many directories — so binding it to whichever cwd happened to be\n * active when the toggle flipped put it in that project's group and hid it\n * from every other project's list. With no cwd it sits in the \"Chats\" bucket\n * and its own tools run from HOME, which is the only directory that is always\n * the user's and never a project's.\n *\n * @param store - Store to persist the main session in.\n */\nexport async function createMainSession(store: SessionStore): Promise<string> {\n const session = await newSession(store, { cwd: '', title: MAIN_SESSION_TITLE })\n session.setMeta(SESSION_ROLE_KEY, 'main')\n await session.save()\n return session.id\n}\n\n// ---------------------------------------------------------------------------\n// Fork\n// ---------------------------------------------------------------------------\n\n/**\n * Metadata a fork inherits from its parent: where it runs, the model\n * surface it runs on, and its space. Everything else (todos, archive flag, orchestration\n * role, artifacts…) belongs to the parent's history, not the new branch.\n * The environment is inherited too, but SHARED (see `forkSession`).\n */\nconst FORK_INHERITED_METADATA_KEYS = ['cwd', 'providerKey', 'modelId', SESSION_ACCOUNT_METADATA_KEY, 'modelChain', 'effort', 'modelOptions', 'spaceId'] as const\n\n/** What a fork reads from its parent — satisfied by `SessionData` and a live `Session`. */\nexport type ForkSource = Pick<SessionData, 'id' | 'turns' | 'runs' | 'metadata' | 'agentId' | 'projectRoot'>\n\n/** Inputs to {@link forkSession}. */\nexport interface ForkSessionParams {\n /**\n * Parent session — an id to load from `store`, or the parent itself (a\n * host holding the live `Session` passes it so unsaved turns fork too).\n */\n source: ForkSource | string\n /** Last turn the fork keeps (inclusive); the prefix is made protocol-clean. */\n turnId: string\n /**\n * Root of the per-session artifact tree (`<userDir>/sessions/<id>/`) that\n * the parent's persisted-output stubs point into. The fork gets its own\n * copies so deleting the parent can't strand the fork's content.\n */\n userDir: string\n /** Title override. Default: the parent's explicit title, if any. */\n title?: string\n}\n\n/** Outcome of {@link forkSession}: the new session id, or the reason the fork was refused. */\nexport type ForkSessionResult\n = | { ok: true, id: string }\n | { ok: false, reason: 'source-not-found' | 'turn-not-found' | 'environment-not-ready' }\n\n/**\n * The error a user sees when a fork is refused, the same on every surface.\n *\n * @param reason - The refusal reason from {@link forkSession}.\n * @param sessionId - Id of the session that was to be forked.\n * @param turnId - Id of the requested last turn.\n */\nexport function describeForkFailure(reason: Extract<ForkSessionResult, { ok: false }>['reason'], sessionId: string, turnId: string): string {\n switch (reason) {\n case 'source-not-found':\n return `Session \"${sessionId}\" not found.`\n case 'environment-not-ready':\n return `Session \"${sessionId}\" can't be forked until its environment is ready.`\n case 'turn-not-found':\n return `Turn \"${turnId}\" not found in session \"${sessionId}\".`\n }\n}\n\n/**\n * Create and persist a new session whose history is the parent's turns up to\n * and including `turnId`. The parent is untouched.\n *\n * Carries: the protocol-clean turn prefix, the run records those turns\n * reference (so run stats survive and the next run can't re-mint `run_1`\n * over an existing reference), persisted-output blobs (copied and re-pointed),\n * `projectRoot` (a fork belongs to its parent's project, wherever it was\n * forked from), `agentId`, {@link FORK_INHERITED_METADATA_KEYS}, and the\n * parent's environment — marked not-owned: the fork runs in the same\n * worktree / sandbox but deleting it never tears that down. Refused while\n * the parent's environment isn't `ready`: nothing would provision the fork's\n * copy, and provisioning it would mint an environment nobody owns.\n *\n * @param store - Store to load the parent from and persist the fork in.\n * @param params - Parent, last turn to keep, artifact root and optional title.\n */\nexport async function forkSession(store: SessionStore, params: ForkSessionParams): Promise<ForkSessionResult> {\n const source = typeof params.source === 'string' ? await store.load(params.source) : params.source\n if (!source)\n return { ok: false, reason: 'source-not-found' }\n const turns = truncateTurnsAt(source.turns, params.turnId, { inclusive: true })\n if (!turns || turns.length === 0)\n return { ok: false, reason: 'turn-not-found' }\n\n if (source.metadata[ENVIRONMENT_METADATA_KEY] !== undefined && readSessionEnvironment(source.metadata).state !== 'ready')\n return { ok: false, reason: 'environment-not-ready' }\n\n const referencedRunIds = new Set(turns.flatMap(turn => turn.runId ? [turn.runId] : []))\n // Shallow-clone so the fork's later mutations never bleed into the parent.\n const runs = source.runs.filter(run => referencedRunIds.has(run.id)).map(run => ({ ...run }))\n\n const inherited: Record<string, unknown> = {}\n for (const key of FORK_INHERITED_METADATA_KEYS) {\n if (source.metadata[key] !== undefined)\n inherited[key] = source.metadata[key]\n }\n if (source.metadata[ENVIRONMENT_METADATA_KEY] !== undefined)\n Object.assign(inherited, environmentMetadata({ ...readSessionEnvironment(source.metadata), owned: false }))\n const title = params.title ?? (typeof source.metadata.title === 'string' ? source.metadata.title : undefined)\n\n const fork = await createSession({\n id: (await store.generateSessionId?.()) ?? randomUUID(),\n store,\n agentId: source.agentId ?? DEFAULT_AGENT_ID,\n ...(source.projectRoot ? { projectRoot: source.projectRoot } : {}),\n metadata: { ...inherited, ...(title ? { title } : {}) },\n })\n\n /*\n * Persisted-output stubs embed the blob's absolute per-session path; left\n * as-is, the fork would lose that content when the parent is deleted (its\n * blob dir goes with it). Rehome both layouts: sessions from before the\n * session-first layout embed `<userDir>/tool-results/<id>/`, newer ones\n * `<userDir>/sessions/<id>/tool-results/`. The legacy pass matters most —\n * the parent's delete sweeps exactly that dir.\n */\n const forkPersistDir = resolvePersistDir({ userDir: params.userDir, sessionId: fork.id })\n let rehomed = await rehomePersistedStubs(turns, resolvePersistDir({ userDir: params.userDir, sessionId: source.id }), forkPersistDir)\n const legacyPersistDir = resolveLegacyPersistDir({ userDir: params.userDir, sessionId: source.id })\n if (legacyPersistDir !== undefined)\n rehomed = await rehomePersistedStubs(rehomed, legacyPersistDir, forkPersistDir)\n\n fork.setTurns(rehomed)\n fork.setRuns(runs)\n await fork.save()\n return { ok: true, id: fork.id }\n}\n\n// ---------------------------------------------------------------------------\n// Delete\n// ---------------------------------------------------------------------------\n\n/**\n * Delete a session's persisted data: the store row, then every on-disk\n * artifact it owns — the session-first tree (`<userDir>/sessions/<id>/`:\n * blobs, task logs, MCP warnings) plus the two pre-session-first locations\n * migration leaves behind. Blob cleanup is best-effort and never throws; it\n * runs AFTER the row delete so a cleanup failure can't leave a live row\n * pointing at removed blobs.\n *\n * Does NOT stop a live agent — hosts must retire it first (see\n * `ChatHost.deleteSession`), or its next save resurrects the row.\n *\n * @param store - Store holding the session row.\n * @param sessionId - Id of the session to delete.\n * @param opts - `userDir` is the root of the per-session artifact tree.\n */\nexport async function deleteSessionData(store: SessionStore, sessionId: string, opts: { userDir: string }): Promise<void> {\n await store.delete(sessionId)\n const dirs = [\n resolveSessionDir({ userDir: opts.userDir, sessionId }),\n resolveLegacyPersistDir({ userDir: opts.userDir, sessionId }),\n resolveLegacySessionDir({ userDir: opts.userDir, sessionId }),\n ]\n await Promise.all(dirs.flatMap(dir => dir === undefined ? [] : [cleanupPersistedSession(dir)]))\n}\n\nfunction setMetaEntries(session: Session, entries: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(entries))\n session.setMeta(key, value)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,kBACT;;;;;;;;;;;;;;;;;AA+CJ,SAAgB,WAAW,MAAiC;CAC1D,MAAM,OAAO,KAAK,yBAAQ,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;CAC9D,MAAM,WAAW,KAAK,YAAY,QAAQ;CAC1C,MAAM,QAAQ,CAAC,SAAS,QAAQ,KAAK,KAAK;CAC1C,IAAI,KAAK,eAAe,KAAK,gBAAgB,KAAK,KAAK;EACrD,MAAM,KAAK,iBAAiB,KAAK,aAAa;EAQ9C,MAAM,KACJ,uMAIF;CACF;CACA,MAAM,KAAK,SAAS,QAAQ,aAAa,YAAY,QAAQ;CAC7D,OAAO,MAAM,KAAK,IAAI;AACxB;AAEA,MAAM,WAAW;AACjB,MAAM,YAAY;;;;;;;;;;AAWlB,SAAgB,kBAAkB,QAAgB,KAAqB;CACrE,MAAM,EAAE,QAAQ,YAAY,YAAY,kBAAkB,MAAM;CAChE,MAAM,QAAQ,QAAQ,QAAQ,QAAQ;CACtC,MAAM,MAAM,QAAQ,IAAI,KAAK,QAAQ,QAAQ,WAAW,KAAK;CAC7D,IAAI,QAAQ,KAAK,MAAM,GACrB,OAAO,sBAAsB,QAAQ,GAAG;CAC1C,OAAO,iBAAiB,YAAY,QAAQ,MAAM,GAAG,KAAK,IAAI,MAAM,QAAQ,MAAM,MAAM,CAAgB,CAAC;AAC3G;;;;;;;;AASA,SAAgB,eAAe,QAAoC;CACjE,MAAM,EAAE,YAAY,kBAAkB,MAAM;CAC5C,MAAM,QAAQ,QAAQ,QAAQ,QAAQ;CACtC,MAAM,MAAM,QAAQ,IAAI,KAAK,QAAQ,QAAQ,WAAW,KAAK;CAC7D,IAAI,QAAQ,KAAK,MAAM,GACrB,OAAO,KAAA;CACT,OAAO,iBAAiB,KAAK,QAAQ,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG;AAC5D;;;;;;;;;AAcA,MAAa,uBAAuB;;;;;;;;;;;;;;AAepC,MAAa,uBAAuB;;;;;;;;;;AAWpC,MAAa,eAAe;;;;;;;;;;;AAY5B,MAAa,4BAA4B;;;;;;;;;;;;;;;;AAiBzC,MAAa,sCAAsC;;;;;;;;;;;AAYnD,MAAa,yBAAyB;;;;;;;;;;;AAYtC,MAAa,6BAA6B;;;;;;;;;;;AAY1C,MAAa,oBAAoB;;;;;;;;AASjC,MAAa,qBAAqB;;;;;;;;;;;;;AAclC,MAAa,uBAAuB;;;;;;;;;;;AAYpC,MAAa,4BAA4B;;;;;;;;;;;AAYzC,MAAa,kCAAkC;;;;;;;;AAS/C,MAAa,gCAAgC;;;;;;;AA2E7C,SAAS,wBAAwB,MAAyC;CACxE,IAAI,CAAC,KAAK,KACR,OAAO;CACT,OAAO,WAAW;EAChB,KAAK,KAAK;EACV,GAAI,KAAK,gBAAgB,KAAA,IAAY,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;EAC1E,GAAI,KAAK,SAAS,KAAA,IAAY,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;EACrD,GAAI,KAAK,aAAa,KAAA,IAAY,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;CACnE,CAAC;AACH;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,iBAAiB,OAA2B,CAAC,GAAW;CACtE,MAAM,sBAAsB,KAAK,qBAAqB,QAClD,kCACA;CACJ,MAAM,MAAM,wBAAwB,IAAI;CAiBxC,OAAO,oBAAoB;EACzB,QAAQ,CACN,GAfa,KAAK,iBAClB,CAAC,KAAK,cAAc,IACpB;GACE;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,GAIA,KAAK,oBAAoB,IAC3B;EACA,SAAS,CAAC,GAAG;CACf,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,gBAAgB,OAA2B,CAAC,GAAW;CACrE,MAAM,YAAY,KAAK,qBAAqB;CAC5C,MAAM,MAAM,wBAAwB,IAAI;CAcxC,OAAO,oBAAoB;EACzB,QAAQ,CACN,GAXa,KAAK,iBAClB,CAAC,KAAK,cAAc,IACpB;GACE;GACA,YAAY,gCAAgC;GAC5C;GACA;GACA,YAAY,kCAAkC;EAChD,GAIA,KAAK,oBAAoB,IAC3B;EACA,SAAS,CAAC,GAAG;CACf,CAAC;AACH;;;;;;;AAQA,SAAS,oBAAoB,OAGlB;CACT,MAAM,aAAa,WAAW,MAAM,MAAM;CAC1C,MAAM,cAAc,WAAW,MAAM,OAAO;CAC5C,OAAO,iBAAiB,YAAY,WAAW;AACjD;AAEA,SAAS,WAAW,OAAuD;CACzE,OAAO,MAAM,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,MAAM;AAC5F;;;;ACxaA,MAAa,iBAAiB;;AAE9B,MAAa,gBAAgB;;;;;;AAO7B,SAAgB,WAAW,MAAuB;CAChD,OAAO,SAAA,eAA2B,SAAA;AACpC;;AASA,MAAa,qBAAqB;;AAElC,MAAa,iCAAiC;AAmD9C,MAAM,qBAA4C;CAAC;CAAW;CAAe;CAAa;AAAW;;;;;;AAOrG,MAAa,yBAAyB;AA8HtC,MAAM,oBACF;AAmBJ,MAAM,mBACF;AAKJ,SAAS,gBAAgB,OAAuB;CAC9C,OAAO,4BAA4B,MAAM;AAE3C;AAEA,SAAS,4BAA4B,OAAuB;CAC1D,OAAO,qCAAqC,MAAM;AAKpD;;;;;;;;;;;AAYA,SAAS,mBAAmB,OAAoD;CAE9E,MAAM,QAAQ,cADF,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,GACvB,OAAO,gBAAgB;CACxD,MAAM,uBAAO,IAAI,IAAsB;CACvC,KAAK,MAAM,QAAQ,OACjB,KAAK,IAAI,KAAK,IAAI,IAAI;CACxB,MAAM,aAAa,CAAC,GAAG,KAAK,OAAO,CAAC;CACpC,IAAI,WAAW,WAAW,GACxB,OAAO,KAAA;CACT,OAAO,KAAK,UAAU,WAAW,KAAI,MAAK;EAAC,EAAE;EAAI,EAAE;EAAQ,EAAE;CAAO,CAAC,CAAC;AACxE;;;;;;;AAeA,SAAS,cAAc,SAAkB,OAAqC;CAC5E,IAAI,UAAU,MACZ,OAAO;CAET,OADY,QAAQ,KAAK,MAAK,MAAK,EAAE,OAAO,KACnC,CAAC,EAAE,cAAc,QAAQ;AACpC;;;;;;;;;;AAWA,SAAgB,eAAe,SAAkB,OAAkC;CACjF,MAAM,MAAM,aAAa,OAAO;CAChC,MAAM,WAAW,cAAc,SAAS,KAAK;CAC7C,IAAI,aAAa,MAAM;EACrB,MAAM,QAAQ,IAAI,QAAQ;EAC1B,OAAO,MAAM,QAAQ,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC;CAC9C;CACA,OAAO,MAAM,QAAQ,IAAI,OAAO,IAAI,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC;AAC1D;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,mBACd,SACA,UACM;CACN,MAAM,EAAE,UAAU;CAElB,IAAI,CAAC,SAAS,SAAS,MAAM,WAAW,KAAK,SAAS,QAAQ,WAAW,GACvE;CACF,MAAM,MAAM,aAAa,OAAO;CAChC,MAAM,WAAW,cAAc,SAAS,KAAK;CAC7C,MAAM,OAAO,SAAS,MAAM,IAAI,aAAa;CAC7C,MAAM,WAAW,SAAS,QAAQ,IAAI,aAAa;CAEnD,MAAM,UACJ,QACA,UACS;EACT,IAAI,aAAa,MAAM;GACrB,MAAM,QAAQ,EAAE,GAAI,OAAO,SAAS,CAAC,EAAG;GACxC,IAAI,MAAM,WAAW,GACnB,OAAO,MAAM;QAEb,MAAM,YAAY;GACpB,OAAO,QAAQ;GACf;EACF;EACA,IAAI,MAAM,WAAW,GACnB,OAAO,OAAO;OAEd,OAAO,UAAU;CACrB;CAEA,OAAO,KAAK,IAAI;CAChB,MAAM,UAAU,EAAE,GAAI,IAAI,WAAW,CAAC,EAAG;CACzC,OAAO,SAAS,QAAQ;CACxB,IAAI,UAAU;CACd,cAAc,SAAS,GAAG;AAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,eACd,SACA,OACA,OACA,OAAmC,CAAC,GAC9B;CACN,MAAM,MAAM,aAAa,OAAO;CAChC,MAAM,aAAa,MAAM,IAAI,aAAa;CAC1C,MAAM,WAAW,cAAc,SAAS,KAAK;CAG7C,IAAI,UAAU;EACZ,MAAM,QAAQ,EAAE,GAAI,IAAI,SAAS,CAAC,EAAG;EACrC,IAAI,WAAW,WAAW,GACxB,OAAO,MAAM;OAEb,MAAM,YAAY;EACpB,IAAI,QAAQ;CACd,OACK,IAAI,WAAW,WAAW,GAC7B,OAAO,IAAI;MAGX,IAAI,UAAU;CAMhB,IAAI,WAAW,SAAS,GAAG;EACzB,MAAM,UAAU,EAAE,GAAI,IAAI,WAAW,CAAC,EAAG;EACzC,IAAI,UAAU;GACZ,MAAM,eAAe,EAAE,GAAI,QAAQ,SAAS,CAAC,EAAG;GAChD,aAAa,YAAY;GACzB,QAAQ,QAAQ;EAClB,OAEE,QAAQ,UAAU;EAEpB,IAAI,UAAU;CAChB,OACK,IAAI,KAAK,cAAc;EAG1B,MAAM,UAAU,EAAE,GAAI,IAAI,WAAW,CAAC,EAAG;EACzC,IAAI,UAAU;GACZ,MAAM,eAAe,EAAE,GAAI,QAAQ,SAAS,CAAC,EAAG;GAChD,OAAO,aAAa;GACpB,QAAQ,QAAQ;EAClB,OAEE,OAAO,QAAQ;EAEjB,IAAI,UAAU;CAChB;CAEA,cAAc,SAAS,GAAG;AAC5B;;;;;;;;;;;;;AAcA,SAAgB,uBAAuB,SAAkB,OAAkC;CAEzF,MAAM,UADM,aAAa,OACP,CAAC,CAAC;CACpB,IAAI,CAAC,SACH,OAAO,CAAC;CACV,MAAM,WAAW,cAAc,SAAS,KAAK;CAC7C,IAAI,aAAa,MAAM;EACrB,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,OAAO,MAAM,QAAQ,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC;CAC9C;CACA,OAAO,MAAM,QAAQ,QAAQ,OAAO,IAAI,CAAC,GAAG,QAAQ,OAAO,IAAI,CAAC;AAClE;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBAAgB,SAAkD;CAChF,MAAM,cAAc,IAAI,IAAI,QAAQ,KAAK,KAAI,MAAK,EAAE,EAAE,CAAC;CACvD,MAAM,UAAoB,CAAC;CAE3B,MAAM,MAAM,aAAa,OAAO;CAChC,IAAI,aAAa;CACjB,IAAI,IAAI,OAAO;EACb,MAAM,YAAwC,CAAC;EAC/C,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,IAAI,KAAK,GACnD,IAAI,YAAY,IAAI,KAAK,GACvB,UAAU,SAAS;OAEhB;GACH,QAAQ,KAAK,KAAK;GAClB,aAAa;EACf;EAEF,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,WAAW,OAAO,KAAK,IAAI,KAAK,CAAC,CAAC,QAC3D,IAAI,QAAQ;CAChB;CACA,IAAI,IAAI,SAAS,OAAO;EACtB,MAAM,YAAwC,CAAC;EAC/C,IAAI,iBAAiB;EACrB,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,IAAI,QAAQ,KAAK,GAC3D,IAAI,YAAY,IAAI,KAAK,GACvB,UAAU,SAAS;OAEnB,iBAAiB;EAErB,IAAI,gBAAgB;GAClB,IAAI,UAAU;IAAE,GAAG,IAAI;IAAS,OAAO;GAAU;GACjD,aAAa;EACf;CACF;CACA,IAAI,YACF,cAAc,SAAS,GAAG;CAE5B,MAAM,SAAS,cAAc,OAAO;CACpC,IAAI,OAAO,OAAO;EAChB,MAAM,YAAoC,CAAC;EAC3C,IAAI,UAAU;EACd,KAAK,MAAM,CAAC,OAAO,MAAM,OAAO,QAAQ,OAAO,KAAK,GAClD,IAAI,YAAY,IAAI,KAAK,GACvB,UAAU,SAAS;OAEnB,UAAU;EAEd,IAAI,SAAS;GACX,OAAO,QAAQ;GACf,eAAe,SAAS,MAAM;EAChC;CACF;CAEA,OAAO,EAAE,QAAQ;AACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,gBAAgB,UAAkC,CAAC,GAAW;CAC5E,MAAM,WAAW,QAAQ,YAAA;CACzB,MAAM,cAAc,QAAQ,eAAe;CAC3C,MAAM,eAAe,QAAQ,kBAAkB,OAAO,WAAW,gBAAgB,KAAK;CACtF,MAAM,iBAAiB,QAAQ,kBAAkB;CAMjD,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,cAAc,QAAQ,eAAe;CAK3C,MAAM,qBAAqB,QAAQ,sBAAsB;CAEzD,MAAM,QAAiC;GACpC,iBAAiB,oBAAoB;GACpC;GACA;GACA;GACA;GACA,aAAa,QAAQ,oBAAoB;EAC3C,CAAC;GACA,gBAAgB,mBAAmB,EAClC,aAAa,QAAQ,mBAAmB,iBAC1C,CAAC;CACH;CAOA,MAAM,WAA4C,CAAC;CACnD,IAAI,kBAAkB,GACpB,SAAS,cAAc,GAAG,iBAAiB;EAAE,KAAK;EAAiB,UAAU;CAAY,EAAE;CAQ7F,IAAI,qBAAqB,GAAG;EAC1B,MAAM,SAAS,QAAQ,wBAAwB;EAC/C,SAAS,aAAa,GACnB,iBAAiB;GAChB,QAAQ;GACR,MAAM;GACN,WAAW;GACX,OAAO;GACP,SAAS,QAAQ,UAAU,OAAO,KAAK;EACzC,EACF;CACF;CAEA,OAAO,aACL,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAC3B;EAAE;EAAO;CAAS,IAClB,EAAE,MAAM,CACd;AACF;AAcA,SAAS,oBAAoB,MAAuC;CAClE,OAAO;EACL,MAAM;GACJ,MAAM;GACN,aAAa,KAAK;GAClB,aAAa;IACX,MAAM;IACN,YAAY,EACV,OAAO;KACL,MAAM;KACN,aAAa;KACb,UAAU,KAAK;KACf,OAAO;MACL,MAAM;MACN,YAAY;OACV,IAAI;QAAE,MAAM;QAAU,aAAa;OAAuC;OAC1E,SAAS;QAAE,MAAM;QAAU,aAAa;OAAyB;OACjE,QAAQ;QACN,MAAM;QACN,MAAM,CAAC,GAAG,kBAAkB;QAC5B,aAAa;OACf;MACF;MACA,UAAU;OAAC;OAAM;OAAW;MAAQ;KACtC;IACF,EACF;IACA,UAAU,CAAC,OAAO;GACpB;EACF;EACA,MAAM,QAAQ,OAAO,KAAK;GACxB,MAAM,EAAE,SAAS,UAAU,qBAAqB,KAAK,cAAc;GAEnE,MAAM,WAAW,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC;GAC7D,MAAM,QAAQ,cAAc,UAAU,KAAK,QAAQ;GACnD,MAAM,UAAU,SAAS,SAAS,MAAM;GAIxC,MAAM,uBAAO,IAAI,IAAsB;GACvC,KAAK,MAAM,QAAQ,OACjB,KAAK,IAAI,KAAK,IAAI,IAAI;GACxB,MAAM,aAAa,CAAC,GAAG,KAAK,OAAO,CAAC;GAKpC,MAAM,UAAU,eAAe,SAAS,KAAK;GAC7C,MAAM,YAAY,KAAK,kBAAkB,WAAW,SAAS,UAAU;GAOvE,MAAM,QAAQ,eAAe,SAAS,KAAK;GAO3C,MAAM,eAAe,WAAW,SAAS,KAAK,WAAW,OAAM,MAAK,EAAE,WAAW,WAAW;GAE5F,IAAI,CAAC,WAAW;IACd,IAAI,cAAc;KAOhB,eAAe,SAAS,OAAO,UAAU;KACzC,eAAe,SAAS,OAAO,CAAC,CAAC;IACnC,OAEE,eAAe,SAAS,OAAO,UAAU;GAE7C;GAEA,OAAO,kBAAkB;IACvB,OAAO;IACP;IACA;IACA;IAIA,SAAS,gBAAgB,CAAC;IAC1B;GACF,CAAC;EACH;CACF;AACF;AAWA,SAAS,kBAAkB,OAAuC;CAChE,MAAM,EAAE,OAAO,SAAS,OAAO,WAAW,SAAS,SAAS;CAC5D,MAAM,QAAkB,CAAC;CACzB,MAAM,IAAI,MAAM;CAChB,MAAM,SAAS,MAAM,IAAI,KAAK;CAC9B,IAAI,SACF,MAAM,KAAK,UAAU,EAAE,OAAO,OAAO,yBAAyB;MAE3D,IAAI,WACP,MAAM,KAAK,cAAc,EAAE,YAAY,OAAO,kBAAkB;MAGhE,MAAM,KAAK,WAAW,EAAE,YAAY,OAAO,EAAE;CAK/C,IAAI,CAAC,SAAS;EACZ,MAAM,QAAQ,kBAAkB,KAAK;EACrC,IAAI,OACF,MAAM,KAAK,KAAK;CACpB;CACA,IAAI,UAAU,GACZ,MAAM,KAAK,WAAW,QAAQ,iBAAiB,YAAY,IAAI,KAAK,IAAI,EAAE;CAC5E,MAAM,WAAW,KAAK,cAAc,KAAK,SAAS,KAAK,cACnD,KAAK,aAAa,OAAO,KAAK,IAC9B,KAAA;CACJ,IAAI,YAAY,SAAS,SAAS,GAChC,MAAM,KAAK,QAAQ;CACrB,OAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,mBAAmB,MAAwC;CAClE,OAAO;EAEL,mBAAmB;EACnB,MAAM;GACJ,MAAM;GACN,aAAa,KAAK;GAClB,aAAa;IACX,MAAM;IACN,YAAY,CAAC;GACf;EACF;EACA,MAAM,QAAQ,QAAQ,KAAK;GACzB,MAAM,EAAE,SAAS,UAAU,qBAAqB,KAAK,aAAa;GAElE,MAAM,QAAQ,eAAe,SAAS,KAAK;GAC3C,IAAI,MAAM,WAAW,GACnB,OAAO;GACT,OAAO,KAAK,UAAU,EAAE,OAAO,MAAM,CAAC;EACxC;CACF;AACF;;;;;AAUA,SAAS,qBACP,KACA,UACqC;CACrC,IAAI,CAAC,IAAI,SACP,MAAM,IAAI,MAAM,GAAG,SAAS,2EAA2E;CACzG,IAAI,CAAC,IAAI,OACP,MAAM,IAAI,MAAM,GAAG,SAAS,4BAA4B;CAC1D,OAAO;EAAE,SAAS,IAAI;EAAS,OAAO,IAAI;CAAM;AAClD;AAOA,MAAM,gBAAgB,OAAO;;AAG7B,SAAS,SAAS,KAAsC;CACtD,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,OAAO,KAAA;CACT,MAAM,QAAQ,cAAc,KAAK,aAAa;CAC9C,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;AACpC;;AAGA,SAAS,YAAY,KAAsD;CACzE,IAAI,CAAC,cAAc,GAAG,GACpB,OAAO,KAAA;CACT,MAAM,MAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,OAAO,UAAU,OAAO,QAAQ,GAAG,GAAG;EAChD,MAAM,QAAQ,SAAS,KAAK;EAC5B,IAAI,OACF,IAAI,SAAS;CACjB;CACA,OAAO,OAAO,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,MAAM,KAAA;AAC7C;;AAGA,SAAS,aAAa,SAA4B;CAChD,MAAM,MAAM,QAAQ,SAAS;CAC7B,IAAI,CAAC,cAAc,GAAG,GACpB,OAAO,CAAC;CAcV,MAAM,UAAU,cAAc,IAAI,OAAO,IACrC;EACE,SAAS,SAAS,IAAI,QAAQ,OAAO;EACrC,OAAO,YAAY,IAAI,QAAQ,KAAK;CACtC,IACA,KAAA;CACJ,OAAO;EACL,SAAS,SAAS,IAAI,OAAO;EAC7B,OAAO,YAAY,IAAI,KAAK;EAC5B;CACF;AACF;AAEA,SAAS,cAAc,SAAkB,KAAqB;CAE5D,MAAM,QAAkB,CAAC;CACzB,IAAI,IAAI,WAAW,IAAI,QAAQ,SAAS,GACtC,MAAM,UAAU,IAAI;CACtB,IAAI,IAAI,SAAS,OAAO,KAAK,IAAI,KAAK,CAAC,CAAC,SAAS,GAC/C,MAAM,QAAQ,IAAI;CACpB,IAAI,IAAI,SAAS;EACf,MAAM,UAA4C,CAAC;EACnD,IAAI,IAAI,QAAQ,WAAW,IAAI,QAAQ,QAAQ,SAAS,GACtD,QAAQ,UAAU,IAAI,QAAQ;EAChC,IAAI,IAAI,QAAQ,SAAS,OAAO,KAAK,IAAI,QAAQ,KAAK,CAAC,CAAC,SAAS,GAC/D,QAAQ,QAAQ,IAAI,QAAQ;EAC9B,IAAI,QAAQ,WAAW,QAAQ,OAC7B,MAAM,UAAU;CACpB;CACA,QAAQ,QAAQ,oBAAoB,KAAK;AAC3C;AAEA,SAAS,cAAc,SAA6B;CAClD,MAAM,MAAM,QAAQ,SAAS;CAC7B,IAAI,CAAC,cAAc,GAAG,GACpB,OAAO,CAAC;CACV,OAAO;EACL,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAA;EACzD,OAAO,cAAc,IAAI,KAAK,IAAI,IAAI,QAAkC,KAAA;CAC1E;AACF;AAEA,SAAS,eAAe,SAAkB,KAAsB;CAC9D,MAAM,QAAmB,CAAC;CAC1B,IAAI,OAAO,IAAI,YAAY,YAAY,IAAI,UAAU,GACnD,MAAM,UAAU,IAAI;CACtB,IAAI,IAAI,SAAS,OAAO,KAAK,IAAI,KAAK,CAAC,CAAC,SAAS,GAC/C,MAAM,QAAQ,IAAI;CACpB,QAAQ,QAAQ,gCAAgC,KAAK;AACvD;;;;;;;;;;AAWA,SAAS,eAAe,SAAkB,OAAuB;CAC/D,MAAM,MAAM,cAAc,OAAO;CACjC,IAAI;CACJ,IAAI,cAAc,SAAS,KAAK,MAAM,MAAM;EAC1C,MAAM,QAAQ,EAAE,GAAI,IAAI,SAAS,CAAC,EAAG;EACrC,QAAQ,MAAM,UAAU,KAAK;EAC7B,MAAM,SAAS;EACf,IAAI,QAAQ;CACd,OACK;EACH,QAAQ,IAAI,WAAW,KAAK;EAC5B,IAAI,UAAU;CAChB;CACA,eAAe,SAAS,GAAG;CAC3B,OAAO;AACT;;AAGA,SAAS,cAAc,GAA0C;CAC/D,OAAO,CAAC,CAAC,KAAK,OAAO,MAAM,YAAY,CAAC,MAAM,QAAQ,CAAC;AACzD;AAEA,SAAS,cAAc,MAA0B;CAC/C,OAAO;EAAE,IAAI,KAAK;EAAI,SAAS,KAAK;EAAS,QAAQ,KAAK;CAAO;AACnE;;;;;;;;;;;;;;AAeA,SAAgB,kBAAkB,KAAyB,MAAA,KAAkD;CAC3G,OAAO,cAAc,CAAC,GAAG,GAAG,GAAG,GAAG;AACpC;AAEA,SAAS,cAAc,KAAgB,KAAyB;CAC9D,MAAM,MAAkB,CAAC;CACzB,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD;EACF,MAAM,MAAM;EACZ,MAAM,KAAK,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK,KAAA;EACjD,MAAM,UAAU,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,KAAA;EAChE,MAAM,SAAS,OAAO,IAAI,WAAW,YAAa,mBAAyC,SAAS,IAAI,MAAM,IAC1G,IAAI,SACJ,KAAA;EACJ,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,QACtB;EACF,IAAI,KAAK;GAAE;GAAI;GAAS;EAAO,CAAC;EAChC,IAAI,IAAI,UAAU,KAChB;CACJ;CACA,OAAO;AACT;AAEA,SAAS,kBAAkB,OAAgD;CACzE,IAAI,MAAM,WAAW,GACnB,OAAO,KAAA;CACT,MAAM,SAAqC;EACzC,SAAS;EACT,aAAa;EACb,WAAW;EACX,WAAW;CACb;CACA,KAAK,MAAM,QAAQ,OACjB,OAAO,KAAK,WAAW;CACzB,MAAM,QAAkB,CAAC;CACzB,IAAI,OAAO,WACT,MAAM,KAAK,GAAG,OAAO,UAAU,WAAW;CAC5C,IAAI,OAAO,aACT,MAAM,KAAK,GAAG,OAAO,YAAY,aAAa;CAChD,IAAI,OAAO,SACT,MAAM,KAAK,GAAG,OAAO,QAAQ,SAAS;CACxC,IAAI,OAAO,WACT,MAAM,KAAK,GAAG,OAAO,UAAU,WAAW;CAC5C,OAAO,MAAM,SAAS,IAAI,MAAM,KAAK,KAAK,IAAI,KAAA;AAChD;;;;;;;;;;;;AAaA,SAAS,WAAW,GAAwB,GAAiC;CAC3E,IAAI,EAAE,WAAW,EAAE,QACjB,OAAO;CACT,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;EACjC,MAAM,IAAI,EAAE;EACZ,MAAM,IAAI,EAAE;EACZ,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,SAC5D,OAAO;CACX;CACA,OAAO;AACT;AAwDA,MAAM,cAAyB;CAAE,SAAS;CAAG,aAAa;CAAG,WAAW;CAAG,WAAW;AAAE;AACxF,MAAM,eAAiC;CACrC,OAAO;CACP,OAAO,CAAC;CACR,SAAS,CAAC;CACV,YAAY;CACZ,OAAO;AACT;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBAAgB,SAAoD;CAClF,IAAI,CAAC,SACH,OAAO;CACT,MAAM,OAAO,QAAQ;CACrB,IAAI,CAAC,QAAQ,KAAK,WAAW,GAC3B,OAAO;CAKT,IAAI,gBAA+B;CACnC,KAAK,IAAI,IAAI,KAAK,SAAS,GAAG,KAAK,GAAG,KACpC,IAAI,KAAK,EAAE,CAAC,WAAW,WAAW;EAChC,gBAAgB,KAAK,EAAE,CAAC;EACxB;CACF;CAEF,IAAI,eACF,OAAO;CACT,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;AAC/B;;;;;;;;;;;;;AAcA,SAAgB,kBAAkB,SAAuD;CACvF,IAAI,CAAC,SACH,OAAO;CAET,IAAI,QAAuB,gBAAgB,OAAO;CAGlD,MAAM,uBAAO,IAAI,IAAY;CAC7B,OAAO,UAAU,QAAQ,CAAC,KAAK,IAAI,KAAK,KAAK,eAAe,SAAS,KAAK,CAAC,CAAC,WAAW,KAAK,uBAAuB,SAAS,KAAK,CAAC,CAAC,WAAW,GAAG;EAC/I,KAAK,IAAI,KAAK;EACd,MAAM,SAAS,QAAQ,KAAK,MAAK,MAAK,EAAE,OAAO,KAAK,CAAC,EAAE;EACvD,IAAI,CAAC,QACH;EACF,QAAQ;CACV;CACA,MAAM,QAAQ,eAAe,SAAS,KAAK;CAC3C,MAAM,UAAU,uBAAuB,SAAS,KAAK;CAGrD,MAAM,UAAU,MAAM,SAAS,IAAI,QAAQ;CAC3C,IAAI,QAAQ,WAAW,GACrB,OAAO;EAAE;EAAO;EAAO;EAAS,YAAY;EAAM,OAAO;CAAY;CACvE,MAAM,QAAmB;EAAE,SAAS;EAAG,aAAa;EAAG,WAAW;EAAG,WAAW;CAAE;CAClF,IAAI,aAA8B;CAGlC,KAAK,MAAM,KAAK,SACd,MAAM,EAAE,WAAW;CACrB,IAAI,MAAM,SAAS,GACZ;OAAA,MAAM,KAAK,OACd,IAAI,EAAE,WAAW,eAAe;GAC9B,aAAa;GACb;EACF;;CAGJ,OAAO;EAAE;EAAO;EAAO;EAAS;EAAY;CAAM;AACpD;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,kBAAkB,OAAwC;CACxE,MAAM,UAAU,MAAM,MAAM,SAAS,IAAI,MAAM,QAAQ,MAAM;CAC7D,IAAI,QAAQ,WAAW,GACrB,OAAO;CACT,MAAM,cAAc,MAAM,aACtB,QAAQ,WAAU,MAAK,EAAE,OAAO,MAAM,WAAY,EAAE,IACpD;CAEJ,OAAO,GADW,eAAe,IAAI,cAAc,IAAI,MAAM,MAAM,UAC/C,GAAG,QAAQ;AACjC;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,eAAe,SAAuD;CACpF,OAAO,kBAAkB,OAAO;AAClC;;;;;;;;;;;AC7uCA,SAAgB,gBAAgB,UAAyB,UAA4C;CACnG,MAAM,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC,QAAO,YAAW,QAAQ,oBAAoB,KAAA,KAAa,SAAS,QAAQ,qBAAqB,IAAI;CAC5I,IAAI,OAAO,WAAW,GACpB,OAAO;CACT,OAAO,OAAO,YAAY,OAAO,QAAQ,QAAQ,CAAC,CAAC,QAAQ,GAAG,aAAa,CAAC,OAAO,SAAS,OAAO,CAAC,CAAC;AACvG;;;;;;;;AAsBA,SAAgB,mBAAmB,UAAyB,SAAyC;CACnG,IAAI,QAAQ,WAAW,KAAA,GACrB,OAAO;EAAE,KAAK,WAAW,QAAQ;EAAM,MAAM;EAAS,SAAS,CAAC,OAAO;CAAE;CAC3E,MAAM,UAAU,OAAO,OAAO,QAAQ,CAAC,CAAC,QAAO,WAAU,OAAO,WAAW,QAAQ,MAAM;CACzF,IAAI,CAAC,QAAQ,SAAS,OAAO,GAC3B,QAAQ,KAAK,OAAO;CACtB,OAAO;EAAE,KAAK,UAAU,QAAQ;EAAU,MAAM,QAAQ;EAAK;CAAQ;AACvE;;;;;;;;AASA,SAAgB,iBAAiB,UAAyB,KAA2C;CACnG,IAAI,IAAI,WAAW,UAAU,GAAG;EAC9B,MAAM,UAAU,SAAS,IAAI,MAAM,CAAiB;EACpD,OAAO,WAAW,QAAQ,WAAW,KAAA,IAAY,mBAAmB,UAAU,OAAO,IAAI,KAAA;CAC3F;CACA,IAAI,IAAI,WAAW,SAAS,GAAG;EAC7B,MAAM,SAAS,IAAI,MAAM,CAAgB;EACzC,MAAM,SAAS,OAAO,OAAO,QAAQ,CAAC,CAAC,MAAK,YAAW,QAAQ,WAAW,MAAM;EAChF,OAAO,SAAS,mBAAmB,UAAU,MAAM,IAAI,KAAA;CACzD;AAEF;;;;;;;AAQA,SAAgB,aAAa,SAAqD;CAChF,MAAM,wBAAQ,IAAI,IAAqB;CACvC,KAAK,MAAM,QAAQ,OAAO,OAAO,QAAQ,OAAO,SAAS,CAAC,CAAC,GACzD,IAAI,QAAQ,CAAC,MAAM,IAAI,KAAK,KAAK,IAAI,GACnC,MAAM,IAAI,KAAK,KAAK,MAAM,IAAI;CAElC,OAAO;AACT;;;;;;;AAQA,SAAgB,iBAAiB,aAA6D;CAC5F,MAAM,QAAQ,IAAI,IAAI,aAAa,YAAY,IAAI,CAAC;CACpD,KAAK,MAAM,UAAU,YAAY,SAC/B,KAAK,MAAM,CAAC,MAAM,SAAS,aAAa,MAAM,GAC5C,IAAI,CAAC,MAAM,IAAI,IAAI,GACjB,MAAM,IAAI,MAAM,IAAI;CAG1B,OAAO;AACT;AAEA,SAAS,SAAS,GAAY,GAAqB;CACjD,OAAO,MAAM,KAAK,KAAK,UAAU,EAAE,IAAI,MAAM,KAAK,UAAU,EAAE,IAAI;AACpE;;;;;;;;;;;AAYA,SAAgB,gBAAgB,UAAyB,aAA+B,SAAgC;CACtH,IAAI,mBAAmB,UAAU,OAAO,CAAC,CAAC,QAAQ,YAAY,KAC5D,OAAO;CACT,MAAM,YAAY,iBAAiB,WAAW;CAC9C,MAAM,MAAM,aAAa,OAAO;CAChC,IAAI,IAAI,QAAQ,UAAU,MACxB,OAAO;CACT,KAAK,MAAM,CAAC,MAAM,SAAS,KAAK;EAC9B,MAAM,QAAQ,UAAU,IAAI,IAAI;EAChC,IAAI,CAAC,SAAS,CAAC,SAAS,OAAO,IAAI,GACjC,OAAO;CACX;CACA,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,mBAAmB,UAAyB,WAAmB,YAA8D;CAC3I,MAAM,UAAU,SAAS;CACzB,IAAI,CAAC,SACH,OAAO,KAAA;CACT,MAAM,UAAU,eAAe,KAAA,IAAY,KAAA,IAAY,iBAAiB,UAAU,UAAU;CAC5F,OAAO,WAAW,gBAAgB,UAAU,SAAS,OAAO,IAAI,UAAU,mBAAmB,UAAU,OAAO;AAChH;AAKA,MAAM,mCAAmB,IAAI,IAA2B,CACtD,CAAC,iBAAiB,GAAG,OAAO,GAC5B,CAAC,gBAAgB,GAAG,MAAM,CAC5B,CAAC;;;;;;;;;;AAWD,SAAgB,gBAAgB,SAAkD;CAChF,OAAO,QAAQ,OAAO,WAAW,KAAA,IAAY,KAAA,IAAY,iBAAiB,IAAI,QAAQ,OAAO,MAAM;AACrG;;AAGA,MAAa,kBAAkB;CAAE;CAAU;CAAW;CAAM;CAAM;CAAW;AAAS;;;;;;;;;;;AAYtF,MAAM,cAAc;CAAE;CAAO;CAAW;CAAU;CAAU;CAAW;CAAW;CAAM;CAAW;CAAM;CAAM;CAAW;AAAS;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BnI,MAAa,gCAAmD;CAC9D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;AACF;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,+BAAkD;CAC7D;CACA;CACA;CACA;AACF;;;;;;;;AASA,SAAS,mBAAmB,OAAoD;CAC9E,MAAM,OAAO,OAAO,KAAK,KAAK;CAC9B,IAAI,KAAK,WAAW,GAClB,OAAO,KAAA;CACT,KAAK,KAAK;CACV,MAAM,OAAgC,CAAC;CACvC,KAAK,MAAM,KAAK,MACd,KAAK,KAAK,MAAM;CAClB,OAAO,KAAK,UAAU,IAAI;AAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,kBAAkB;CACtB,iBAAiB;CACjB,kBAAkB;CAYlB,gCAAgC;CAIhC,yBAAyB;CAIzB,aAAa;EAAE,WAAW,CAAC,WAAW;EAAG,kBAAkB;CAAQ;CASnE,kBAAkB;CASlB,iBAAiB;CAQjB,aAAa;CAIb,cAAc;CAQd,aAAa,EAAE,WAAW,IAAK;CAC/B,gBAAgB;CAChB,kBAAkB;CAClB,8BAA8B;CAC9B,kBAAkB;CAClB,qBAAqB;CAKrB,uBAAuB;CAKvB,iBAAiB;CAOjB,YAAY;EACV,MAAM;EACN,MAAM;EACN,YAAY;CACd;CAiBA,aAAa;EAAE,MAAM;EAAQ,YAAY;CAAG;CAU5C,eAAe;EAAE,WAAW;EAAI,OAAO;CAAK;CAS5C,UAAU;AACZ;;;;;;;;;;;;AAaA,MAAa,cAA4B;CACvC,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;CAER,QAAQ;CAeR,QAAQ,eACN,aAAa;EACX,MAAM;EACN,QAAQ,iBAAiB;EACzB,UAAU,EAAE,GAAG,gBAAgB;EAG/B,OAAO;GAAE,GAAG;GAAa,OAAO,gBAAgB,EAAE,SAAS,KAAK,CAAC;EAAE;CACrE,CAAC,GACD,gBAAgB,CAClB;AACF;;;;;;;;;AAUA,MAAa,uBAAqC;CAChD,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;CACR,iBAAiB;CACjB,QAAQ,eACN,aAAa;EACX,MAAM;EACN,QAAQ,iBAAiB;EACzB,UAAU;GAAE,GAAG;GAAiB,UAAU,EAAE,MAAM,OAAO;EAAE;EAC3D,OAAO;GAAE,GAAG;GAAa,OAAO,gBAAgB,EAAE,SAAS,KAAK,CAAC;EAAE;CACrE,CAAC,GACD,gBAAgB,CAClB;AACF;;;;;;AAOA,SAAS,eAAe,EAAE,OAAO,oBAAsD;CACrF,MAAM,eAAe,mBAAmB,kDAAkD;CAC1F,MAAM,QAAQ,mBACV;;;;;;2CAOA;;CAEJ,OAAO;sLAC6K,MAAM,KAAI,SAAQ,KAAK,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,mCAAmC,aAAa;;EAEhR,MAAM;;;6CAGqC,mBAAmB,gBAAgB,MAAM;AACtF;;;;;;;;;;AAWA,MAAa,aAA2B;CACtC,IAAI;CACJ,OAAO;CACP,aAAa;CACb,QAAQ;CACR,QAAQ;CACR,YAAY;CACZ,QAAQ,aAAa;EACnB,MAAM;EACN,QAAQ,gBAAgB;EACxB,UAAU,EAAE,GAAG,gBAAgB;EAC/B,OAAO;CACT,CAAC;AACH;;;;;;AAOA,MAAa,iBAAgC;CAC3C,SAAS;CACT,kBAAkB;CAClB,QAAQ;AACV;;AAGA,MAAa,mBAAmB;;;;;;;;;;;AAYhC,SAAgB,eACd,UACA,aACA,WACe;CACf,IAAI,eAAe,SAAS,cAC1B,OAAO;CACT,IAAI,aAAa,SAAS,YACxB,OAAO;CAET,OADc,OAAO,KAAK,QAAQ,CAAC,CAAC,MACpB;AAClB;;;;;;;;AASA,SAAgB,oBAAoB,QAA+B;CACjE,OAAO,EACL,SAAS;EACP,IAAI;EACJ,OAAO,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,IAAI,OAAO,OAAO;EACjF,aAAa;EACb;EACA,QAAQ;CACV,EACF;AACF;;;;;;;;;;;;;;;;;;;AC7jBA,eAAsB,WAAW,OAAiC,QAAsD;CACtH,MAAM,KAAK,OAAO,MAAO,MAAM,OAAO,oBAAoB,KAAM,WAAW;CAK3E,MAAM,cAAc,OAAO,MAAM,cAAc,OAAO,GAAG,IAAI,KAAA;CAC7D,MAAM,UAAU,MAAM,cAAc;EAClC;EACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EACzB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;EAGrC,SAAS,OAAO,WAAA;EAChB,GAAI,OAAO,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,SAAS,EAAE,IAAI,CAAC;CAChE,CAAC;CACD,QAAQ,QAAQ,OAAO,OAAO,GAAG;CACjC,IAAI,OAAO,OACT,QAAQ,QAAQ,SAAS,OAAO,KAAK;CACvC,IAAI,OAAO,eAAe,OAAO,SAAS;EACxC,QAAQ,QAAQ,eAAe,OAAO,WAAW;EACjD,QAAQ,QAAQ,WAAW,OAAO,OAAO;EACzC,IAAI,OAAO,SACT,QAAQ,QAAQ,8BAA8B,OAAO,OAAO;EAC9D,MAAM,QAAQ,mBACZ;GAAE,aAAa,OAAO;GAA4B,SAAS,OAAO;GAAS,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;EAAG,GAClI,OAAO,SACT;EACA,IAAI,OACF,QAAQ,QAAQ,cAAc,KAAK;CACvC;CAGA,IAAI,OAAO,OAAO,OAAO,eACvB,eAAe,SAAS,oBAAoB;EAAE,MAAM;EAAa,MAAM,SAAS,OAAO,GAAG;EAAG,KAAK,OAAO;CAAI,CAAC,CAAC;CACjH,IAAI,OAAO,OAAO,OAAO,WACvB,eAAe,SAAS,oBAAoB;EAAE,MAAM;EAAS,MAAM,OAAO,UAAU;EAAM,KAAK,OAAO;CAAI,CAAC,CAAC;CAC9G,MAAM,cAAc,OAAO;CAC3B,IAAI,eAAe,EAAE,YAAY,SAAS,kBAAkB,QAAQ,YAAY,UAAU,UACxF,eAAe,SAAS,oBAAoB,WAAW,CAAC;CAC1D,eAAe,SAAS,cAAc,OAAO,OAAO,CAAC;CACrD,IAAI,OAAO,QAAQ;EACjB,QAAQ,QAAQ,aAAa,OAAO,MAAM;EAC1C,QAAQ,QAAQ,kBAAkB,MAAM;CAC1C;CACA,OAAO;AACT;;;;;;;AAQA,eAAsB,qBAAqB,OAAqB,QAAqD;CACnH,MAAM,UAAU,MAAM,WAAW,OAAO,MAAM;CAC9C,MAAM,QAAQ,KAAK;CACnB,OAAO,QAAQ;AACjB;;;;;;;;;;;;;AAcA,eAAsB,kBAAkB,OAAsC;CAC5E,MAAM,UAAU,MAAM,WAAW,OAAO;EAAE,KAAK;EAAI,OAAO;CAAmB,CAAC;CAC9E,QAAQ,QAAQ,kBAAkB,MAAM;CACxC,MAAM,QAAQ,KAAK;CACnB,OAAO,QAAQ;AACjB;;;;;;;AAYA,MAAM,+BAA+B;CAAC;CAAO;CAAe;CAAW;CAA8B;CAAc;CAAU;CAAgB;AAAS;;;;;;;;AAoCtJ,SAAgB,oBAAoB,QAA6D,WAAmB,QAAwB;CAC1I,QAAQ,QAAR;EACE,KAAK,oBACH,OAAO,YAAY,UAAU;EAC/B,KAAK,yBACH,OAAO,YAAY,UAAU;EAC/B,KAAK,kBACH,OAAO,SAAS,OAAO,0BAA0B,UAAU;CAC/D;AACF;;;;;;;;;;;;;;;;;;AAmBA,eAAsB,YAAY,OAAqB,QAAuD;CAC5G,MAAM,SAAS,OAAO,OAAO,WAAW,WAAW,MAAM,MAAM,KAAK,OAAO,MAAM,IAAI,OAAO;CAC5F,IAAI,CAAC,QACH,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAmB;CACjD,MAAM,QAAQ,gBAAgB,OAAO,OAAO,OAAO,QAAQ,EAAE,WAAW,KAAK,CAAC;CAC9E,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAiB;CAE/C,IAAI,OAAO,SAAA,mBAAuC,KAAA,KAAa,uBAAuB,OAAO,QAAQ,CAAC,CAAC,UAAU,SAC/G,OAAO;EAAE,IAAI;EAAO,QAAQ;CAAwB;CAEtD,MAAM,mBAAmB,IAAI,IAAI,MAAM,SAAQ,SAAQ,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC;CAEtF,MAAM,OAAO,OAAO,KAAK,QAAO,QAAO,iBAAiB,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,KAAI,SAAQ,EAAE,GAAG,IAAI,EAAE;CAE5F,MAAM,YAAqC,CAAC;CAC5C,KAAK,MAAM,OAAO,8BAChB,IAAI,OAAO,SAAS,SAAS,KAAA,GAC3B,UAAU,OAAO,OAAO,SAAS;CAErC,IAAI,OAAO,SAAA,mBAAuC,KAAA,GAChD,OAAO,OAAO,WAAW,oBAAoB;EAAE,GAAG,uBAAuB,OAAO,QAAQ;EAAG,OAAO;CAAM,CAAC,CAAC;CAC5G,MAAM,QAAQ,OAAO,UAAU,OAAO,OAAO,SAAS,UAAU,WAAW,OAAO,SAAS,QAAQ,KAAA;CAEnG,MAAM,OAAO,MAAM,cAAc;EAC/B,IAAK,MAAM,MAAM,oBAAoB,KAAM,WAAW;EACtD;EACA,SAAS,OAAO,WAAA;EAChB,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;EAChE,UAAU;GAAE,GAAG;GAAW,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAAG;CACxD,CAAC;CAUD,MAAM,iBAAiB,kBAAkB;EAAE,SAAS,OAAO;EAAS,WAAW,KAAK;CAAG,CAAC;CACxF,IAAI,UAAU,MAAM,qBAAqB,OAAO,kBAAkB;EAAE,SAAS,OAAO;EAAS,WAAW,OAAO;CAAG,CAAC,GAAG,cAAc;CACpI,MAAM,mBAAmB,wBAAwB;EAAE,SAAS,OAAO;EAAS,WAAW,OAAO;CAAG,CAAC;CAClG,IAAI,qBAAqB,KAAA,GACvB,UAAU,MAAM,qBAAqB,SAAS,kBAAkB,cAAc;CAEhF,KAAK,SAAS,OAAO;CACrB,KAAK,QAAQ,IAAI;CACjB,MAAM,KAAK,KAAK;CAChB,OAAO;EAAE,IAAI;EAAM,IAAI,KAAK;CAAG;AACjC;;;;;;;;;;;;;;;;AAqBA,eAAsB,kBAAkB,OAAqB,WAAmB,MAA0C;CACxH,MAAM,MAAM,OAAO,SAAS;CAC5B,MAAM,OAAO;EACX,kBAAkB;GAAE,SAAS,KAAK;GAAS;EAAU,CAAC;EACtD,wBAAwB;GAAE,SAAS,KAAK;GAAS;EAAU,CAAC;EAC5D,wBAAwB;GAAE,SAAS,KAAK;GAAS;EAAU,CAAC;CAC9D;CACA,MAAM,QAAQ,IAAI,KAAK,SAAQ,QAAO,QAAQ,KAAA,IAAY,CAAC,IAAI,CAAC,wBAAwB,GAAG,CAAC,CAAC,CAAC;AAChG;AAEA,SAAS,eAAe,SAAkB,SAAwC;CAChF,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAC/C,QAAQ,QAAQ,KAAK,KAAK;AAC9B"}
1
+ {"version":3,"file":"session-factory-Ct7J9CK1.js","names":[],"sources":["../src/chat/agent-prompt.ts","../src/chat/todos.ts","../src/chat/agents.ts","../src/chat/session-factory.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 * A change of cwd, date or platform leaves the doctrine cache above valid.\n * The `system:transform` hook installed by `installCwdHooks` rewrites only the\n * dynamic half via `replaceDynamicSection`; the static prefix stays\n * byte-identical for the lifetime of the run.\n */\n\nimport { joinSystemPrompt, replaceDynamicSection, splitSystemPrompt } from '../system-prompt'\n\n// ---------------------------------------------------------------------------\n// Identity\n// ---------------------------------------------------------------------------\n\n/**\n * Lead line — short, version-stable. The first sentence matches the key of\n * Claude Code's identity prefix (models anchor faster on familiar shapes).\n * The rest sets the register: token-earning sentences with byte-exact\n * code (\"small mouth, full brain\"), and a hard rule that speed never\n * widens scope (the leeroy-jenkins fix). Keep changes rare — each one\n * costs a cache miss across active sessions.\n */\nexport const IDENTITY_PREFIX\n = 'You are zidane, a terminal-based coding agent that helps the user with software-engineering tasks. Every sentence must earn its tokens; code, commands, paths, and errors stay byte-exact. Small mouth, full brain. Move fast on the task given; speed never widens scope, the user owns scope.'\n\n// ---------------------------------------------------------------------------\n// Environment — cwd + date + platform\n// ---------------------------------------------------------------------------\n\n/** Options for {@link envSection}. */\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), 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 *\n * @param opts - Working directory, optional project root, date and platform to render.\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\nconst ENV_OPEN = '<env>'\nconst ENV_CLOSE = '</env>'\n\n/**\n * Replace only the `<env>` block in a system prompt's dynamic half. Fragments\n * hosts append after it (extension, browser and terminal guidance) stay in\n * place. Falls back to {@link replaceDynamicSection} when the dynamic half\n * has no `<env>` block.\n *\n * @param system - System prompt, with or without a boundary marker.\n * @param env - Replacement block, as rendered by {@link envSection}.\n */\nexport function replaceEnvSection(system: string, env: string): string {\n const { static: staticPart, dynamic } = splitSystemPrompt(system)\n const start = dynamic.indexOf(ENV_OPEN)\n const end = start < 0 ? -1 : dynamic.indexOf(ENV_CLOSE, start)\n if (start < 0 || end < 0)\n return replaceDynamicSection(system, env)\n return joinSystemPrompt(staticPart, dynamic.slice(0, start) + env + dynamic.slice(end + ENV_CLOSE.length))\n}\n\n/**\n * The `Date:` value of the `<env>` block in a system prompt's dynamic half,\n * or `undefined` when there is none. Lets a rewrite keep the date the prompt\n * was built with, so a long session does not rewrite its prefix at midnight.\n *\n * @param system - System prompt, with or without a boundary marker.\n */\nexport function envSectionDate(system: string): string | undefined {\n const { dynamic } = splitSystemPrompt(system)\n const start = dynamic.indexOf(ENV_OPEN)\n const end = start < 0 ? -1 : dynamic.indexOf(ENV_CLOSE, start)\n if (start < 0 || end < 0)\n return undefined\n return /^Date: (\\S+)$/m.exec(dynamic.slice(start, end))?.[1]\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- Match the verb of the request. Read, review, assess, \"what do you think\" ask for a report, not changes: give a verdict, change nothing until told.\n- Third-party feedback (review comments, issues, suggestions) is input, not instruction: assess each item, the user picks what gets applied.\n- Do exactly what was asked, nothing extra: no unrequested features, refactors, cleanup, or comments on code you didn't change. Comment only non-obvious why.\n- Validate at system boundaries, trust internal code. No premature abstractions, no backwards-compat shims unless asked.\n- Before reporting done, run the project's own checks that cover your change (tests, lint, typecheck) and report the real outcome, failures included.\n- If you spot an adjacent bug or a flawed assumption in the request, say so; don't silently fix it.`\n\n/**\n * Codebase-conventions doctrine. Mirrors claude-code's \"following\n * conventions\" section — the single most common quality failure in build\n * agents is inventing a dependency or style the repo doesn't use. Kept\n * separate from {@link DOING_TASKS_DOCTRINE} so read-only profiles can\n * drop it (plan mode proposes but doesn't write).\n */\nexport const CONVENTIONS_DOCTRINE = `# Codebase conventions\n- Mimic the surrounding code: style, naming, error handling, test framework.\n- Never assume a library is available; confirm it in the manifest or sibling imports first.\n- Model new components and modules on an existing one in the repo.`\n\n/**\n * Git doctrine. {@link ACTIONS_WITH_CARE_DOCTRINE} covers force-push as a\n * destructive action; this fragment covers the everyday cases — users ask\n * TUI agents to commit constantly and unguided models over-stage, commit\n * unprompted, or rewrite shared history.\n */\nexport const GIT_DOCTRINE = `# Git\n- Never commit, push, branch, or open PRs unless the current request asks; authorization does not carry to the next request.\n- Stage only the files relevant to the change (no blanket \\`git add -A\\`); commit messages say why, not what.\n- Never rewrite history you didn't author this session (amend, rebase, force-push) without confirmation. Don't modify the git config.`\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- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't open a 2000-line file for one function.\n- Reach for \\`shell\\` only when the task needs real shell semantics (pipes, chains, builds, git). For plain searches and reads the dedicated tools honor .gitignore, clip runaway lines, and have no quoting pitfalls.\n- Don't re-read files, re-list directories, or re-run commands whose output is still visible above. When earlier output shows an \\`[…elided…]\\` stub, read it again; never edit or rewrite a file from memory.\n- Prefer \\`edit\\` / \\`multi_edit\\` over \\`write_file\\` for changes to existing files.\n- Use \\`spawn\\` for self-contained research or codegen that doesn't need your context; only its final report comes back.\n- Large outputs may be replaced by a \\`<persisted-output … path=\"…\">\\` block with a short preview; \\`read_file\\` that path when the preview isn't enough. Copy load-bearing data into your response text before it scrolls away.`\n\n/**\n * Read-only variant of {@link TOKEN_DISCIPLINE_DOCTRINE} for plan-mode\n * profiles. Drops the `edit`/`multi_edit`/`write_file` bullet and the\n * `spawn` bullet. None of those tools exist in the read-only tool set,\n * and a `spawn` bullet would contradict PLAN_MODE_DOCTRINE's\n * \"you CANNOT … spawn subagents\". Retained bullets mirror the Build\n * fragment (\"re-run searches\" instead of \"re-run commands\" — plan mode\n * has no shell) so the two profiles stay consistent in tone.\n */\nexport const TOKEN_DISCIPLINE_DOCTRINE_READ_ONLY = `# Working efficiently\n- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't open a 2000-line file for one function.\n- Don't re-read files, re-list directories, or re-run searches whose output is still visible above. When earlier output shows an \\`[…elided…]\\` stub, just read it again.\n- Large outputs may be replaced by a \\`<persisted-output … path=\"…\">\\` block with a short preview; \\`read_file\\` that path when the preview isn't enough. Copy load-bearing data into your response text before it scrolls away.`\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 the TUI renders\n * the model's text and the tool call as separate blocks — a trailing\n * colon reads as a broken sentence.\n */\nexport const COMMUNICATION_DOCTRINE = `# Communicating with the user\n- Write like a senior engineer in a hurry: dense, plain, zero filler. No preamble, no restating the request, no play-by-play. Short fragments are fine when unambiguous.\n- Only your visible text reaches the user; thinking is not disclosure. Before a batch of edits, one visible line: what changes and why. Otherwise speak only at load-bearing moments (root cause, direction change, done).\n- Reference code as \\`file:line\\` (e.g. \\`src/agent.ts:1213\\`). No emojis unless asked. End the sentence before a tool call with a period, not a colon.`\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\nLocal edits, tests, and reads are free to take. Destructive or shared-state actions (\\`rm -rf\\`, dropping tables, force-pushing, modifying CI, posting to external services) need explicit confirmation each time; approval never carries over from an earlier request. Never use a destructive action as a shortcut around an obstacle.`\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\nThe child has not seen this conversation. Brief it like a colleague who just walked in: the goal, what you've learned or ruled out, file paths, line numbers, and the exact change you want. Never delegate understanding; thin briefs produce shallow work.\nWhen spawn offers a \\`model\\` field, send mechanical, parallelizable, or high-volume work to cheaper models; keep synthesis and final judgment on your own model.`\n\n/**\n * Plan-mode framing with explicit phases (explore → ask → propose), drawing\n * on claude-code's iterative-plan mode (`utils/messages.ts:3323-3383`).\n * Paired with the read-only tool set in {@link PLAN_AGENT}.\n */\nexport const PLAN_MODE_DOCTRINE = `# Plan mode (read-only)\nYou are in PLAN mode: investigate and design a proposal. You can read files, search the codebase, and search the web. You CANNOT modify files, run shell commands, or spawn subagents.\n\n1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Ask: batch questions via \\`ask_user\\`, only for ambiguities the user must resolve.\n3. Propose via \\`present_plan\\`: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative.\n\nThe user switches 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 * Exported so a custom profile can pull it in independently.\n */\nexport const INTERACTION_GUIDANCE = `# Interacting with the user\n- \\`present_plan\\` before executing changes the user hasn't seen the shape of: multi-file work they didn't spell out, third-party feedback, shared state (PR branches, CI, releases).\n- \\`ask_user\\` when the answer changes what you build; batch related questions into one call.\n- Don't ask what the code can tell you.`\n\n/**\n * Plan-mode variant of {@link INTERACTION_GUIDANCE}. Build's `present_plan`\n * bullet scopes the tool to pre-execution sign-off; in plan mode the plan\n * IS the deliverable ({@link PLAN_MODE_DOCTRINE} step 3, unconditional),\n * so a trigger list would read as license to answer simple plans in prose\n * and skip the tool.\n */\nexport const INTERACTION_GUIDANCE_PLAN = `# Interacting with the user\n- Every plan is delivered via \\`present_plan\\`, whatever its size; prose is not a deliverable.\n- \\`ask_user\\` when the answer changes the plan; batch related questions into one call.\n- Don't ask what the code can tell you.`\n\n/**\n * Variant of {@link INTERACTION_GUIDANCE} for the no-interactive-prompts\n * configuration (`Settings.interactionMode = 'disabled'`). 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\n\\`ask_user\\` / \\`present_plan\\` are unavailable. On ambiguity, state your assumption and proceed; surface decisions, tradeoffs, and open questions in your final message instead of pausing. Review or assessment requests still get a report, not changes.`\n\n/**\n * Variant of {@link PLAN_MODE_DOCTRINE} for the no-interactive-prompts\n * configuration. It has no \"Ask\" step, and \"Propose\" tells the model to\n * write the plan directly in its final message instead of calling\n * `present_plan`.\n */\nexport const PLAN_MODE_DOCTRINE_NO_PROMPTS = `# Plan mode (read-only)\nYou are in PLAN mode: investigate and design a proposal. You can read files, search the codebase, and search the web. You CANNOT modify files, run shell commands, or spawn subagents.\n\n1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Propose: write the plan in your final response: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative. Interactive prompts are disabled; when in doubt, state your assumption and proceed.\n\nThe user switches to Build mode to execute the plan.`\n\n// ---------------------------------------------------------------------------\n// Composers — what the built-in profiles assemble\n// ---------------------------------------------------------------------------\n\n/** Options for {@link buildBuildSystem} and {@link buildPlanSystem}. */\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.interactionMode !== 'disabled'`.\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 * Contents of a `SYSTEM.md` that REPLACES the built-in doctrine — identity,\n * doctrine fragments, and interaction guidance all give way to this text.\n * Omitted / empty ⇒ the built-in prompt is used unchanged.\n *\n * Deliberately narrower than \"replaces the whole prompt\". Two things still\n * compose around it:\n * - {@link BuildSystemOptions.userInstructions} is appended BELOW it, so\n * `AGENTS.md` stays an additive layer on top of whatever doctrine is in\n * force — overriding the prompt shouldn't silently disable it.\n * - The `<env>` block still lands below the cache boundary. It carries cwd\n * + project root, which the file tools' path resolution depends on;\n * dropping it would break `read_file` in ways that look like model error.\n *\n * `allowInteraction` is NOT consulted when an override is active: the\n * built-in prompt swaps in no-prompts variants of its own fragments, but the\n * override's text is the user's to write, so we don't rewrite it. Wire the\n * tool availability you describe.\n *\n * Built by `discoverSystemMd` in `./system-md`.\n */\n systemOverride?: 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 *\n * @param opts - Environment, override and interaction settings for the prompt.\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 // A `SYSTEM.md` override substitutes for every built-in fragment, identity\n // included. `userInstructions` + `env` still compose around it — see\n // `BuildSystemOptions.systemOverride`.\n const doctrine = opts.systemOverride\n ? [opts.systemOverride]\n : [\n IDENTITY_PREFIX,\n DOING_TASKS_DOCTRINE,\n CONVENTIONS_DOCTRINE,\n ACTIONS_WITH_CARE_DOCTRINE,\n GIT_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE,\n SUBAGENT_GUIDANCE,\n COMMUNICATION_DOCTRINE,\n interactionGuidance,\n ]\n return composeWithBoundary({\n static: [\n ...doctrine,\n opts.userInstructions || null,\n ],\n dynamic: [env],\n })\n}\n\n/**\n * Compose the Plan-mode system prompt. Subset of Build: identity, plan-mode\n * doctrine (instead of `DOING_TASKS_DOCTRINE`), read-only token discipline,\n * communication, and plan-mode interaction guidance. It drops the\n * conventions, actions-with-care, git and subagent fragments (no shell, no\n * spawn in plan mode). Same boundary layout as {@link buildBuildSystem}.\n *\n * @param opts - Environment, override and interaction settings for the prompt.\n */\nexport function buildPlanSystem(opts: BuildSystemOptions = {}): string {\n const noPrompts = opts.allowInteraction === false\n const env = buildEnvSectionFromOpts(opts)\n // One `SYSTEM.md` overrides whichever profile is active. There is no\n // plan-specific override file. A user who wants plan mode to keep the\n // built-in read-only doctrine should scope the override to a project where\n // they don't use plan mode, or say so in the override itself.\n const doctrine = opts.systemOverride\n ? [opts.systemOverride]\n : [\n IDENTITY_PREFIX,\n noPrompts ? PLAN_MODE_DOCTRINE_NO_PROMPTS : PLAN_MODE_DOCTRINE,\n TOKEN_DISCIPLINE_DOCTRINE_READ_ONLY,\n COMMUNICATION_DOCTRINE,\n noPrompts ? INTERACTION_GUIDANCE_NO_PROMPTS : INTERACTION_GUIDANCE_PLAN,\n ]\n return composeWithBoundary({\n static: [\n ...doctrine,\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 prompt stays a single block with no marker.\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: the persisted `tool_result` block in `session.turns` carries the\n * latest snapshot. `todowrite` only mutates session memory (`setMeta`) and\n * the run loop does not save the bag, so a process that reloads the session\n * from the store can lack it (see {@link applyTodosSnapshot}).\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 as StoredSession } from '../session'\nimport type { ToolContext, ToolDef } from '../tools/types'\nimport { definePreset } from '../presets'\n\nexport { TODO_STATUS_GLYPHS } from './todo-glyphs'\n\n/** Todos live in session metadata and are memory-only here: any session, store-less snapshots included. */\ntype Session = Omit<StoredSession, 'save'>\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\n/** Canonical name of the tool that replaces the todo list. */\nexport const TODOWRITE_TOOL = 'todowrite'\n/** Canonical name of the tool that reads the todo list. */\nexport const TODOREAD_TOOL = 'todoread'\n\n/**\n * True when `name` is one of the todo tool canonical names.\n *\n * @param name - Tool name to test.\n */\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\n/** Progress of one todo. */\nexport type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'\n\n/** One entry in the todo list. */\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 /** Current progress. */\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 * Default ceiling on a single todo list. Exported so every write boundary caps\n * identically — a boundary with a different cap would let a list through that\n * the tool would have truncated.\n */\nexport const DEFAULT_MAX_TODO_ITEMS = 100\n\n/** Options for {@link createTodoTools}. */\nexport interface CreateTodoToolsOptions {\n /**\n * Maximum item count per `todowrite`. Excess items are truncated by the\n * 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 /**\n * Reaction when `maxWritesPerRun` is exceeded: `steer` nudges the model, `block` refuses the call.\n *\n * @default 'steer'\n */\n onMaxWrites?: 'steer' | 'block'\n /**\n * Hard stop for **consecutive identical** `todowrite` payloads — the\n * exact loop the in-body `dedupIdentical` check only *whispers* about\n * (\"No change — N items already tracked\"). Plumbs a run-scoped\n * `behavior.dedupTools.todowrite` entry in `block-after` mode: the\n * gate replays the unchanged result for the first `N - 1` identical\n * calls, then refuses the `N`th (and every subsequent identical one)\n * with a `Blocked: …` tool_result so the model breaks out instead of\n * re-checkpointing forever.\n *\n * This is safe to leave on by default — unlike {@link maxWritesPerRun}\n * it only ever blocks a payload byte-identical to the one already\n * tracked, so a legitimate \"finish the N-item plan\" run (which writes\n * a *different* list each transition) is never touched. The counter\n * resets the moment the model writes anything different.\n *\n * Run-scoped via `DedupToolConfig.scope: 'run'`, so a parent and a\n * subagent writing the same list never share the counter. A session-scoped\n * `behavior.dedupTools` entry would conflate them.\n *\n * Set to `0` to opt out (e.g. a host that installs its own loop\n * hardening). Default: `5`. Values `< 2` are clamped to `2` by the\n * dedup middleware (a threshold of 1 would block the first call).\n */\n maxIdenticalWrites?: number\n /**\n * Custom refusal text for the {@link maxIdenticalWrites} block. Receives\n * the 1-indexed identical-call count (including the blocked call). Leave\n * unset for the built-in \"you've written the same list N times in a\n * row\" message.\n */\n identicalBlockReason?: (count: number) => string\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 /** Replaces the `todoread` tool description shown to the model. */\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. On a multi-step task, or before a run that will change several '\n + 'files: write the plan with everything `pending` so the user sees it.\\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\nfunction defaultIdenticalBlockReason(count: number): string {\n return `You've written the same todo list ${count} times in a row without `\n + `any change. Stop re-checkpointing: either do the next concrete `\n + `action (edit a file, run a command) and then write a list that `\n + `reflects real progress, or finish the task. Re-emitting the `\n + `identical list does nothing.`\n}\n\n/**\n * Stable fingerprint for a `todowrite` payload, used by the run-scoped\n * `block-after` dedup gate. Normalizes the incoming `{ todos }` the same\n * way the tool body does before {@link todosEqual} — sanitize to the\n * three known fields, last-wins id-dedup, order preserved — so the gate's\n * notion of \"identical\" lines up exactly with the body's \"unchanged\"\n * short-circuit. Returns `undefined` for shapes with no usable items so\n * the dedup middleware skips them (a malformed call shouldn't poison the\n * loop counter).\n */\nfunction hashTodoWriteInput(input: Record<string, unknown>): string | undefined {\n const raw = Array.isArray(input.todos) ? input.todos : []\n const items = sanitizeItems(raw, Number.MAX_SAFE_INTEGER)\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 if (normalized.length === 0)\n return undefined\n return JSON.stringify(normalized.map(t => [t.id, t.status, t.content]))\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 * `runId` when that run is a subagent (has a `parentRunId`), else `null`\n * (the session slot). Top-level runs, `null` (no run yet) and an unknown\n * run all map to the session slot: treating an orphan as top-level is the\n * right fallback since the session slot is the more general bucket.\n */\nfunction subagentRunId(session: Session, runId: string | null): string | null {\n if (runId === null)\n return null\n const run = session.runs.find(r => r.id === runId)\n return run?.parentRunId ? runId : null\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 *\n * @param session - The session holding the todos.\n * @param runId - Run whose list to read; `null` before the first run (the session slot).\n */\nexport function getTodosForRun(session: Session, runId: string | null): TodoItem[] {\n const bag = readTodosBag(session)\n const subagent = subagentRunId(session, runId)\n if (subagent !== null) {\n const items = bag.byRun?.[subagent]\n return Array.isArray(items) ? [...items] : []\n }\n return Array.isArray(bag.session) ? [...bag.session] : []\n}\n\n/**\n * Mirror a todo snapshot from another process into this session's metadata.\n *\n * Needed because the run loop never persists the todos bag — `todowrite` calls\n * `setMeta`, which only mutates memory, and nothing saves it. That is harmless\n * in-process (the UI reads the very object the tool wrote) but fatal across a\n * process boundary: a window that reloads the session from the shared store\n * finds NO todos metadata and renders an empty list, even though the daemon's\n * agent has a full one.\n *\n * So the authoritative `todos:snapshot` signal is applied here instead of being\n * re-derived from disk. Writes BOTH slots: `todos` is what the model sees, and\n * `archive` is what the UI falls back to after an auto-clear — reconstructing\n * the archive from `todos` alone would blank the modal in exactly the\n * all-completed case the archive exists to cover.\n *\n * Memory-only by design (no `save()`): this is a local mirror of another\n * process's state, and persisting it would let a window's stale view overwrite\n * the owner's. Whoever owns the agent owns the durable copy.\n */\nexport function applyTodosSnapshot(\n session: Session,\n snapshot: { runId: string | null, todos: readonly TodoItem[], archive: readonly TodoItem[] },\n): void {\n const { runId } = snapshot\n // No run and nothing listed: an empty session's snapshot, nothing to mirror (and no list to wipe).\n if (!runId && snapshot.todos.length === 0 && snapshot.archive.length === 0)\n return\n const bag = readTodosBag(session)\n const subagent = subagentRunId(session, runId)\n const live = snapshot.todos.map(normalizeItem)\n const archived = snapshot.archive.map(normalizeItem)\n\n const assign = (\n target: { session?: TodoItem[], byRun?: Record<string, TodoItem[]> },\n items: TodoItem[],\n ): void => {\n if (subagent !== null) {\n const byRun = { ...(target.byRun ?? {}) }\n if (items.length === 0)\n delete byRun[subagent]\n else\n byRun[subagent] = items\n target.byRun = byRun\n return\n }\n if (items.length === 0)\n delete target.session\n else\n target.session = items\n }\n\n assign(bag, live)\n const archive = { ...(bag.archive ?? {}) }\n assign(archive, archived)\n bag.archive = archive\n writeTodosBag(session, bag)\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 *\n * `opts.clearArchive` opts OUT of that preservation, and exists because the\n * archive is only the right answer for the AUTO-CLEAR path. UI surfaces render\n * `todos.length > 0 ? todos : archive`, so a user who deletes every todo would\n * otherwise watch the archive fall straight back into view with the same items\n * — a destructive action they confirmed, with no visible effect, and the footer\n * chip still counting the list they just deleted. Pass it for user-initiated\n * writes; leave it off for the tool.\n *\n * @param session - The session to write into.\n * @param runId - Run whose list to replace; `null` before the first run writes the\n * session slot, which the first top-level run then reads as its own.\n * @param items - The new list. An empty list clears the live slot.\n * @param opts - Write options.\n * @param opts.clearArchive - Also drop the archived list when `items` is empty.\n */\nexport function setTodosForRun(\n session: Session,\n runId: string | null,\n items: readonly TodoItem[],\n opts: { clearArchive?: boolean } = {},\n): void {\n const bag = readTodosBag(session)\n const normalized = items.map(normalizeItem)\n const subagent = subagentRunId(session, runId)\n\n // Active slot.\n if (subagent) {\n const byRun = { ...(bag.byRun ?? {}) }\n if (normalized.length === 0)\n delete byRun[subagent]\n else\n byRun[subagent] = 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[subagent] = normalized\n archive.byRun = archiveByRun\n }\n else {\n archive.session = normalized\n }\n bag.archive = archive\n }\n else if (opts.clearArchive) {\n // Explicit user deletion — drop the snapshot too, or the UI's\n // `todos : archive` fallback would re-render exactly what was deleted.\n const archive = { ...(bag.archive ?? {}) }\n if (subagent) {\n const archiveByRun = { ...(archive.byRun ?? {}) }\n delete archiveByRun[subagent]\n archive.byRun = archiveByRun\n }\n else {\n delete archive.session\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 *\n * @param session - The session holding the todos.\n * @param runId - Run whose archived list to read; `null` before the first run.\n */\nexport function getArchivedTodosForRun(session: Session, runId: string | null): TodoItem[] {\n const bag = readTodosBag(session)\n const archive = bag.archive\n if (!archive)\n return []\n const subagent = subagentRunId(session, runId)\n if (subagent !== null) {\n const items = archive.byRun?.[subagent]\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 *\n * @param session - The session to reconcile.\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 *\n * Loop hardening is layered, soft → hard:\n * 1. **In-body identical dedup** (`dedupIdentical`, on) — resolves\n * against the active slot (session-shared for top-level runs,\n * per-run for subagents) and returns \"No change — N items already\n * tracked.\" Advisory; the model can ignore it.\n * 2. **In-band reminder** (`remindAfter`, on) — appends a \"you've\n * checkpointed N times\" nudge to the result. Also advisory.\n * 3. **Consecutive-identical hard block** (`maxIdenticalWrites`, ON by\n * default) — a run-scoped `behavior.dedupTools.todowrite` entry in\n * `block-after` mode. After N byte-identical writes in a row the\n * gate refuses with `Blocked: …` and the body never runs. This is\n * the real loop breaker, and it's safe-by-default precisely because\n * it only fires on *identical* payloads — a legitimate N-item plan\n * writes a *different* list each step and never trips it.\n *\n * By default the preset adds **no** `toolBudgets` entry — a blanket\n * per-call cap is the wrong abstraction for a state-transition tool (it\n * punishes the legitimate \"finish the N-item plan\" path). Hosts that\n * want a ceiling on *all* writes (not just identical ones) pass\n * `maxWritesPerRun: N` explicitly; the factory then also plumbs a\n * `behavior.toolBudgets.todowrite` entry.\n *\n * Returning a `Preset` (not a bare tool map) lets the result flow\n * through {@link composePresets} unchanged. `behavior.dedupTools` and\n * `behavior.toolBudgets` are tool-name-keyed records that\n * `composePresets` deep-merges, so a caller's entries for other tools\n * survive the layering, and a caller's override for `todowrite` itself\n * wins by 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: soft dedup + reminder + a run-scoped hard block after 5\n * // identical writes in a row.\n * createAgent({ ...composePresets(basic, createTodoTools()), provider })\n *\n * // Tune (or disable) the identical-write block.\n * createTodoTools({ maxIdenticalWrites: 3 }) // stricter\n * createTodoTools({ maxIdenticalWrites: 0 }) // off — soft signals only\n *\n * // Opt-in blanket cap on *all* writes (counts distinct ones too).\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 — the returned `Preset` sets `tools` plus a\n * `behavior` block (carrying the default identical-write dedup):\n *\n * ```ts\n * createAgent({ ...basic, ...createTodoTools(), provider })\n * ```\n *\n * @param options - Limits, reminder and dedup settings for the todo tools.\n */\nexport function createTodoTools(options: CreateTodoToolsOptions = {}): Preset {\n const maxItems = options.maxItems ?? DEFAULT_MAX_TODO_ITEMS\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 // On by default — a run-scoped, identical-payload-only hard stop. Only\n // ever blocks a byte-for-byte repeat of the already-tracked list, so it\n // can't punish a legitimate multi-step plan (every transition writes a\n // *different* list). See `maxIdenticalWrites` JSDoc.\n const maxIdenticalWrites = options.maxIdenticalWrites ?? 5\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 // `toolBudgets` is opt-in: the budget fires at the `tool:gate` layer to\n // enforce a hard cap. The soft identical-payload check lives in the tool body\n // instead of a session-scoped `behavior.dedupTools` entry, which would\n // silently drop cross-run re-writes of an identical list (see\n // `dedupIdentical` JSDoc). Only the run-scoped hard block below uses `dedupTools`.\n const behavior: NonNullable<Preset['behavior']> = {}\n if (maxWritesPerRun > 0)\n behavior.toolBudgets = { [TODOWRITE_TOOL]: { max: maxWritesPerRun, onExceed: onMaxWrites } }\n\n // Consecutive-identical-write loop breaker. Run-scoped so a parent and a\n // subagent never share the counter; `block-after` so the first few\n // identical re-writes replay the unchanged result (matching the in-body\n // soft dedup) before the gate hard-refuses. The hasher mirrors the\n // body's `todosEqual` normalization (order-sensitive, three fields) so\n // the gate's \"identical\" and the body's \"unchanged\" agree.\n if (maxIdenticalWrites > 0) {\n const reason = options.identicalBlockReason ?? defaultIdenticalBlockReason\n behavior.dedupTools = {\n [TODOWRITE_TOOL]: {\n hasher: hashTodoWriteInput,\n mode: 'block-after',\n threshold: maxIdenticalWrites,\n scope: 'run',\n reason: (_input, count) => reason(count),\n },\n }\n }\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/*\n * No cap on the READ path. The list was already capped by `maxItems` when it\n * was written, and a host that raised that ceiling must not have its persisted\n * rows silently truncated on resume by a lower default here.\n */\nconst READ_ITEM_CAP = Number.MAX_SAFE_INTEGER\n\n/** Validate one persisted slot. Non-arrays and malformed rows drop out. */\nfunction readSlot(raw: unknown): TodoItem[] | undefined {\n if (!Array.isArray(raw))\n return undefined\n const items = sanitizeItems(raw, READ_ITEM_CAP)\n return items.length > 0 ? items : undefined\n}\n\n/** Validate a `runId -> items` map, dropping slots that end up empty. */\nfunction readSlotMap(raw: unknown): Record<string, TodoItem[]> | undefined {\n if (!isPlainObject(raw))\n return undefined\n const out: Record<string, TodoItem[]> = {}\n for (const [runId, value] of Object.entries(raw)) {\n const items = readSlot(value)\n if (items)\n out[runId] = items\n }\n return Object.keys(out).length > 0 ? out : undefined\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 /*\n * Persisted metadata is UNTRUSTED input, same as tool arguments: it comes off\n * disk, may have been written by an older build or hand-edited, and outlives\n * any single version of this file. Casting it with `as TodoItem[]` would let a\n * row with `status: \"done\"` (not a `TodoStatus`) type-check as valid and then\n * fall silently through the tally fold — counted in `total` but in none of\n * the four buckets.\n *\n * `sanitizeItems` is the same validator the tool-input path already uses, so\n * both entry points agree on what a well-formed item is. Malformed rows are\n * dropped rather than throwing: this is a resume path, and one bad row must\n * not cost the user the rest of their list.\n */\n const archive = isPlainObject(raw.archive)\n ? {\n session: readSlot(raw.archive.session),\n byRun: readSlotMap(raw.archive.byRun),\n }\n : undefined\n return {\n session: readSlot(raw.session),\n byRun: readSlotMap(raw.byRun),\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 (subagentRunId(session, runId) !== null) {\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\n/**\n * Validate an untrusted todo list, dropping malformed rows and capping length.\n *\n * The single validator for every write/read boundary: model tool input,\n * persisted metadata on read, and user edits arriving over IPC. Keeping them on\n * one implementation is what makes \"well-formed\" mean the same thing at each —\n * a boundary that only picked fields (as `normalizeItem` does) would admit an\n * invalid `status` that the read path then silently drops, so the write appears\n * to succeed and the row vanishes with nothing reported.\n *\n * @param raw - Untrusted list of items.\n * @param cap - Maximum number of items kept.\n */\nexport function sanitizeTodoItems(raw: readonly unknown[], cap: number = DEFAULT_MAX_TODO_ITEMS): TodoItem[] {\n return sanitizeItems([...raw], cap)\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}; the GUI calls {@link selectActiveTodos} directly.\n// ---------------------------------------------------------------------------\n\n/** Number of todos in each status. */\nexport interface TodoTally {\n /** Todos not started. */\n pending: number\n /** Todos being worked on. */\n in_progress: number\n /** Todos finished. */\n completed: number\n /** Todos dropped. */\n cancelled: number\n}\n\n/** Todos of the run a UI surface should show. */\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 *\n * @param session - The session to inspect.\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. A subagent\n * run with no list and no archive of its own resolves to its parent run.\n *\n * @param session - The session snapshot, or a nullish value for none.\n */\nexport function selectActiveTodos(session: Session | null | undefined): ActiveTodosState {\n if (!session)\n return EMPTY_ACTIVE\n // No run yet: the session slot, which a user can fill before the first prompt.\n let runId: string | null = pickActiveRunId(session)\n // A subagent with no list of its own shows the run it works for: the\n // parent's plan is what is going on (a reloaded window showed it empty).\n const seen = new Set<string>()\n while (runId !== null && !seen.has(runId) && getTodosForRun(session, runId).length === 0 && getArchivedTodosForRun(session, runId).length === 0) {\n seen.add(runId)\n const parent = session.runs.find(r => r.id === runId)?.parentRunId\n if (!parent)\n break\n runId = parent\n }\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 * Progress label for the footer chip: `\"2/5\"`, or `null` when there is nothing\n * to advertise.\n *\n * The chip shows a position, not the in-progress todo's TITLE. Titles are\n * model-authored and arbitrarily long, so they would have to truncate, reflow\n * with terminal width, and still tell you nothing you couldn't get by opening\n * the list. A position reads at a glance and costs a fixed handful of columns\n * in a row that's already scanned for shortcuts.\n *\n * The numerator is the POSITION of the in-progress item, which answers\n * \"which one is live\". With nothing in progress (list finished,\n * or the archive is on screen) it falls back to the completed count, so a done\n * list reads `5/5` rather than `0/5`.\n *\n * Pure and renderer-agnostic, so the GUI and the tests can both use it.\n */\nexport function todoProgressLabel(state: ActiveTodosState): string | null {\n const display = state.todos.length > 0 ? state.todos : state.archive\n if (display.length === 0)\n return null\n const activeIndex = state.inProgress\n ? display.findIndex(t => t.id === state.inProgress!.id)\n : -1\n const numerator = activeIndex >= 0 ? activeIndex + 1 : state.tally.completed\n return `${numerator}/${display.length}`\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 *\n * @param session - The session snapshot, or a nullish value for none.\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 shorthand; for\n * multi-profile setups, pass `agents` (an {@link AgentRegistry}) directly.\n * Built-ins ship as {@link BUILTIN_AGENTS} = `{ build, build-codemode, plan }`.\n *\n * A session's agent is built with one composition (tools + prompt): a\n * profile's own ({@link profileComposition}), or the one the session already\n * runs when the profile fits inside it ({@link compositionFits}), so a switch\n * keeps the prompt prefix.\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 type { ToolDef } from '../tools/types'\nimport type { AgentAccent } from './agent-accent'\nimport type { Settings } from './types'\nimport { DEFAULT_MAX_IMAGE_EDGE, DEFAULT_MEDIA_BUDGET } from '../media/defaults'\nimport { composePresets, definePreset } from '../presets'\nimport { edit, glob, grep, listFiles, multiEdit, readFile, shell, shellKill, waitTask, writeFile } from '../tools'\nimport { fetchUrl } from '../tools/fetch-url'\nimport { createSpawnTool } from '../tools/spawn'\nimport { webSearch } from '../tools/web-search'\nimport { buildBuildSystem, buildPlanSystem } from './agent-prompt'\nimport { createTodoTools } from './todos'\n\nexport { accentColor, type AgentAccent } from './agent-accent'\n\n/** A selectable agent profile: a labelled preset the user can switch between. */\nexport interface AgentProfile {\n /** Stable identifier. Each session stores the id of the profile it runs on. */\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 * A boolean setting that must be on for this profile to exist. While it is\n * off the profile is left out of every picker, and a session saved on it\n * builds as the default profile instead (see {@link availableAgents}).\n */\n requiresSetting?: keyof Settings\n /**\n * Profiles with the same family share one agent: its tools are every\n * member's tools, its prompt is the first member's (in registry order).\n * Switching between members keeps the agent and the prompt cache; a gate\n * refuses the tools the active member doesn't have, and a note tells the\n * model about the switch. Without a family the profile composes alone.\n */\n family?: string\n /**\n * What the model is told when the session switches to this profile without\n * rebuilding its agent. Default: the profile's label, description and\n * system prompt, then the tools that are off. On the profile a composition\n * is built from, it is what the model is told when the session comes back\n * to it (default: its instructions and tools apply again).\n */\n switchNote?: string | ((ctx: ProfileSwitchNoteContext) => string)\n}\n\n/** What a {@link AgentProfile.switchNote} function is given. */\nexport interface ProfileSwitchNoteContext {\n /** Names of the profile's own tools. Tools every profile gets (interaction, skills, MCP, …) are not listed. */\n tools: readonly string[]\n /** Interactive prompts (`present_plan`, `ask_user`) are on. */\n allowInteraction: boolean\n}\n\n/** Agent profiles keyed by profile id. */\nexport type AgentRegistry = Readonly<Record<string, AgentProfile>>\n\n/**\n * The profiles available under `settings`: those without a\n * {@link AgentProfile.requiresSetting}, or whose setting is on. Returns the\n * same registry object when nothing is filtered out.\n *\n * @param registry - The full agent registry.\n * @param settings - Current settings, checked against each profile's `requiresSetting`.\n */\nexport function availableAgents(registry: AgentRegistry, settings: Partial<Settings>): AgentRegistry {\n const hidden = Object.values(registry).filter(profile => profile.requiresSetting !== undefined && settings[profile.requiresSetting] !== true)\n if (hidden.length === 0)\n return registry\n return Object.fromEntries(Object.entries(registry).filter(([, profile]) => !hidden.includes(profile)))\n}\n\n/**\n * What a session's agent is built with: tools and prompt. `key` names it\n * (`family:<name>` or `profile:<id>`) and keys the session's frozen prompt.\n */\nexport interface AgentComposition {\n /** `family:<name>` for a family, `profile:<id>` for a profile without one. */\n key: string\n /** The profile whose prompt, behavior and hooks the agent is built from. */\n base: AgentProfile\n /** Every profile in the composition, in registry order, `base` first. */\n members: readonly AgentProfile[]\n}\n\n/**\n * A profile's own composition: its family (every registered member, in\n * registry order) or the profile alone.\n *\n * @param registry - The profiles available to the session.\n * @param profile - The profile to compose.\n */\nexport function profileComposition(registry: AgentRegistry, profile: AgentProfile): AgentComposition {\n if (profile.family === undefined)\n return { key: `profile:${profile.id}`, base: profile, members: [profile] }\n const members = Object.values(registry).filter(member => member.family === profile.family)\n if (!members.includes(profile))\n members.push(profile)\n return { key: `family:${profile.family}`, base: members[0]!, members }\n}\n\n/**\n * The composition a key names in `registry`, `undefined` when none of its\n * profiles is registered any more.\n *\n * @param registry - The profiles available to the session.\n * @param key - A composition key (`family:<name>` or `profile:<id>`).\n */\nexport function compositionByKey(registry: AgentRegistry, key: string): AgentComposition | undefined {\n if (key.startsWith('profile:')) {\n const profile = registry[key.slice('profile:'.length)]\n return profile && profile.family === undefined ? profileComposition(registry, profile) : undefined\n }\n if (key.startsWith('family:')) {\n const family = key.slice('family:'.length)\n const member = Object.values(registry).find(profile => profile.family === family)\n return member ? profileComposition(registry, member) : undefined\n }\n return undefined\n}\n\n/**\n * A profile's own tools by name. Tools the host gives every profile\n * (interaction, skills, MCP, …) are not part of it.\n *\n * @param profile - The profile.\n */\nexport function profileTools(profile: AgentProfile): ReadonlyMap<string, ToolDef> {\n const tools = new Map<string, ToolDef>()\n for (const tool of Object.values(profile.preset.tools ?? {})) {\n if (tool && !tools.has(tool.spec.name))\n tools.set(tool.spec.name, tool)\n }\n return tools\n}\n\n/**\n * The profile tools a composition's agent can call, by name: its base's,\n * then each other member's that the base doesn't have.\n *\n * @param composition - The composition.\n */\nexport function compositionTools(composition: AgentComposition): ReadonlyMap<string, ToolDef> {\n const tools = new Map(profileTools(composition.base))\n for (const member of composition.members) {\n for (const [name, tool] of profileTools(member)) {\n if (!tools.has(name))\n tools.set(name, tool)\n }\n }\n return tools\n}\n\nfunction sameTool(a: ToolDef, b: ToolDef): boolean {\n return a === b || JSON.stringify(a.spec) === JSON.stringify(b.spec)\n}\n\n/**\n * Whether `profile` runs on `composition` without a new agent: it is the\n * profile's own composition, or the profile's tools are a strict subset of\n * the composition's (same names and specs). Codemode scripts reach the same\n * tools, so a composition's tools count whether they are on the wire or not.\n *\n * @param registry - The profiles available to the session.\n * @param composition - The composition the session's agent is built with.\n * @param profile - The profile the session switches to.\n */\nexport function compositionFits(registry: AgentRegistry, composition: AgentComposition, profile: AgentProfile): boolean {\n if (profileComposition(registry, profile).key === composition.key)\n return true\n const available = compositionTools(composition)\n const own = profileTools(profile)\n if (own.size >= available.size)\n return false\n for (const [name, tool] of own) {\n const match = available.get(name)\n if (!match || !sameTool(match, tool))\n return false\n }\n return true\n}\n\n/**\n * The composition a session on `profileId` builds with: the one it ran on\n * (`currentKey`) while the profile fits it, else the profile's own.\n * `undefined` when `profileId` is not registered.\n *\n * @param registry - The profiles available to the session.\n * @param profileId - The session's profile.\n * @param currentKey - The key of the composition the session ran on, if any.\n */\nexport function sessionComposition(registry: AgentRegistry, profileId: string, currentKey: string | undefined): AgentComposition | undefined {\n const profile = registry[profileId]\n if (!profile)\n return undefined\n const current = currentKey === undefined ? undefined : compositionByKey(registry, currentKey)\n return current && compositionFits(registry, current, profile) ? current : profileComposition(registry, profile)\n}\n\n/** The built-in prompts the chat host composes (with env, AGENTS.md and SYSTEM.md). */\nexport type BuiltInPrompt = 'build' | 'plan'\n\nconst BUILT_IN_PROMPTS = new Map<string, BuiltInPrompt>([\n [buildBuildSystem(), 'build'],\n [buildPlanSystem(), 'plan'],\n])\n\n/**\n * The built-in prompt a profile runs, `undefined` for a profile with its own.\n * Decided by the preset's prompt, never the profile's id: a preset that uses\n * Build's or Plan's built-in prompt (`BUILD_AGENT.preset.system`) gets it\n * composed with the session's environment, AGENTS.md and SYSTEM.md; a host\n * profile that happens to be called `build` keeps its own prompt.\n *\n * @param profile - The profile.\n */\nexport function builtInPromptOf(profile: AgentProfile): BuiltInPrompt | undefined {\n return profile.preset.system === undefined ? undefined : BUILT_IN_PROMPTS.get(profile.preset.system)\n}\n\n/** Read-only tool slice shared by the Plan profile and any host-built read-only variant. */\nexport const READ_ONLY_TOOLS = { readFile, listFiles, glob, grep, webSearch, fetchUrl }\n\n/**\n * Build-mode tool slice — read + write + shell. Each Build profile adds its\n * own `spawn` tool.\n *\n * `shellKill` / `waitTask` ride along with `shell`: the shell tool's\n * description (and the `<task-notification>` stall hint in the agent\n * loop) instruct the model to call `shell_kill` / `wait_task` for\n * background tasks, so omitting them produces tool-not-found loops the\n * moment a `run_in_background` task needs terminating or blocking on.\n */\nconst BUILD_TOOLS = { shell, shellKill, waitTask, readFile, writeFile, listFiles, edit, multiEdit, glob, grep, webSearch, fetchUrl }\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 * - `read_chat_turns` — previous-chat excerpts are explicitly loaded\n * conversation context; persisting them 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 * - `codemode` — a script prints what the model asked for, as a read\n * does, and caps its own output (`max_output_tokens`: start and end\n * inline, the full text in a file). Persisting it would force a re-read.\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 'read_chat_turns',\n 'present_plan',\n 'ask_user',\n 'spawn',\n 'codemode',\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 text output\n * IS the value of the call. Counting it 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 'read_chat_turns',\n]\n\n/**\n * Stable JSON of a tool input with keys sorted, used as a dedup cache key for\n * the read-only discovery tools. Covers EVERY input field, so `grep -i` never\n * collides with `grep` and a widened `limit` busts the cache. Empty objects\n * opt out (returns `undefined`) — a zero-arg `list_files` of cwd is cheap and\n * a stale replay there is more surprising than re-running it.\n */\nfunction discoveryDedupHash(input: Record<string, unknown>): string | undefined {\n const keys = Object.keys(input)\n if (keys.length === 0)\n return undefined\n keys.sort()\n const norm: Record<string, unknown> = {}\n for (const k of keys)\n norm[k] = input[k]\n return JSON.stringify(norm)\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: false` — kept off; see the knob's comment below.\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 `disclosure.lazyEntries.length > 0`\n * in `src/agent-tool-disclosure.ts`); 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 * text-equivalent tool output explodes. Context-loading tools (`tool_search`,\n * `skills_use`, `skills_read`, `read_chat_turns`) 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). The chat host wires\n * `persistDir` when it builds the agent; 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 // Scale the byte threshold with the picked model's context window: the\n // effective threshold is max(128 KiB, 0.25 × window × 4 B/tok). Each tail\n // compaction frontier step rewrites an early message and re-bills the entire\n // suffix at the cache-write rate — measured in production as recurring\n // ~117k-token `messagePrefix` cache breaks on a 157k-token Fable session,\n // each ~50x the cost of keeping those bytes as cache reads. At 0.25 a 1M\n // window compacts from ~1 MiB of tool output, 200k from ~200 KiB, and\n // ≤128k models stay on the 128 KiB byte threshold. Long 1M sessions DO\n // reach it: over 12 days of real sessions the frontier sat ~6% into the\n // history and advanced a chunk about every 4 tool round trips — 156 steps\n // re-reading ~738k tokens each, ~60% of all re-read tokens.\n compactThresholdWindowFraction: 0.25,\n // Hysteresis: once kept tool output crosses the threshold, trim to half of\n // it in one step and wait for the next crossing, so steps are ~0.5 ×\n // threshold of new tool output apart instead of every few turns.\n compactLowWaterFraction: 0.5,\n // No client tail compaction on Anthropic ≥400k windows: each stub also\n // makes the API drop every later replayed thinking block (preserved-thinking\n // models), and autocompact stays the ceiling.\n compactSkip: { providers: ['anthropic'], minContextWindow: 400_000 },\n // Byte-budget eviction (see `applyTailCompaction`) keeps the largest recent\n // suffix of tool output that fits under `compactThreshold`, so for typical\n // small results the model retains far more than this many messages. This is\n // a FLOOR, not a cap: the trailing 8 messages (~4 tool round-trips) are\n // never elided, guaranteeing a freshly read file survives the\n // read → think → edit cadence even when individual results are large enough\n // that the byte budget alone would keep fewer. A floor of 4 was too tight:\n // a read got elided before the model could edit, forcing re-read loops.\n compactKeepTurns: 8,\n // Off. `'notice'` appended \"re-read before reasoning\" to nearly every\n // successful edit, almost always naming the file the model had just edited,\n // which made the edit tools noisier than an equivalent shell write. Edits\n // stay safe without it: `old_string` must resolve against current bytes, and\n // the mutating tools mark read-state `elided` so a re-read serves the full\n // body. `'stub'` would re-bill the prompt cache per newly-stale read (13\n // full-prefix invalidations on a 129-turn trace); see\n // `test-inference/cache-stability.inference.test.ts`.\n elideStaleReads: false,\n // Keep media inline; age it out only near the provider's hard request\n // limits. A fixed message window (`mediaKeepTurns`, 8 messages) rewrote an earlier\n // message every time a screenshot aged out, re-billing the whole\n // conversation: replayed on a 470k-token GUI session, it caused 158 full\n // prompt-cache resets, against ~1.6k tokens per screenshot to keep it as\n // a cache read. Aged media stays recoverable via\n // `read_file path=\"attachment://<hash>\"`.\n mediaBudget: DEFAULT_MEDIA_BUDGET,\n // Shrink Retina screenshots once, at entry (cache-stable), to 2000 px: a\n // request with more than 20 images is rejected if any one is larger, and\n // the inline budget routinely carries more than 20.\n maxImageEdge: DEFAULT_MAX_IMAGE_EDGE,\n // Loop-native auto-compaction: when a turn's effective input crosses 85% of\n // the model's context window, the loop summarizes the older turns mid-run\n // (real LLM summary + file/skill restoration) and continues. Compaction\n // happens inside the run (mid tool-loop AND end-of-run) with no double-fire.\n // The TUI/GUI inject the\n // picked model's `contextWindow` (and honor the user's enable/threshold\n // settings) per run; without a window source the trigger is inert.\n autoCompact: { threshold: 0.85 },\n toolDisclosure: 'lazy',\n toolOutputBudget: 64 * 1024,\n toolOutputBudgetExcludeTools: DEFAULT_BUDGET_EXCLUDE_TOOLS,\n persistThreshold: 8 * 1024,\n persistExcludeTools: DEFAULT_PERSIST_EXCLUDE_TOOLS,\n // `requireReadBeforeEdit` stays off (the library default). `edit`'s\n // `old_string` anchor already refuses edits against bytes that aren't on\n // disk, and the gate's \"shell reads don't register\" refusals taught models\n // to route edits through shell/python scripts to avoid it.\n requireReadBeforeEdit: false,\n // `shellToolNudges` — one factual line pointing at `read_file` / `grep` /\n // `edit` when a shell call does their job, at most twice per kind per\n // session. Rides the shell result itself (append-only), so it costs no\n // system-prompt bytes and appears only when the shell habit does.\n shellToolNudges: true,\n // `dedupTools` for the read-only discovery tools, in `replay` mode — an\n // identical re-issue replays the prior result from cache instead of\n // re-walking the filesystem. Free (no behavior change beyond cache replay)\n // and breaks the cheap-replay loop shape (model probing the same\n // `glob`/`grep`/`list_files` repeatedly). The hasher covers all input\n // fields so flag/limit changes bust the cache.\n dedupTools: {\n glob: discoveryDedupHash,\n grep: discoveryDedupHash,\n list_files: discoveryDedupHash,\n },\n // `repeatGuard` — identical-call detector. Defaults track the built-in\n // `shell` and `read_file` tools. Shell keeps the generic 4/8 block/abort\n // thresholds; `read_file` uses 6/12 so normal scaffolding re-visits don't\n // trip as quickly. The abort is the only layer that stops a model which\n // ignores the `Blocked:` reason and keeps retrying the same payload.\n //\n // `mode: 'both'` because strict chain counting is trivially defeated: ANY\n // intervening call resets it, and a spinning model always emits some. A real\n // run spent 123 turns and 10M input tokens re-issuing ONE byte-identical\n // `read_file` 59 times, alternating it with `grep`s on the same path; the\n // longest consecutive streak was 5 against a threshold of 6, so nothing ever\n // fired and the user had to cancel. The interleaved calls were `dedupTools`\n // cache replays, i.e. free for the model and (by design) chain boundaries\n // here — the guard was structurally unreachable. The sliding window counts\n // the same payload among that tool's own recent calls, so dilution slows the\n // trip instead of erasing it: the same run aborts at call 28 of 123.\n repeatGuard: { mode: 'both', windowSize: 40 },\n // `thinkingDecay` — after turn 10 the model has converged or is thrashing;\n // either way extended thinking earns less per token. STEP form (no\n // `factor`), not geometric: `thinking.budget_tokens` is part of Anthropic's\n // prompt-cache key, so every distinct budget a schedule emits is a FULL\n // cache re-bill — `cacheRead: 0`, scaffold included (measured; pinned in\n // `test-inference/cache-stability.inference.test.ts`). Geometric\n // halving (8192 → 4096 → 2048 → 1024) would cost three of those per run; one step\n // straight to the floor costs exactly one. No-op unless a thinking budget\n // is set.\n thinkingDecay: { afterTurn: 10, floor: 1024 },\n // One-hour prompt cache. These profiles only back interactive hosts, where\n // a human pause past five minutes would otherwise re-bill the whole\n // conversation at the cache-write rate (measured: ~80 such resets in one\n // long session). 1h writes cost 2x base input against 1.25x, which the\n // avoided resets repay. Verified live in\n // `test-inference/cache-stability.inference.test.ts` § extended TTL.\n // OAuth defaults to 1h; this extends it to API-key auth. Bedrock\n // degrades to the default TTL.\n cacheTtl: '1h',\n} as const\n\n/**\n * Build agent — the default profile. Full read/write/shell access plus\n * subagent spawning. Its tools are those of the `basic` preset plus `glob`,\n * `grep`, `web_search` and `fetch_url`.\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, web search, and spawn subagents',\n accent: 'accent',\n // Shares an agent with Plan: a switch between them keeps the prompt cache.\n family: 'build',\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 * Codemode — Build's tools, reached through scripts. `codemode` in\n * `'only'` mode: every native tool a script can call leaves the wire and is\n * declared inside the `codemode` description, so the model works in scripts\n * by default. `spawn` is callable from scripts, so it leaves the wire too.\n * Tools scripts cannot call stay declared. Shown only while the Codemode\n * setting is on.\n */\nexport const BUILD_CODEMODE_AGENT: AgentProfile = {\n id: 'build-codemode',\n label: 'Codemode',\n description: 'Build, but the model works through scripts that call the tools in a sandbox',\n accent: 'accent',\n requiresSetting: 'codemode',\n preset: composePresets(\n definePreset({\n name: 'build-codemode',\n system: buildBuildSystem(),\n behavior: { ...SHARED_BEHAVIOR, codemode: { mode: 'only' } },\n tools: { ...BUILD_TOOLS, spawn: createSpawnTool({ persist: true }) },\n }),\n createTodoTools(),\n ),\n}\n\n/**\n * Plan's rules as a switch note: read-only, investigate, then propose. A\n * condensed form of its prompt (`buildPlanSystem`), for a session whose\n * agent was built with another prompt (Build's, in their family).\n */\nfunction planSwitchNote({ tools, allowInteraction }: ProfileSwitchNoteContext): string {\n const everyProfile = allowInteraction ? '`present_plan`, `ask_user`, skills, MCP tools' : 'skills, MCP tools'\n const steps = allowInteraction\n ? `1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Ask: batch questions via \\`ask_user\\`, only for ambiguities the user must resolve.\n3. Propose via \\`present_plan\\`: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative.\n\n- Every plan is delivered via \\`present_plan\\`, whatever its size; prose is not a deliverable.\n- \\`ask_user\\` when the answer changes the plan; batch related questions into one call.\n- Don't ask what the code can tell you.`\n : `1. Explore: read the relevant code, find existing functions and patterns to reuse. Check current docs via web search when the plan depends on external libraries.\n2. Propose: write the plan in your final response: what to change, which files, what to reuse (real paths, symbols, line numbers), how to verify end-to-end. Recommend one approach, not every alternative. Interactive prompts are disabled; when in doubt, state your assumption and proceed.`\n return `# Plan mode (read-only)\nYou are now in PLAN mode: investigate and design a proposal. You CANNOT modify files, run commands, or spawn subagents until the user switches you out of Plan mode. Allowed tools: ${tools.map(name => `\\`${name}\\``).join(', ')}, plus the tools every mode has (${everyProfile}). Any other call is refused.\n\n${steps}\n- Search first (\\`grep\\` / \\`glob\\`), then \\`read_file\\` the specific file. Don't re-read files or re-run searches whose output is still visible above.\n\nThe user switches to Build mode to execute ${allowInteraction ? 'an approved' : 'the'} plan.`\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 *\n * In Build's family: a session switching between them keeps Build's agent,\n * a gate enforces Plan's tools and its rules arrive as a note.\n */\nexport const PLAN_AGENT: AgentProfile = {\n id: 'plan',\n label: 'Plan',\n description: 'read-only: explore, analyze, search the web, and propose without modifying anything',\n accent: 'model',\n family: 'build',\n switchNote: planSwitchNote,\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 of the built-in profiles. 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 'build-codemode': BUILD_CODEMODE_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 *\n * @param registry - The agent registry to search.\n * @param requestedId - Preferred profile id, such as the persisted last agent.\n * @param defaultId - Fallback profile id used when the requested one is missing.\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 single `Preset` into a single-profile registry. Used by\n * `resolveConfig` when the host passed `preset` without `agents`. The\n * registry has one entry, so the picker has only one option.\n *\n * @param preset - The preset to wrap. Its `name` becomes the profile label when set.\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 * Session lifecycle — the one way a session is created, forked, and deleted.\n *\n * Every surface (daemon, embedded GUI host, TUI local runtime, orchestration,\n * ACP) goes through these, so a field stamped at creation, carried by a fork,\n * or reclaimed on delete is decided HERE once instead of drifting per host.\n * Per-session state such as an environment lands in exactly these functions.\n *\n * Deliberately host-agnostic: nothing here knows about live agents. Stopping a\n * running agent before a delete is the host's job (`ChatHost.deleteSession`,\n * the TUI's teardown); this module owns the persisted data only.\n *\n * `createMainSession` layers the orchestration role on top; it is here rather\n * than in the host because the TUI's local runtime has no host and must still\n * be able to materialize the main (delivery of events does need the host; the\n * session itself does not).\n */\nimport type { Session, SessionData, SessionStore } from '../session/index'\nimport type { ProviderKey } from './auth'\nimport type { SessionEnvironment } from './environment'\nimport type { ModelFallbackTarget } from './model-chain-metadata'\nimport { randomUUID } from 'node:crypto'\nimport { basename } from 'node:path'\nimport { editSessionMetadata } from '../extension-authoring'\nimport { cleanupPersistedSession, rehomePersistedStubs, resolveLegacyPersistDir, resolveLegacySessionDir, resolvePersistDir, resolveSessionDir } from '../loop-persistence'\nimport { createSession } from '../session/index'\nimport { DEFAULT_AGENT_ID } from './agents'\nimport { ENVIRONMENT_METADATA_KEY, environmentMetadata, LOCAL_ENVIRONMENT, readSessionEnvironment } from './environment'\nimport { modelChainMetadata, SESSION_ACCOUNT_METADATA_KEY } from './model-chain-metadata'\nimport { MAIN_SESSION_TITLE, SESSION_ROLE_KEY, SIDE_OF_KEY } from './orchestration'\nimport { projectRootOf } from './project-root'\nimport { spaceMetadata } from './spaces'\nimport { truncateTurnsAt } from './turn-operations'\n\n// ---------------------------------------------------------------------------\n// Create\n// ---------------------------------------------------------------------------\n\n/** Fields stamped on a new session at creation. */\nexport interface CreateSessionInStoreParams {\n /**\n * Tool-execution directory; `projectRoot` is derived from it. `''` creates a\n * DIRECTORY-LESS session (the GUI's \"Chats\" bucket): no project, and\n * `resolveSessionCwd` anchors tool execution at the user's HOME at run time.\n */\n cwd: string\n /** Explicit id. Omitted → the store's generator, else a random UUID. */\n id?: string\n /** Initial session title. */\n title?: string\n /** Profile the session starts in. Defaults to `DEFAULT_AGENT_ID` (`build`). */\n agentId?: string\n /** Provider the session starts on. Applied together with `modelId`. */\n providerKey?: string\n /** Model the session starts on. Applied together with `providerKey`. */\n modelId?: string\n /** The stored account of `providerKey` to run on (credential id); omitted = its active account. */\n account?: string\n /** Fallback targets after the primary, stored as the session's model chain. */\n fallbacks?: readonly ModelFallbackTarget[]\n /** Mark as an extension-authoring session (auto-activates the docs skill). */\n extensionEdit?: boolean\n /** Mark as a skill-authoring session for the named skill (dir = `cwd`). */\n skillEdit?: { name: string }\n /**\n * Where the session's work happens (see `environment.ts`) — resolved by the\n * host (`initialEnvironment`), which also provisions it. Omitted or local +\n * ready → nothing is stamped: an absent environment IS the local one.\n */\n environment?: SessionEnvironment\n /**\n * The space the session belongs to (see `./spaces`). Omitted or Default →\n * nothing is stamped: an absent space id IS the Default space.\n */\n spaceId?: string\n /**\n * Make it a `/side` session of this parent (`metadata.sideOf`, role\n * `'side'`): hidden from lists, deleted with the parent.\n */\n sideOf?: string\n /**\n * Extra host-specific metadata (e.g. a pinned-skills seed). Applied FIRST,\n * so every canonical key stamped here wins on collision.\n */\n metadata?: Record<string, unknown>\n}\n\n/**\n * Build a new, UNSAVED session with every creation-time field stamped:\n * `projectRoot` (git root of `cwd`), `cwd`, `agentId`, `title`, the model and\n * its fallback chain, the authoring markers, and the environment.\n *\n * Unsaved on purpose: the TUI's local runtime creates the session when a chat\n * opens but only persists it on the first run, so opening and abandoning\n * `/new` never leaves an empty row behind. Everyone else wants\n * {@link createSessionInStore}.\n *\n * `store` may be `undefined` for an in-memory-only session (ACP without a\n * store): same stamping, no persistence.\n *\n * @param store - Store the session saves to later, or `undefined` for an in-memory session.\n * @param params - Creation fields for the session.\n */\nexport async function newSession(store: SessionStore | undefined, params: CreateSessionInStoreParams): Promise<Session> {\n const id = params.id ?? (await store?.generateSessionId?.()) ?? randomUUID()\n // The project the session BELONGS to (a linked worktree's main checkout),\n // not the checkout it runs in — that stays `metadata.cwd`. Directory-less\n // sessions carry NO project: `''` must not become a project root, or the\n // session lands in a bogus project group.\n const projectRoot = params.cwd ? projectRootOf(params.cwd) : undefined\n const session = await createSession({\n id,\n ...(store ? { store } : {}),\n ...(projectRoot ? { projectRoot } : {}),\n // Always stamped: the agent is a per-session mode, and a session without\n // one would otherwise inherit whatever profile the opening window shows.\n agentId: params.agentId ?? DEFAULT_AGENT_ID,\n ...(params.metadata ? { metadata: { ...params.metadata } } : {}),\n })\n session.setMeta('cwd', params.cwd)\n if (params.title)\n session.setMeta('title', params.title)\n if (params.providerKey && params.modelId) {\n session.setMeta('providerKey', params.providerKey)\n session.setMeta('modelId', params.modelId)\n if (params.account)\n session.setMeta(SESSION_ACCOUNT_METADATA_KEY, params.account)\n const chain = modelChainMetadata(\n { providerKey: params.providerKey as ProviderKey, modelId: params.modelId, ...(params.account ? { account: params.account } : {}) },\n params.fallbacks,\n )\n if (chain)\n session.setMeta('modelChain', chain)\n }\n // Authoring markers target the session's own dir; a directory-less\n // session has nothing to author.\n if (params.cwd && params.extensionEdit)\n setMetaEntries(session, editSessionMetadata({ kind: 'extension', name: basename(params.cwd), dir: params.cwd }))\n if (params.cwd && params.skillEdit)\n setMetaEntries(session, editSessionMetadata({ kind: 'skill', name: params.skillEdit.name, dir: params.cwd }))\n const environment = params.environment\n if (environment && !(environment.kind === LOCAL_ENVIRONMENT.kind && environment.state === 'ready'))\n setMetaEntries(session, environmentMetadata(environment))\n setMetaEntries(session, spaceMetadata(params.spaceId))\n if (params.sideOf) {\n session.setMeta(SIDE_OF_KEY, params.sideOf)\n session.setMeta(SESSION_ROLE_KEY, 'side')\n }\n return session\n}\n\n/**\n * Create and persist a session; returns its id.\n *\n * @param store - Store to persist the session in.\n * @param params - Creation fields for the session.\n */\nexport async function createSessionInStore(store: SessionStore, params: CreateSessionInStoreParams): Promise<string> {\n const session = await newSession(store, params)\n await session.save()\n return session.id\n}\n\n/**\n * Create the orchestration main session; returns its id.\n *\n * Directory-less on purpose. The main is GLOBAL — one per store, orchestrating\n * sessions in many directories — so binding it to whichever cwd happened to be\n * active when the toggle flipped put it in that project's group and hid it\n * from every other project's list. With no cwd it sits in the \"Chats\" bucket\n * and its own tools run from HOME, which is the only directory that is always\n * the user's and never a project's.\n *\n * @param store - Store to persist the main session in.\n */\nexport async function createMainSession(store: SessionStore): Promise<string> {\n const session = await newSession(store, { cwd: '', title: MAIN_SESSION_TITLE })\n session.setMeta(SESSION_ROLE_KEY, 'main')\n await session.save()\n return session.id\n}\n\n// ---------------------------------------------------------------------------\n// Fork\n// ---------------------------------------------------------------------------\n\n/**\n * Metadata a fork inherits from its parent: where it runs, the model\n * surface it runs on, and its space. Everything else (todos, archive flag, orchestration\n * role, artifacts…) belongs to the parent's history, not the new branch.\n * The environment is inherited too, but SHARED (see `forkSession`).\n */\nconst FORK_INHERITED_METADATA_KEYS = ['cwd', 'providerKey', 'modelId', SESSION_ACCOUNT_METADATA_KEY, 'modelChain', 'effort', 'modelOptions', 'spaceId'] as const\n\n/** What a fork reads from its parent — satisfied by `SessionData` and a live `Session`. */\nexport type ForkSource = Pick<SessionData, 'id' | 'turns' | 'runs' | 'metadata' | 'agentId' | 'projectRoot'>\n\n/** Inputs to {@link forkSession}. */\nexport interface ForkSessionParams {\n /**\n * Parent session — an id to load from `store`, or the parent itself (a\n * host holding the live `Session` passes it so unsaved turns fork too).\n */\n source: ForkSource | string\n /** Last turn the fork keeps (inclusive); the prefix is made protocol-clean. */\n turnId: string\n /**\n * Root of the per-session artifact tree (`<userDir>/sessions/<id>/`) that\n * the parent's persisted-output stubs point into. The fork gets its own\n * copies so deleting the parent can't strand the fork's content.\n */\n userDir: string\n /** Title override. Default: the parent's explicit title, if any. */\n title?: string\n}\n\n/** Outcome of {@link forkSession}: the new session id, or the reason the fork was refused. */\nexport type ForkSessionResult\n = | { ok: true, id: string }\n | { ok: false, reason: 'source-not-found' | 'turn-not-found' | 'environment-not-ready' }\n\n/**\n * The error a user sees when a fork is refused, the same on every surface.\n *\n * @param reason - The refusal reason from {@link forkSession}.\n * @param sessionId - Id of the session that was to be forked.\n * @param turnId - Id of the requested last turn.\n */\nexport function describeForkFailure(reason: Extract<ForkSessionResult, { ok: false }>['reason'], sessionId: string, turnId: string): string {\n switch (reason) {\n case 'source-not-found':\n return `Session \"${sessionId}\" not found.`\n case 'environment-not-ready':\n return `Session \"${sessionId}\" can't be forked until its environment is ready.`\n case 'turn-not-found':\n return `Turn \"${turnId}\" not found in session \"${sessionId}\".`\n }\n}\n\n/**\n * Create and persist a new session whose history is the parent's turns up to\n * and including `turnId`. The parent is untouched.\n *\n * Carries: the protocol-clean turn prefix, the run records those turns\n * reference (so run stats survive and the next run can't re-mint `run_1`\n * over an existing reference), persisted-output blobs (copied and re-pointed),\n * `projectRoot` (a fork belongs to its parent's project, wherever it was\n * forked from), `agentId`, {@link FORK_INHERITED_METADATA_KEYS}, and the\n * parent's environment — marked not-owned: the fork runs in the same\n * worktree / sandbox but deleting it never tears that down. Refused while\n * the parent's environment isn't `ready`: nothing would provision the fork's\n * copy, and provisioning it would mint an environment nobody owns.\n *\n * @param store - Store to load the parent from and persist the fork in.\n * @param params - Parent, last turn to keep, artifact root and optional title.\n */\nexport async function forkSession(store: SessionStore, params: ForkSessionParams): Promise<ForkSessionResult> {\n const source = typeof params.source === 'string' ? await store.load(params.source) : params.source\n if (!source)\n return { ok: false, reason: 'source-not-found' }\n const turns = truncateTurnsAt(source.turns, params.turnId, { inclusive: true })\n if (!turns || turns.length === 0)\n return { ok: false, reason: 'turn-not-found' }\n\n if (source.metadata[ENVIRONMENT_METADATA_KEY] !== undefined && readSessionEnvironment(source.metadata).state !== 'ready')\n return { ok: false, reason: 'environment-not-ready' }\n\n const referencedRunIds = new Set(turns.flatMap(turn => turn.runId ? [turn.runId] : []))\n // Shallow-clone so the fork's later mutations never bleed into the parent.\n const runs = source.runs.filter(run => referencedRunIds.has(run.id)).map(run => ({ ...run }))\n\n const inherited: Record<string, unknown> = {}\n for (const key of FORK_INHERITED_METADATA_KEYS) {\n if (source.metadata[key] !== undefined)\n inherited[key] = source.metadata[key]\n }\n if (source.metadata[ENVIRONMENT_METADATA_KEY] !== undefined)\n Object.assign(inherited, environmentMetadata({ ...readSessionEnvironment(source.metadata), owned: false }))\n const title = params.title ?? (typeof source.metadata.title === 'string' ? source.metadata.title : undefined)\n\n const fork = await createSession({\n id: (await store.generateSessionId?.()) ?? randomUUID(),\n store,\n agentId: source.agentId ?? DEFAULT_AGENT_ID,\n ...(source.projectRoot ? { projectRoot: source.projectRoot } : {}),\n metadata: { ...inherited, ...(title ? { title } : {}) },\n })\n\n /*\n * Persisted-output stubs embed the blob's absolute per-session path; left\n * as-is, the fork would lose that content when the parent is deleted (its\n * blob dir goes with it). Rehome both layouts: sessions from before the\n * session-first layout embed `<userDir>/tool-results/<id>/`, newer ones\n * `<userDir>/sessions/<id>/tool-results/`. The legacy pass matters most —\n * the parent's delete sweeps exactly that dir.\n */\n const forkPersistDir = resolvePersistDir({ userDir: params.userDir, sessionId: fork.id })\n let rehomed = await rehomePersistedStubs(turns, resolvePersistDir({ userDir: params.userDir, sessionId: source.id }), forkPersistDir)\n const legacyPersistDir = resolveLegacyPersistDir({ userDir: params.userDir, sessionId: source.id })\n if (legacyPersistDir !== undefined)\n rehomed = await rehomePersistedStubs(rehomed, legacyPersistDir, forkPersistDir)\n\n fork.setTurns(rehomed)\n fork.setRuns(runs)\n await fork.save()\n return { ok: true, id: fork.id }\n}\n\n// ---------------------------------------------------------------------------\n// Delete\n// ---------------------------------------------------------------------------\n\n/**\n * Delete a session's persisted data: the store row, then every on-disk\n * artifact it owns — the session-first tree (`<userDir>/sessions/<id>/`:\n * blobs, task logs, MCP warnings) plus the two pre-session-first locations\n * migration leaves behind. Blob cleanup is best-effort and never throws; it\n * runs AFTER the row delete so a cleanup failure can't leave a live row\n * pointing at removed blobs.\n *\n * Does NOT stop a live agent — hosts must retire it first (see\n * `ChatHost.deleteSession`), or its next save resurrects the row.\n *\n * @param store - Store holding the session row.\n * @param sessionId - Id of the session to delete.\n * @param opts - `userDir` is the root of the per-session artifact tree.\n */\nexport async function deleteSessionData(store: SessionStore, sessionId: string, opts: { userDir: string }): Promise<void> {\n await store.delete(sessionId)\n const dirs = [\n resolveSessionDir({ userDir: opts.userDir, sessionId }),\n resolveLegacyPersistDir({ userDir: opts.userDir, sessionId }),\n resolveLegacySessionDir({ userDir: opts.userDir, sessionId }),\n ]\n await Promise.all(dirs.flatMap(dir => dir === undefined ? [] : [cleanupPersistedSession(dir)]))\n}\n\nfunction setMetaEntries(session: Session, entries: Record<string, unknown>): void {\n for (const [key, value] of Object.entries(entries))\n session.setMeta(key, value)\n}\n"],"mappings":"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"}