@letrquan/book 0.2.0 → 0.4.0-beta.0

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 (177) hide show
  1. package/CHANGELOG.md +3771 -40
  2. package/README.md +135 -1311
  3. package/dist/additional-roots-VAWRQ625.js +32 -0
  4. package/dist/{app-MOXXWEOY.js → app-J7BVPSUH.js} +9648 -5509
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-2AAOF5B5.js +28 -0
  7. package/dist/chunk-2AAOF5B5.js.map +1 -0
  8. package/dist/chunk-32WBCUAN.js +660 -0
  9. package/dist/chunk-32WBCUAN.js.map +1 -0
  10. package/dist/chunk-4DKHLZRI.js +120 -0
  11. package/dist/chunk-4DKHLZRI.js.map +1 -0
  12. package/dist/{chunk-CAGOB2N4.js → chunk-62EEAUID.js} +6 -4
  13. package/dist/chunk-62EEAUID.js.map +1 -0
  14. package/dist/chunk-AHC7OU75.js +1124 -0
  15. package/dist/chunk-AHC7OU75.js.map +1 -0
  16. package/dist/{chunk-E75IPST5.js → chunk-AJZWLKLQ.js} +73 -70
  17. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  18. package/dist/chunk-B5FC3MYZ.js +395 -0
  19. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  20. package/dist/{chunk-KWHQ2XXL.js → chunk-DNFQTSMJ.js} +55 -13
  21. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  22. package/dist/{chunk-J7MIZ5CA.js → chunk-E5WLQSKA.js} +719 -89
  23. package/dist/chunk-E5WLQSKA.js.map +1 -0
  24. package/dist/chunk-ECLC4CIJ.js +1181 -0
  25. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  26. package/dist/chunk-EEX6QNXV.js +127 -0
  27. package/dist/chunk-EEX6QNXV.js.map +1 -0
  28. package/dist/chunk-EFXVSQ5J.js +931 -0
  29. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  30. package/dist/{chunk-V4BOYR52.js → chunk-ENLB7NYV.js} +1304 -326
  31. package/dist/chunk-ENLB7NYV.js.map +1 -0
  32. package/dist/chunk-FPR7SKIE.js +6926 -0
  33. package/dist/chunk-FPR7SKIE.js.map +1 -0
  34. package/dist/{chunk-DGMQEI6Y.js → chunk-HFB74CLM.js} +30 -9
  35. package/dist/chunk-HFB74CLM.js.map +1 -0
  36. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  37. package/dist/chunk-HOZXKOE3.js.map +1 -0
  38. package/dist/chunk-IDYWN37T.js +36 -0
  39. package/dist/chunk-IDYWN37T.js.map +1 -0
  40. package/dist/chunk-J4F2QLM2.js +273 -0
  41. package/dist/chunk-J4F2QLM2.js.map +1 -0
  42. package/dist/chunk-JGF5HLSI.js +1065 -0
  43. package/dist/chunk-JGF5HLSI.js.map +1 -0
  44. package/dist/{chunk-XIFFTATU.js → chunk-KDNWMKXV.js} +7166 -4237
  45. package/dist/chunk-KDNWMKXV.js.map +1 -0
  46. package/dist/chunk-KXAYUIKP.js +90 -0
  47. package/dist/chunk-KXAYUIKP.js.map +1 -0
  48. package/dist/{chunk-3U5IBM24.js → chunk-MEOB2DZA.js} +12 -2
  49. package/dist/chunk-MEOB2DZA.js.map +1 -0
  50. package/dist/{chunk-WKBO5R4O.js → chunk-MV5PQNIA.js} +2 -2
  51. package/dist/chunk-MV5PQNIA.js.map +1 -0
  52. package/dist/chunk-N2G4Q7ME.js +2672 -0
  53. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  54. package/dist/chunk-OJBMDM5H.js +299 -0
  55. package/dist/chunk-OJBMDM5H.js.map +1 -0
  56. package/dist/{chunk-URLQAXTR.js → chunk-PTDC7ZUP.js} +2 -2
  57. package/dist/chunk-PTDC7ZUP.js.map +1 -0
  58. package/dist/{chunk-574C6MQR.js → chunk-REASC2QG.js} +173 -64
  59. package/dist/chunk-REASC2QG.js.map +1 -0
  60. package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
  61. package/dist/chunk-RT5SATF7.js.map +1 -0
  62. package/dist/chunk-SBTU5BEC.js +114 -0
  63. package/dist/chunk-SBTU5BEC.js.map +1 -0
  64. package/dist/{chunk-5KLVH3PY.js → chunk-SWPDKLNM.js} +5 -3
  65. package/dist/chunk-SWPDKLNM.js.map +1 -0
  66. package/dist/chunk-UCFJ43OS.js +520 -0
  67. package/dist/chunk-UCFJ43OS.js.map +1 -0
  68. package/dist/{chunk-VZQXX3WW.js → chunk-W7SLYS3M.js} +80 -16
  69. package/dist/chunk-W7SLYS3M.js.map +1 -0
  70. package/dist/{chunk-ANSJPYGI.js → chunk-WV3O5PGX.js} +3 -3
  71. package/dist/chunk-WV3O5PGX.js.map +1 -0
  72. package/dist/chunk-XHAO3K7I.js +18 -0
  73. package/dist/chunk-XHAO3K7I.js.map +1 -0
  74. package/dist/chunk-XJRJVXJR.js +1050 -0
  75. package/dist/chunk-XJRJVXJR.js.map +1 -0
  76. package/dist/chunk-YIL6HTJX.js +663 -0
  77. package/dist/chunk-YIL6HTJX.js.map +1 -0
  78. package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
  79. package/dist/chunk-ZPSZZUWF.js.map +1 -0
  80. package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-ZRY66LCW.js} +7 -5
  81. package/dist/context-C3RAP6K5.js +41 -0
  82. package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-DPQD5H65.js} +6 -5
  83. package/dist/index.js +947 -203
  84. package/dist/index.js.map +1 -1
  85. package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-73AQO5WP.js} +18 -9
  86. package/dist/interactive-assets-73AQO5WP.js.map +1 -0
  87. package/dist/job-runner.js +166 -111
  88. package/dist/job-runner.js.map +1 -1
  89. package/dist/job-supervisor.d.ts +2 -0
  90. package/dist/job-supervisor.js +121 -0
  91. package/dist/job-supervisor.js.map +1 -0
  92. package/dist/loader-DQNPDKOX.js +27 -0
  93. package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-27LJYADQ.js} +6 -5
  94. package/dist/{mcp-config-YIJIES2C.js → mcp-config-GXSODNCM.js} +4 -3
  95. package/dist/memory-store-I6Z54VUN.js +51 -0
  96. package/dist/{model-window-store-YFVNXCIP.js → model-window-store-6VKXXTPJ.js} +9 -4
  97. package/dist/openai-compatible-IPUNBJ53.js +16 -0
  98. package/dist/permission-approvals-Q5IMFEZI.js +15 -0
  99. package/dist/permissions-7Y35X6HZ.js +62 -0
  100. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  101. package/dist/sandbox-N2RY72GN.js +42 -0
  102. package/dist/{scrollback-LUKLIBVI.js → scrollback-GD76GNM4.js} +44 -26
  103. package/dist/scrollback-GD76GNM4.js.map +1 -0
  104. package/dist/sdk.d.ts +2739 -2069
  105. package/dist/sdk.js +77 -42
  106. package/dist/sdk.js.map +1 -1
  107. package/dist/settings-loader-KDBH45AQ.js +43 -0
  108. package/dist/settings-loader-KDBH45AQ.js.map +1 -0
  109. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  110. package/dist/settings-removed-3ATWJEF3.js.map +1 -0
  111. package/dist/shell-selection-Z5WORZU4.js +31 -0
  112. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  113. package/package.json +25 -24
  114. package/dist/app-MOXXWEOY.js.map +0 -1
  115. package/dist/chunk-24AXE6SP.js.map +0 -1
  116. package/dist/chunk-3U5IBM24.js.map +0 -1
  117. package/dist/chunk-574C6MQR.js.map +0 -1
  118. package/dist/chunk-5GDZ22YP.js +0 -449
  119. package/dist/chunk-5GDZ22YP.js.map +0 -1
  120. package/dist/chunk-5KLVH3PY.js.map +0 -1
  121. package/dist/chunk-5RDRAO4B.js.map +0 -1
  122. package/dist/chunk-7N4J557C.js.map +0 -1
  123. package/dist/chunk-A7JLBK2Y.js +0 -3506
  124. package/dist/chunk-A7JLBK2Y.js.map +0 -1
  125. package/dist/chunk-ANSJPYGI.js.map +0 -1
  126. package/dist/chunk-BTZZUCLI.js +0 -264
  127. package/dist/chunk-BTZZUCLI.js.map +0 -1
  128. package/dist/chunk-C7D4NJ2D.js +0 -217
  129. package/dist/chunk-C7D4NJ2D.js.map +0 -1
  130. package/dist/chunk-CAGOB2N4.js.map +0 -1
  131. package/dist/chunk-DGMQEI6Y.js.map +0 -1
  132. package/dist/chunk-E75IPST5.js.map +0 -1
  133. package/dist/chunk-EYGD6X2T.js +0 -180
  134. package/dist/chunk-EYGD6X2T.js.map +0 -1
  135. package/dist/chunk-GTVB4HEM.js +0 -765
  136. package/dist/chunk-GTVB4HEM.js.map +0 -1
  137. package/dist/chunk-J7MIZ5CA.js.map +0 -1
  138. package/dist/chunk-KWHQ2XXL.js.map +0 -1
  139. package/dist/chunk-L6LK2QIQ.js +0 -657
  140. package/dist/chunk-L6LK2QIQ.js.map +0 -1
  141. package/dist/chunk-MAQXCUR4.js +0 -1007
  142. package/dist/chunk-MAQXCUR4.js.map +0 -1
  143. package/dist/chunk-S4XL7HOM.js +0 -402
  144. package/dist/chunk-S4XL7HOM.js.map +0 -1
  145. package/dist/chunk-URLQAXTR.js.map +0 -1
  146. package/dist/chunk-V4BOYR52.js.map +0 -1
  147. package/dist/chunk-VZQXX3WW.js.map +0 -1
  148. package/dist/chunk-WKBO5R4O.js.map +0 -1
  149. package/dist/chunk-XIFFTATU.js.map +0 -1
  150. package/dist/context-ZC5IFEAX.js +0 -25
  151. package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
  152. package/dist/loader-4IDS2RIE.js +0 -15
  153. package/dist/openai-compatible-RHZUBHV6.js +0 -10
  154. package/dist/permission-approvals-76PVREFD.js +0 -14
  155. package/dist/permissions-CHCKTFQQ.js +0 -28
  156. package/dist/sandbox-CNVAQDA7.js +0 -23
  157. package/dist/scrollback-LUKLIBVI.js.map +0 -1
  158. package/dist/settings-loader-M2QP5TX2.js +0 -21
  159. package/dist/shell-selection-V6QAYXPX.js +0 -17
  160. package/patches/ink+6.8.0.patch +0 -13
  161. package/scripts/apply-ink-patch.mjs +0 -29
  162. package/scripts/ink-patch.mjs +0 -59
  163. package/scripts/verify-ink-patch.mjs +0 -11
  164. /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  165. /package/dist/{context-ZC5IFEAX.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  166. /package/dist/{hook-approvals-CTUFKEJT.js.map → context-C3RAP6K5.js.map} +0 -0
  167. /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  168. /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-DQNPDKOX.js.map} +0 -0
  169. /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  170. /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  171. /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  172. /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  173. /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  174. /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  175. /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  176. /package/dist/{settings-removed-JWWPT4WA.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  177. /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-N2RY72GN.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jobs/process-tree.ts","../src/system32.ts","../src/tools/git.ts","../src/tools/git-repository-programs.ts"],"sourcesContent":["/**\n * Process-liveness helpers shared by the shell manager and the detached job runner.\n *\n * A POSIX process group outlives its leader. Background commands run through `sh -c`, and the\n * shell forks the real worker instead of exec'ing it whenever it cannot hand its own process\n * over, so the direct child can exit — for instance by taking the SIGTERM a worker ignores —\n * while that worker keeps running in the same group. Termination therefore has to be judged on\n * group membership; the direct child's own exit proves nothing about the tree behind it.\n */\n\nimport { execFile, type ChildProcess } from 'node:child_process';\nimport { systemClock, type Clock } from '../clock.js';\nimport { buildChildEnv } from '../child-env.js';\nimport { system32Executable } from '../system32.js';\n\nconst GROUP_POLL_INTERVAL_MS = 25;\n/** How long a process group is given between SIGTERM and SIGKILL, and how long taskkill may take. */\nexport const TERMINATE_GRACE_MS = 1_500;\n\n/** A signal target exists when it accepts signal 0, or rejects it as another user's. */\nfunction signalTargetExists(target: number): boolean {\n try {\n process.kill(target, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n}\n\nexport function isProcessAlive(pid: number | undefined): boolean {\n return pid === undefined ? false : signalTargetExists(pid);\n}\n\n/** True while any process remains in the group, whether or not the leader is one of them. */\nexport function isProcessGroupAlive(pgid: number | undefined): boolean {\n if (pgid === undefined || process.platform === 'win32') return false;\n return signalTargetExists(-pgid);\n}\n\n/** Signal every process in the group, falling back to the direct child when it has no group. */\nexport function signalProcessGroup(\n proc: { kill(signal: NodeJS.Signals): boolean } | undefined,\n pgid: number,\n signal: NodeJS.Signals,\n): void {\n try {\n process.kill(-pgid, signal);\n } catch {\n try {\n proc?.kill(signal);\n } catch {\n // The tree may have exited between the group signal and the direct-child fallback.\n }\n }\n}\n\n/**\n * Resolve `true` once the group holds no processes, or `false` when the bound elapses first.\n *\n * `kill(-pgid, 0)` also succeeds for a member that has exited but has not been reaped yet, so a\n * group reads as alive until its reaper runs. That bound is deliberate: a zombie holds no ports,\n * file handles, or CPU, and reporting \"not stopped\" for the moments before it is reaped is\n * honest, where trusting the direct child's exit is not.\n */\nexport async function waitForProcessGroupExit(\n pgid: number,\n timeoutMs: number,\n // A kill bound is a duration: on the wall clock, a backwards step here waits\n // past the bound and a forwards one abandons a group that was about to exit.\n clock: Clock = systemClock,\n): Promise<boolean> {\n const deadline = clock.monotonicNowMs() + timeoutMs;\n while (isProcessGroupAlive(pgid)) {\n if (clock.monotonicNowMs() >= deadline) return false;\n await new Promise((resolve) => setTimeout(resolve, GROUP_POLL_INTERVAL_MS));\n }\n return true;\n}\n\nexport function waitForProcessClose(proc: ChildProcess, timeoutMs: number): Promise<boolean> {\n if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve(true);\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n cleanup();\n resolve(proc.exitCode !== null || proc.signalCode !== null);\n }, timeoutMs);\n const onClose = () => {\n cleanup();\n resolve(true);\n };\n const cleanup = () => {\n clearTimeout(timer);\n proc.off('close', onClose);\n };\n proc.once('close', onClose);\n });\n}\n\ntype WindowsTreeKill = (pid: number) => Promise<boolean>;\n\n/**\n * `taskkill /T /F` on the tree rooted at `pid`, resolved through `system32Executable` so a\n * workspace on the PATH cannot stand in for it. Reports whether the kill actually ran. The bound is\n * a parameter because a caller that exits straight afterwards has nobody to escalate for it and can\n * afford to wait far longer than one that does.\n */\nexport async function runTaskkill(\n pid: number,\n timeoutMs: number = TERMINATE_GRACE_MS,\n): Promise<boolean> {\n return new Promise((resolve) => {\n execFile(\n system32Executable('taskkill'),\n ['/PID', String(pid), '/T', '/F'],\n // Book's own helper, but it is a command like any other and is handed no more of Book's\n // environment than the next one would be.\n { windowsHide: true, timeout: timeoutMs, env: buildChildEnv() },\n (error) => resolve(!error),\n );\n });\n}\n\nexport async function terminateWindowsProcessTree(\n proc: ChildProcess,\n pid: number,\n signal: NodeJS.Signals,\n treeKill: WindowsTreeKill = runTaskkill,\n): Promise<boolean> {\n const alreadyExited = proc.exitCode !== null || proc.signalCode !== null;\n if (alreadyExited) return true;\n if (await treeKill(pid)) return true;\n try {\n proc.kill(signal);\n } catch {\n // The process may have exited between taskkill and the direct-child fallback.\n }\n return false;\n}\n\n/**\n * Escalate SIGTERM → SIGKILL across the whole tree and report whether it is really gone.\n *\n * On POSIX the direct child is the `sh -c` wrapper, which dies from SIGTERM even when the worker\n * it forked ignores it, so its exit says nothing about the tree — success is judged on whether\n * the process group still holds anything. On Windows `taskkill /T /F` covers the tree, so the\n * direct child's close speaks for the tree only when taskkill actually ran.\n */\nexport async function terminateProcessTree(\n proc: ChildProcess | undefined,\n pid: number | undefined,\n hasClosed: (timeoutMs: number) => Promise<boolean>,\n): Promise<boolean> {\n if (!proc || pid === undefined) return true;\n if (process.platform !== 'win32') {\n signalProcessGroup(proc, pid, 'SIGTERM');\n if (await waitForProcessGroupExit(pid, TERMINATE_GRACE_MS)) return true;\n signalProcessGroup(proc, pid, 'SIGKILL');\n return waitForProcessGroupExit(pid, TERMINATE_GRACE_MS);\n }\n const confirmed = await terminateWindowsProcessTree(proc, pid, 'SIGTERM');\n if (await hasClosed(TERMINATE_GRACE_MS)) return confirmed;\n try {\n proc.kill('SIGKILL');\n } catch {\n return false;\n }\n return (await hasClosed(TERMINATE_GRACE_MS)) && confirmed;\n}\n\nexport async function terminateForegroundProcess(proc: ChildProcess): Promise<void> {\n await terminateProcessTree(proc, proc.pid, (timeoutMs) => waitForProcessClose(proc, timeoutMs));\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Resolve an executable from %SystemRoot%\\System32 on Windows.\n *\n * When System32 is missing from PATH (e.g. Git Bash, MSYS, or sanitized subprocess environments),\n * bare executable names fail with ENOENT under execFile / spawn with shell: false.\n * Off Windows, or if the executable is not found in System32, falls back to the bare name.\n */\nexport function system32Executable(\n name: string,\n platform: NodeJS.Platform = process.platform,\n env: NodeJS.ProcessEnv = process.env,\n): string {\n if (platform !== 'win32') return name;\n const fileName = name.toLowerCase().endsWith('.exe') ? name : `${name}.exe`;\n const systemRoot = env.SystemRoot || env.windir || 'C:\\\\Windows';\n const targetPath = join(systemRoot, 'System32', fileName);\n return existsSync(targetPath) ? targetPath : name;\n}\n","import { execFile } from 'child_process';\nimport type { ToolDefinition, ToolContext, ToolResult } from '../types/tools.js';\nimport { buildChildEnv } from '../child-env.js';\nimport { toolFailure, toolSuccess } from './result.js';\nimport {\n GIT_HARDENING_ARGS,\n hardenedGitArgs,\n hardenedGitEnv,\n homeGitCeilingEnv,\n readRepositoryGitConfig,\n} from './git-repository-programs.js';\n\ntype ExecFile = typeof execFile;\n\n/** Base -c overrides; see {@link GIT_HARDENING_ARGS} in `git-repository-programs.ts`. */\nexport { GIT_HARDENING_ARGS };\n\n/**\n * The per-command flags that close the two remaining routes a checkout owns for producing a diff,\n * for every hardened caller that runs `git diff`: `diff.external` names a program to produce it,\n * and a `.gitattributes` `textconv` line names one per file. They are command flags rather than\n * configuration, which is why they are not in {@link GIT_HARDENING_ARGS}, and they are on the\n * command as well as in the config so the guarantee does not rest on the `-c` overrides being\n * consulted for the subcommand.\n *\n * `--ignore-submodules=dirty` keeps the diff to this repository: a submodule whose recorded\n * commit moved still shows, what changed inside one does not. It is a flag rather than\n * `diff.ignoreSubmodules`, because a repository's own `.gitmodules` outranks that setting.\n * `--submodule=short` shows that moved submodule as its two commit ids, whatever `diff.submodule`\n * says: `log` and `diff` would have git run a second diff inside the submodule, which these flags\n * do not reach.\n */\nexport const HARDENED_DIFF_ARGS = [\n '--no-ext-diff',\n '--no-textconv',\n '--ignore-submodules=dirty',\n '--submodule=short',\n] as const;\n\n/** Environment floor for hardened calls; see {@link hardenedGitEnv} in `git-repository-programs.ts`. */\nexport { hardenedGitEnv };\n\n/** Hardening flags in front of arguments; see {@link hardenedGitArgs} in `git-repository-programs.ts`. */\nexport { hardenedGitArgs };\n\n/**\n * The environment for a git read that stays local. In a partial clone a missing object is\n * reported as missing rather than fetched (`GIT_NO_LAZY_FETCH`, which git 2.44 or later reads, and\n * older releases patched in 2024; an older git ignores it), and git's transport allowlist is empty\n * (`GIT_ALLOW_PROTOCOL`).\n *\n * Every hardened read carries it: the read-only Git tools, `/diff`, `/review`'s target, the\n * prompt's per-turn git line, the TUI status poll and git isolation. A `git` the user runs through\n * `Bash` is theirs and does not. The prompt line and the poll go further and skip the status read\n * in a partial clone altogether (see `ReadOnlyGitReader.partialClone`), so on a git that ignores\n * `GIT_NO_LAZY_FETCH` they still read nothing a remote would have to supply.\n */\nexport function noFetchGitEnv(): Record<string, string> {\n return { GIT_NO_LAZY_FETCH: '1', GIT_ALLOW_PROTOCOL: '' };\n}\n\n/**\n * The note put in front of a failed read whose output shows git needed an object a partial clone\n * does not have, so the reader learns why and what to do rather than only git's own line.\n */\nexport const MISSING_OBJECTS_NOTE =\n \"This repository is a partial clone, and git needs an object that is not here. Book's git reads do not fetch missing objects: run the same git command yourself, which fetches what it needs, or `git fetch --refetch <remote>` after unsetting `remote.<remote>.partialclonefilter`, then retry.\";\n\n/**\n * `message`, with {@link MISSING_OBJECTS_NOTE} in front when it shows a missing object. Git says\n * so in its own words only sometimes (a newer git prints just `unable to read tree`), so in a\n * repository known to be a partial clone a failure to read an object counts too.\n */\nexport function describeMissingObjects(message: string, partialClone = false): string {\n const missing =\n /lazy fetching disabled|promisor remote|transport '[^']*' not allowed/i.test(message) ||\n (partialClone &&\n /unable to read|could not read|missing (blob|tree|commit)|bad object|could not fetch/i.test(\n message,\n ));\n return missing ? `${MISSING_OBJECTS_NOTE}\\n${message}` : message;\n}\n\n/**\n * `runGit` runs a caller's arguments as given, and the caller chooses.\n *\n * The hardening is a property of the calls that are *not* the operator's own work, not of every\n * git call this module makes. `GitCommit` is the operator's own work, and it is the only\n * mutating tool here: it comes through this function with its own argv and the environment the\n * user configured, exactly as it did before the hardening existed, so a `pre-commit` or\n * `commit-msg` hook still runs. The one addition is the dotfiles home ceiling\n * (`homeGitCeilingEnv`), so it does not commit into a repository the read-only tools report as no\n * repository (#579). Silently not running the user's hook would be a worse failure\n * than the one the hardening exists to prevent. The read-only tools and the prompt's per-turn git\n * line go through {@link readOnlyGit}, and a caller whose work is Book's own builds its argv with\n * {@link hardenedGitArgs} directly.\n */\nexport async function runGit(\n args: string[],\n ctx: ToolContext,\n execute: ExecFile = execFile,\n): Promise<{ success: boolean; output: string; error?: string }> {\n return new Promise((resolve) => {\n const options = {\n cwd: ctx.workspaceRoot,\n encoding: 'utf-8' as const,\n timeout: 30_000,\n signal: ctx.signal,\n // A hook git runs is a project command like any other, so it must not inherit a NODE_ENV\n // Book defaulted for its own renderer.\n env: buildChildEnv(process.env, ctx.env),\n };\n\n execute('git', args, options, (error, stdout, stderr) => {\n if (!error) {\n resolve({ success: true, output: stdout || '(no output)' });\n return;\n }\n\n if (ctx.signal?.aborted) {\n resolve({ success: false, output: '', error: 'CANCELLED: Git command was cancelled' });\n return;\n }\n\n resolve({\n success: false,\n output: '',\n error: stderr || error.message || 'Git command failed',\n });\n });\n });\n}\n\n/** One repository's read-only git, with its configuration read once for every call. */\nexport interface ReadOnlyGitReader {\n /** Whether the repository is a partial clone (`extensions.partialClone`, a `remote.*.promisor`). */\n partialClone: boolean;\n /** One hardened read, as {@link readOnlyGit} runs it. */\n run(args: readonly string[]): Promise<Awaited<ReturnType<typeof runGit>>>;\n}\n\n/**\n * Read the workspace's git configuration once and return a reader for several read-only calls in\n * it, each hardened as {@link readOnlyGit} is. Rejects when the configuration cannot be read or\n * names a program the pins cannot neutralize.\n */\nexport async function openReadOnlyGit(ctx: ToolContext): Promise<ReadOnlyGitReader> {\n const env = { ...ctx.env, ...hardenedGitEnv(), ...noFetchGitEnv() };\n const config = await readRepositoryGitConfig(ctx.workspaceRoot, undefined, {\n signal: ctx.signal,\n env,\n });\n return {\n partialClone: config.partialClone,\n run: async (args) => {\n const result = await runGit(hardenedGitArgs([...config.pins, ...args]), { ...ctx, env });\n return result.success || !result.error\n ? result\n : { ...result, error: describeMissingObjects(result.error, config.partialClone) };\n },\n };\n}\n\n/**\n * A read-only report: hardened, with the pager and credential prompt it cannot wait on, and no\n * fetch (see {@link noFetchGitEnv}).\n *\n * Exported for the prompt's `<session-state>` git line (`gitContext` in `agent/context.ts`), which\n * runs on every turn before any tool call or approval, and so must not start a program the\n * checkout's `.git/config` names any more than `GitStatus` may (#574).\n *\n * The TUI's `/diff` uses it too, to show what `GitDiff` would.\n */\nexport async function readOnlyGit(args: readonly string[], ctx: ToolContext) {\n try {\n return await (await openReadOnlyGit(ctx)).run(args);\n } catch (error) {\n return {\n success: false,\n output: '',\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\nfunction gitResult(result: Awaited<ReturnType<typeof runGit>>): ToolResult {\n return result.success\n ? toolSuccess(result.output)\n : toolFailure(result.error ?? 'Git command failed', { content: result.output });\n}\n\nasync function gitStatus(_args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {\n const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitStatus, ctx);\n return gitResult(result);\n}\n\nasync function gitDiff(_args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {\n const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitDiff, ctx);\n return gitResult(result);\n}\n\nasync function gitLog(_args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {\n const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitLog, ctx);\n return gitResult(result);\n}\n\nasync function gitCommit(args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {\n const message = args.message as string;\n const result = await runGit(['commit', '-m', message], {\n ...ctx,\n env: { ...ctx.env, ...homeGitCeilingEnv() },\n });\n return gitResult(result);\n}\n\nasync function gitBranch(_args: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {\n const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitBranch, ctx);\n return gitResult(result);\n}\n\n/**\n * The arguments the four read-only tools run.\n *\n * These are the tools `permissions.ts` lets through without a prompt, so their whole security\n * argument rests on two things: they read the workspace and change nothing, and the repository's\n * own configuration cannot make them execute a program (see {@link GIT_HARDENING_ARGS}). Exported\n * so that argument can be asserted per tool rather than taken on trust.\n *\n * `GitDiff` carries {@link HARDENED_DIFF_ARGS}, which close the two remaining routes a checkout\n * owns: `diff.external` names a program to produce the diff, and a `.gitattributes` `textconv`\n * line names one per file. `GitStatus` carries `--ignore-submodules=dirty`: a submodule whose\n * recorded commit moved still shows, but what changed inside one does not count, so the status\n * stays a report on this repository alone. `GitLog` carries `--no-show-signature` for the same\n * reason `log.showSignature=false` is in the hardening: verifying a signature runs `gpg.program`. The flag\n * is on the command as well as in the config so the guarantee does not rest on the `-c` override\n * being consulted for this subcommand. `GitCommit` is deliberately not here — it is a mutation,\n * still asks, and runs with the user's own hooks (see {@link runGit}).\n */\nexport const READ_ONLY_GIT_ARGS = {\n GitStatus: ['status', '--short', '--ignore-submodules=dirty'],\n GitDiff: ['diff', ...HARDENED_DIFF_ARGS],\n GitLog: ['log', '--oneline', '-20', '--no-show-signature'],\n GitBranch: ['branch', '-a'],\n} as const satisfies Readonly<Record<string, string[]>>;\n\nexport const gitTools: ToolDefinition[] = [\n {\n name: 'GitStatus',\n idempotent: true,\n policy: { concurrency: 'parallel' },\n description:\n 'Show the working tree status (git status --short). Changes inside submodules are not listed; run `git -C <sub> status` for those.',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: gitStatus,\n },\n {\n name: 'GitDiff',\n idempotent: true,\n policy: { concurrency: 'parallel' },\n description: 'Show changes between commits, commit and working tree, etc.',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: gitDiff,\n },\n {\n name: 'GitLog',\n idempotent: true,\n policy: { concurrency: 'parallel' },\n description: 'Show recent commit logs (last 20, oneline)',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: gitLog,\n },\n {\n name: 'GitCommit',\n description: 'Create a new commit with a message',\n parameters: {\n type: 'object',\n properties: { message: { type: 'string', description: 'Commit message' } },\n required: ['message'],\n },\n execute: gitCommit,\n },\n {\n name: 'GitBranch',\n idempotent: true,\n policy: { concurrency: 'parallel' },\n description: 'List branches',\n parameters: { type: 'object', properties: {}, required: [] },\n execute: gitBranch,\n },\n];\n","import { execFile } from 'node:child_process';\nimport { delimiter } from 'node:path';\nimport { buildChildEnv } from '../child-env.js';\nimport { homeGitCeiling } from '../repo-containment.js';\n\n/**\n * Base -c overrides the Git tools and the managed-agent subsystem carry, so a checkout's own\n * configuration cannot turn a git call into something that runs a program.\n *\n * The configuration belongs to the checkout, and a checkout may have come from an archive, a\n * shared directory, or someone else's machine, so any of these can be set without the operator\n * having made that choice themselves:\n *\n * - `core.fsmonitor` — default off. A command `git status` executes to check whether the tree\n * changed. This is the sharpest one: a status call is supposed to be inert and it will happily\n * run whatever the repository named. `GIT_HARDENING_ARGS` turns it off by default; git's\n * built-in daemon (`core.fsmonitor=true`) is allowed back by `readRepositoryProgramPins` for\n * read-only callers when configured, while hook paths remain disabled (#357).\n * - `log.showSignature` — `git log` then verifies every signature it prints, and verification\n * runs `gpg.program`, another program the repository names. `GitDiff` and `GitStatus` do not\n * reach it, but `GitLog` does on every commit that carries a `gpgsig` header.\n * - `core.pager` — a command Git runs to page its output. `execFile` never allocates a TTY, so\n * git normally skips it, but the setting is read before that decision.\n * - `core.hooksPath` — where hooks live, and where most of the effect of this list lands: a\n * `post-checkout` on `worktree add`, a `pre-commit` on an agent's commit, a\n * `reference-transaction` on every ref Book moves, a `prepare-commit-msg` on the\n * `cherry-pick` that applies a candidate. The four read-only tools here run no hook at all, so\n * for them this is belt and braces against a future subcommand; for\n * `src/agents/git-isolation.ts`, whose calls are writes nothing asks about (#348), switching\n * them off is the decision. It is **not** applied to `GitCommit` or to `runGit` generally: on\n * the operator's own commit it would silently disable a hook they installed, and their code\n * silently not running is a worse failure than the one this list prevents.\n * - `core.untrackedCache`, `gc.auto`, `maintenance.auto` — background writers. `gc.auto=0` is not\n * the whole of it: `maintenance.auto` is separately on by default, so every commit spawns\n * `git maintenance run --auto`, which repacks and can run whatever strategies the configuration\n * registers while Book is moving refs. `--no-optional-locks` below covers the index lock these\n * need.\n *\n * `--no-optional-locks` is a top-level flag, not a config key, so it is added separately: it stops\n * Git taking `.git/index.lock` for a call that only reads, which a read-only worktree and a\n * concurrent `book` session would otherwise fight over.\n *\n * Each of these was checked against a real `git`, not read off this list — see\n * `git.test.ts`. Anything not reached through a non-TTY `execFile` (`column.ui`, `pager.log`,\n * `diff.external`) is not here either, because the flags that close those are per-command and\n * belong in `HARDENED_DIFF_ARGS`, which every hardened caller that runs `git diff` uses.\n */\nexport const GIT_HARDENING_ARGS: readonly string[] = [\n '-c',\n 'core.fsmonitor=false',\n '-c',\n 'core.pager=cat',\n '-c',\n 'core.hooksPath=',\n '-c',\n 'core.untrackedCache=false',\n '-c',\n 'gc.auto=0',\n '-c',\n 'maintenance.auto=false',\n '-c',\n 'log.showSignature=false',\n '--no-optional-locks',\n];\n\n/**\n * The environment every hardened call carries: a pager, so a checkout cannot name a program git\n * pages its output with, and a credential prompt it cannot sit waiting on. Callers that have an\n * environment of their own to merge (a tool's `ToolContext.env`, a temporary `GIT_INDEX_FILE`,\n * git isolation's commit identity) spread this into it; the pager and the prompt are the floor,\n * not a replacement.\n *\n * When the user's home directory holds a `.git` (a dotfiles repository), the home directory is\n * also a `GIT_CEILING_DIRECTORIES` entry (`homeGitCeiling`), appended to one the operator\n * exported, which still applies: git then does not climb into the home directory looking for a\n * repository, so a project below it with no `.git` of its own is in none, while a workspace that\n * is the home directory still finds it, since git never stops at the directory it starts in\n * (#579). Only Book's own git carries it: the read-only Git tools, the prompt's git line, `/review`,\n * the TUI status poll, the committed-file check, and git isolation; `GitCommit` takes the ceiling\n * alone ({@link homeGitCeilingEnv}). The user's `Bash` keeps the environment the user configured.\n *\n * The return type is `Record<string, string>` rather than `NodeJS.ProcessEnv`: a caller's\n * environment is `Record<string, string>` too, and spreading a wider one into it would make the\n * whole object `string | undefined` where nothing may be undefined.\n */\nexport function hardenedGitEnv(): Record<string, string> {\n return { GIT_PAGER: 'cat', GIT_TERMINAL_PROMPT: '0', ...homeGitCeilingEnv() };\n}\n\n/**\n * The home directory as a `GIT_CEILING_DIRECTORIES` entry when it holds a `.git`, appended to the\n * operator's exported list, else nothing; see {@link hardenedGitEnv}. On its own for `GitCommit`,\n * which otherwise keeps the user's environment so their hooks run as they configured them, but\n * must not commit into a dotfiles repository the read-only tools report as no repository (#579).\n */\nexport function homeGitCeilingEnv(): Record<string, string> {\n const ceiling = homeGitCeiling();\n if (ceiling === undefined) return {};\n const exported = process.env.GIT_CEILING_DIRECTORIES;\n return { GIT_CEILING_DIRECTORIES: exported ? `${exported}${delimiter}${ceiling}` : ceiling };\n}\n\n/**\n * The hardening flags in front of a caller's own arguments; see {@link GIT_HARDENING_ARGS}.\n *\n * Four callers, and they read this differently. The read-only Git tools (`readOnlyGit`) take\n * it because nothing asks before they run, so a checkout must not be able to start a program\n * through one; the prompt's per-turn git line (`gitContext` in `src/agent/context.ts`) goes\n * through `readOnlyGit` for the same reason. `/review`'s target (`src/review/target.ts`) and the TUI status poll\n * (`src/tui/hooks/useGitStatus.ts`) are the same: reports and background polls that fire with no\n * prompt. Git isolation (`src/agents/git-isolation.ts`) is the one that takes it on writes as\n * well — the agent's commit, the cherry-pick, `worktree add`, `update-ref` — where hooks being\n * off is a decision (#348) rather than a consequence: that work is Book's, in Book's worktree,\n * and a hook the operator installed is theirs to decide when it runs. The operator's own commits\n * keep their hooks, through `runGit`.\n *\n * Note that guarded callers pass repository program pins after these base flags\n * (`hardenedGitArgs([...pins, ...args])`) so operator values and neutralized pins override them (#357).\n */\nexport function hardenedGitArgs(args: readonly string[]): string[] {\n return [...GIT_HARDENING_ARGS, ...args];\n}\n\n/**\n * One `git config` entry, in the order git reported it.\n */\nexport interface GitConfigEntry {\n /**\n * Git's own name for where the value came from: `system`, `global`, `local`, `worktree` or\n * `command`.\n */\n scope: string;\n /**\n * The key as git canonicalized it: section and variable name lower-cased, subsection left\n * exactly as written.\n */\n key: string;\n /** The value, or `true` for a key written without one. */\n value: string;\n}\n\nexport type RepositoryProgramRunner = (\n args: string[],\n options: {\n cwd: string;\n allowExitCodes?: number[];\n timeoutMs?: number;\n signal?: AbortSignal;\n env?: NodeJS.ProcessEnv;\n windowsHide?: boolean;\n },\n) => Promise<{ stdout: string; stderr: string; code: number }>;\n\n/**\n * The config pattern that matches all repository-level programs: fsmonitor, filters, and merge\n * drivers; and the keys that make a repository a partial clone, `extensions.partialClone`,\n * `remote.<name>.promisor` and `remote.<name>.partialCloneFilter` (see {@link isPartialClone}).\n * Note: subsection name matching uses `.*` rather than `.+` to match empty subsection names (e.g. `[filter \"\"]`).\n */\nexport const REPOSITORY_PROGRAM_CONFIG_PATTERN =\n '^(core\\\\.fsmonitor|filter\\\\..*\\\\.(clean|smudge|process|required)|merge\\\\..*\\\\.driver|extensions\\\\.partialclone|remote\\\\..*\\\\.(promisor|partialclonefilter))$';\n\nconst OPERATOR_SCOPES: ReadonlySet<string> = new Set(['system', 'global', 'command']);\nconst REPOSITORY_SCOPES: ReadonlySet<string> = new Set(['local', 'worktree']);\n\n/**\n * Parse `git config -z --get-regexp` output.\n *\n * With `--show-scope`, the layout is pairs: `scope\\0key\\nvalue\\0` (or `scope\\0key\\0`).\n * Without `--show-scope` (passing `{ scope }`), the layout is entries: `key\\nvalue\\0` (or `key\\0`).\n */\nexport function parseGitConfig(stdout: string, options?: { scope?: string }): GitConfigEntry[] {\n const fields = stdout.split('\\0');\n const entries: GitConfigEntry[] = [];\n\n if (options?.scope !== undefined) {\n const scope = options.scope.toLowerCase();\n for (const field of fields) {\n if (!field) continue;\n const separator = field.indexOf('\\n');\n entries.push({\n scope,\n key: canonicalConfigKey(separator === -1 ? field : field.slice(0, separator)),\n value: separator === -1 ? 'true' : field.slice(separator + 1),\n });\n }\n return entries;\n }\n\n for (let index = 0; index + 1 < fields.length; index += 2) {\n const separator = fields[index + 1].indexOf('\\n');\n entries.push({\n scope: fields[index].toLowerCase(),\n key: canonicalConfigKey(\n separator === -1 ? fields[index + 1] : fields[index + 1].slice(0, separator),\n ),\n value: separator === -1 ? 'true' : fields[index + 1].slice(separator + 1),\n });\n }\n return entries;\n}\n\n/**\n * Canonicalize a config key the way git does: section and variable lower-cased, subsection\n * preserved as-is.\n */\nexport function canonicalConfigKey(raw: string): string {\n const nameStart = raw.lastIndexOf('.');\n if (nameStart === -1) return raw.toLowerCase();\n const head = raw.slice(0, nameStart);\n const name = raw.slice(nameStart + 1).toLowerCase();\n const subsectionStart = head.indexOf('.');\n if (subsectionStart === -1) return `${head.toLowerCase()}.${name}`;\n const section = head.slice(0, subsectionStart).toLowerCase();\n return `${section}.${head.slice(subsectionStart + 1)}.${name}`;\n}\n\n/**\n * Build `-c key=value` pins from git config entries.\n *\n * - Filter names defined in repository scope: clean/smudge/process pinned to operator value or empty;\n * required pinned to operator value or false.\n * - Merge drivers defined in repository scope: driver pinned to operator value or empty.\n * - If any repository-scope filter or merge driver has `=` in its name, throws because git `-c`\n * splits on `=` and cannot neutralize such a program.\n * - core.fsmonitor: if allowFsmonitor is true and effective value is boolean true, pin -c core.fsmonitor=true.\n * Otherwise nothing.\n */\nexport function repositoryProgramPins(\n entries: readonly GitConfigEntry[],\n options: { allowFsmonitor?: boolean } = {},\n): string[] {\n const allowFsmonitor = options.allowFsmonitor ?? true;\n\n const operatorFilters = new Map<\n string,\n { clean?: string; smudge?: string; process?: string; required?: string }\n >();\n const operatorMergeDrivers = new Map<string, string>();\n const repoFilterNames = new Set<string>();\n const repoFiltersWithProcess = new Set<string>();\n const repoMergeDriverNames = new Set<string>();\n\n let lastFsmonitor: string | undefined;\n\n for (const entry of entries) {\n const isRepo = REPOSITORY_SCOPES.has(entry.scope);\n const isOperator = OPERATOR_SCOPES.has(entry.scope);\n\n if (entry.key === 'core.fsmonitor') {\n if (entry.scope !== 'command') {\n lastFsmonitor = entry.value;\n }\n } else if (entry.key.startsWith('filter.')) {\n const lastDot = entry.key.lastIndexOf('.');\n if (lastDot >= 7) {\n const prop = entry.key.slice(lastDot + 1);\n const name = entry.key.slice(7, lastDot);\n if (prop === 'clean' || prop === 'smudge' || prop === 'process' || prop === 'required') {\n if (isRepo) {\n if (name.includes('=')) {\n throw new Error(\n `the repository's configuration defines a filter or merge driver named ${JSON.stringify(name)}, which Book cannot neutralize, so it will not run git here`,\n );\n }\n repoFilterNames.add(name);\n if (prop === 'process') {\n repoFiltersWithProcess.add(name);\n }\n }\n if (isOperator) {\n let op = operatorFilters.get(name);\n if (!op) {\n op = {};\n operatorFilters.set(name, op);\n }\n op[prop] = entry.value;\n }\n }\n }\n } else if (entry.key.startsWith('merge.') && entry.key.endsWith('.driver')) {\n const name = entry.key.slice(6, -7);\n if (isRepo) {\n if (name.includes('=')) {\n throw new Error(\n `the repository's configuration defines a filter or merge driver named ${JSON.stringify(name)}, which Book cannot neutralize, so it will not run git here`,\n );\n }\n repoMergeDriverNames.add(name);\n }\n if (isOperator) {\n operatorMergeDrivers.set(name, entry.value);\n }\n }\n }\n\n const pins: string[] = [];\n\n for (const name of repoFilterNames) {\n const op = operatorFilters.get(name);\n const clean = op?.clean ?? '';\n const smudge = op?.smudge ?? '';\n const process = op?.process ?? '';\n const required = op?.required ?? 'false';\n\n pins.push('-c', `filter.${name}.clean=${clean}`, '-c', `filter.${name}.smudge=${smudge}`);\n\n const hasOperator = op !== undefined;\n const repoHasProcess = repoFiltersWithProcess.has(name);\n const opHasProcess = op?.process !== undefined;\n if (!hasOperator || repoHasProcess || opHasProcess) {\n pins.push('-c', `filter.${name}.process=${process}`);\n }\n\n pins.push('-c', `filter.${name}.required=${required}`);\n }\n\n for (const name of repoMergeDriverNames) {\n const driver = operatorMergeDrivers.get(name) ?? '';\n pins.push('-c', `merge.${name}.driver=${driver}`);\n }\n\n if (allowFsmonitor && lastFsmonitor !== undefined) {\n const trimmed = lastFsmonitor.trim().toLowerCase();\n if (['true', 'yes', 'on', '1'].includes(trimmed)) {\n pins.push('-c', 'core.fsmonitor=true');\n }\n }\n\n return pins;\n}\n\nexport async function defaultGitRunner(\n args: string[],\n options: {\n cwd: string;\n allowExitCodes?: number[];\n timeoutMs?: number;\n signal?: AbortSignal;\n env?: NodeJS.ProcessEnv;\n windowsHide?: boolean;\n },\n): Promise<{ stdout: string; stderr: string; code: number }> {\n const timeout = options.timeoutMs === 0 ? 0 : (options.timeoutMs ?? 30_000);\n return new Promise((resolvePromise, reject) => {\n execFile(\n 'git',\n args,\n {\n cwd: options.cwd,\n timeout,\n signal: options.signal,\n encoding: 'utf8',\n maxBuffer: 50 * 1024 * 1024,\n windowsHide: options.windowsHide ?? true,\n env: buildChildEnv(\n process.env,\n options.env ? { ...hardenedGitEnv(), ...options.env } : hardenedGitEnv(),\n ),\n },\n (error, stdout, stderr) => {\n if (!error) {\n resolvePromise({ stdout, stderr, code: 0 });\n return;\n }\n\n const isKilled = Boolean(error.killed || error.signal);\n\n if (\n !isKilled &&\n typeof error.code === 'number' &&\n options.allowExitCodes?.includes(error.code)\n ) {\n resolvePromise({ stdout, stderr, code: error.code });\n return;\n }\n\n let failureReason: string;\n if (error.killed) {\n failureReason =\n timeout > 0\n ? `git timed out after ${timeout}ms and was killed`\n : 'git timed out and was killed';\n } else if (error.signal) {\n failureReason = `git was killed with ${error.signal}`;\n } else if (\n error.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' ||\n /maxBuffer/i.test(error.message)\n ) {\n failureReason = 'git output too large (maxBuffer exceeded)';\n } else if (typeof error.code === 'string') {\n failureReason = `git failed: ${error.code}`;\n } else {\n failureReason = stderr.trim() || stdout.trim() || error.message || 'git failed';\n }\n\n reject(new Error(failureReason));\n },\n );\n });\n}\n\nexport interface ReadPinsOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n env?: NodeJS.ProcessEnv;\n allowFsmonitor?: boolean;\n}\n\n/**\n * Whether the configuration makes the repository a partial clone, in any scope:\n * `extensions.partialClone` names a remote (any non-empty value, spaces included, as git takes it\n * literally), a `remote.<name>.promisor` is true, or a `remote.<name>.partialCloneFilter` is set,\n * which git also treats as a promisor remote. A promisor value git would not read as false counts,\n * so an unusual spelling errs towards a partial clone.\n */\nexport function isPartialClone(entries: readonly GitConfigEntry[]): boolean {\n return entries.some((entry) => {\n if (entry.key === 'extensions.partialclone') return entry.value !== '';\n if (!entry.key.startsWith('remote.')) return false;\n if (entry.key.endsWith('.partialclonefilter')) return true;\n if (entry.key.endsWith('.promisor')) {\n return !['false', 'no', 'off', '0', ''].includes(entry.value.trim().toLowerCase());\n }\n return false;\n });\n}\n\n/** What one read of a directory's git configuration says. */\nexport interface RepositoryGitConfig {\n /** The `-c` pins for its repository-level programs; see {@link repositoryProgramPins}. */\n pins: string[];\n /** Whether it is a partial clone; see {@link isPartialClone}. */\n partialClone: boolean;\n}\n\n/**\n * Read repository program pins for a directory by inspecting git config.\n */\nexport async function readRepositoryProgramPins(\n cwd: string,\n exec?: RepositoryProgramRunner,\n options?: ReadPinsOptions,\n): Promise<string[]> {\n return (await readRepositoryGitConfig(cwd, exec, options)).pins;\n}\n\n/**\n * Read a directory's git configuration once, for the repository program pins and for whether the\n * repository is a partial clone.\n */\nexport async function readRepositoryGitConfig(\n cwd: string,\n exec?: RepositoryProgramRunner,\n options?: ReadPinsOptions,\n): Promise<RepositoryGitConfig> {\n const runner = exec ?? defaultGitRunner;\n const timeoutMs = options?.timeoutMs === 0 ? 0 : (options?.timeoutMs ?? 30_000);\n const signal = options?.signal;\n const env = options?.env;\n const allowFsmonitor = options?.allowFsmonitor ?? true;\n\n const showScopeArgs = hardenedGitArgs([\n 'config',\n '--show-scope',\n '-z',\n '--get-regexp',\n REPOSITORY_PROGRAM_CONFIG_PATTERN,\n ]);\n\n let isUnknownOption = false;\n let showScopeResult: { stdout: string; stderr: string; code: number } | undefined;\n\n try {\n showScopeResult = await runner(showScopeArgs, {\n cwd,\n allowExitCodes: [1, 129],\n timeoutMs,\n signal,\n env,\n });\n if (showScopeResult.code === 129 || /unknown option/i.test(showScopeResult.stderr)) {\n isUnknownOption = true;\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (/unknown option/i.test(message)) {\n isUnknownOption = true;\n } else {\n throw error;\n }\n }\n\n if (!isUnknownOption && showScopeResult) {\n if (showScopeResult.code === 0) {\n const entries = parseGitConfig(showScopeResult.stdout);\n return {\n pins: repositoryProgramPins(entries, { allowFsmonitor }),\n partialClone: isPartialClone(entries),\n };\n }\n\n if (showScopeResult.code === 1) {\n return { pins: [], partialClone: false };\n }\n\n throw new Error(\n showScopeResult.stderr.trim() ||\n showScopeResult.stdout.trim() ||\n `git config failed (${showScopeResult.code})`,\n );\n }\n\n // Fallback for older git without --show-scope: read system, global, local, worktree in order.\n const entries: GitConfigEntry[] = [];\n\n const systemArgs = hardenedGitArgs([\n 'config',\n '--system',\n '--includes',\n '-z',\n '--get-regexp',\n REPOSITORY_PROGRAM_CONFIG_PATTERN,\n ]);\n try {\n const sysResult = await runner(systemArgs, {\n cwd,\n allowExitCodes: [1],\n timeoutMs,\n signal,\n env,\n });\n if (sysResult.code === 0) {\n entries.push(...parseGitConfig(sysResult.stdout, { scope: 'system' }));\n }\n } catch {\n // System config not present or unreadable; ignore\n }\n\n const globalArgs = hardenedGitArgs([\n 'config',\n '--global',\n '--includes',\n '-z',\n '--get-regexp',\n REPOSITORY_PROGRAM_CONFIG_PATTERN,\n ]);\n const globalResult = await runner(globalArgs, {\n cwd,\n allowExitCodes: [1],\n timeoutMs,\n signal,\n env,\n });\n if (globalResult.code === 0) {\n entries.push(...parseGitConfig(globalResult.stdout, { scope: 'global' }));\n } else if (globalResult.code !== 1) {\n throw new Error(\n globalResult.stderr.trim() ||\n globalResult.stdout.trim() ||\n `git config --global failed (${globalResult.code})`,\n );\n }\n\n const localArgs = hardenedGitArgs([\n 'config',\n '--local',\n '--includes',\n '-z',\n '--get-regexp',\n REPOSITORY_PROGRAM_CONFIG_PATTERN,\n ]);\n const localResult = await runner(localArgs, {\n cwd,\n allowExitCodes: [1],\n timeoutMs,\n signal,\n env,\n });\n if (localResult.code === 0) {\n entries.push(...parseGitConfig(localResult.stdout, { scope: 'local' }));\n } else if (localResult.code !== 1) {\n throw new Error(\n localResult.stderr.trim() ||\n localResult.stdout.trim() ||\n `git config --local failed (${localResult.code})`,\n );\n }\n\n const worktreeArgs = hardenedGitArgs([\n 'config',\n '--worktree',\n '--includes',\n '-z',\n '--get-regexp',\n REPOSITORY_PROGRAM_CONFIG_PATTERN,\n ]);\n try {\n const wtResult = await runner(worktreeArgs, {\n cwd,\n allowExitCodes: [1, 128],\n timeoutMs,\n signal,\n env,\n });\n if (wtResult.code === 0) {\n entries.push(...parseGitConfig(wtResult.stdout, { scope: 'worktree' }));\n }\n } catch {\n // Worktree config not supported or absent; ignore when that fails\n }\n\n return {\n pins: repositoryProgramPins(entries, { allowFsmonitor: false }),\n partialClone: isPartialClone(entries),\n };\n}\n"],"mappings":"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","names":["execFile","execFile","process","execFile","entries","execFile"]}
@@ -0,0 +1,120 @@
1
+ import {
2
+ createDebugLogger
3
+ } from "./chunk-OJBMDM5H.js";
4
+ import {
5
+ canonicalizePath,
6
+ realWorkspaceRoot
7
+ } from "./chunk-YIL6HTJX.js";
8
+ import {
9
+ updateWorkspaceTrust
10
+ } from "./chunk-HFB74CLM.js";
11
+ import {
12
+ resolveBookHome
13
+ } from "./chunk-ZPSZZUWF.js";
14
+
15
+ // src/additional-roots.ts
16
+ import { existsSync, realpathSync } from "fs";
17
+ import { homedir } from "os";
18
+ import { isAbsolute, relative, resolve, sep } from "path";
19
+ var log = createDebugLogger("additional-roots");
20
+ function realPathOfDirectory(workspace, declared) {
21
+ const absolute = isAbsolute(declared) ? resolve(declared) : resolve(workspace, declared);
22
+ if (!existsSync(absolute)) {
23
+ log.debug("ignoring declared directory that does not exist", { declared, resolved: absolute });
24
+ return void 0;
25
+ }
26
+ try {
27
+ return realpathSync.native(absolute);
28
+ } catch (error) {
29
+ log.debug("ignoring declared directory that cannot be resolved", {
30
+ declared,
31
+ resolved: absolute,
32
+ error: error instanceof Error ? error.message : String(error)
33
+ });
34
+ return void 0;
35
+ }
36
+ }
37
+ function collectDeclaredDirectories(workspace, declared) {
38
+ const seen = /* @__PURE__ */ new Set();
39
+ const out = [];
40
+ for (const entry of declared ?? []) {
41
+ const realPath = realPathOfDirectory(workspace, entry);
42
+ if (!realPath || seen.has(realPath)) continue;
43
+ seen.add(realPath);
44
+ out.push({ declared: entry, realPath });
45
+ }
46
+ return out;
47
+ }
48
+ function evaluateProjectDirectory(store, realPath) {
49
+ return store?.[realPath] ?? "unknown";
50
+ }
51
+ function partitionProjectDirectories(directories, store) {
52
+ const partition = { approved: [], pending: [], rejected: [] };
53
+ for (const directory of directories) {
54
+ const state = evaluateProjectDirectory(store, directory.realPath);
55
+ if (state === "approved") partition.approved.push(directory);
56
+ else if (state === "rejected") partition.rejected.push(directory);
57
+ else partition.pending.push(directory);
58
+ }
59
+ return partition;
60
+ }
61
+ function persistProjectDirectoryChoice(workspace, realPath, choice, options = {}) {
62
+ return updateWorkspaceTrust(
63
+ workspace,
64
+ (trust) => {
65
+ trust.projectDirectories[realPath] = choice;
66
+ },
67
+ options.trustStorePath
68
+ );
69
+ }
70
+ function resolveAdditionalRoots(workspace, declared) {
71
+ const realWorkspace = realWorkspaceRoot(workspace);
72
+ const roots = [];
73
+ const seen = /* @__PURE__ */ new Set();
74
+ for (const entry of collectDeclaredDirectories(workspace, declared)) {
75
+ const fromWorkspace = relative(realWorkspace, entry.realPath);
76
+ if (!isAbsolute(fromWorkspace) && fromWorkspace !== ".." && !fromWorkspace.startsWith(`..${sep}`)) {
77
+ log.debug("dropping declared directory the workspace already serves", {
78
+ declared: entry.declared,
79
+ realPath: entry.realPath
80
+ });
81
+ continue;
82
+ }
83
+ if (seen.has(entry.realPath)) continue;
84
+ seen.add(entry.realPath);
85
+ roots.push(entry.realPath);
86
+ }
87
+ return roots;
88
+ }
89
+ function rootHoldsHome(root, homes = homeGuards()) {
90
+ return homes.some((home) => containsPath(root, home));
91
+ }
92
+ function homeGuards() {
93
+ const homes = [];
94
+ for (const home of [resolveBookHome(), homedir()]) {
95
+ const real = canonicalizePath(home);
96
+ if (real && !homes.includes(real)) homes.push(real);
97
+ }
98
+ return homes;
99
+ }
100
+ function pathHoldsHome(canonicalPath, homes = homeGuards()) {
101
+ return homes.some((home) => containsPath(home, canonicalPath));
102
+ }
103
+ function containsPath(root, target) {
104
+ const fromRoot = relative(canonicalizePath(root), canonicalizePath(target));
105
+ return !(fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot));
106
+ }
107
+
108
+ export {
109
+ realPathOfDirectory,
110
+ collectDeclaredDirectories,
111
+ evaluateProjectDirectory,
112
+ partitionProjectDirectories,
113
+ persistProjectDirectoryChoice,
114
+ resolveAdditionalRoots,
115
+ rootHoldsHome,
116
+ homeGuards,
117
+ pathHoldsHome,
118
+ containsPath
119
+ };
120
+ //# sourceMappingURL=chunk-4DKHLZRI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/additional-roots.ts"],"sourcesContent":["/**\n * The directories `additionalDirectories` actually adds, and the decisions about them.\n *\n * The setting names directories the file tools may serve alongside the workspace: Read, Glob and\n * Grep reach them without a prompt, and the write tools' root check accepts them (a write there\n * still goes through the ordinary permission flow for its mode). It is honoured from the layers\n * the user controls — the user-global file, `.book/settings.local.json`, and `--settings` — and\n * withheld from the checked-in project layer until the user approves it in the user-global trust\n * store, exactly as a project-declared `permissions.allow` rule is (see `permission-approvals.ts`\n * for why, and `workspace-trust.ts` for where the decision lives).\n *\n * A decision is keyed by the directory's **real path**, never by the text the repository wrote.\n * A repository controls that text and could declare `./link` for a symlink pointing at `/etc`; a\n * user who approved the string would have approved a path they never saw. Resolving first also\n * makes the key stable: `./shared`, `shared` and `<workspace>/shared/` are one directory, and\n * retargeting a link makes the key change, so the earlier decision no longer covers it and the\n * directory is pending again.\n */\nimport { existsSync, realpathSync } from 'fs';\nimport { homedir } from 'os';\nimport { isAbsolute, relative, resolve, sep } from 'path';\nimport { createDebugLogger } from './debug-log.js';\nimport { resolveBookHome } from './book-home.js';\nimport { realWorkspaceRoot, canonicalizePath } from './tools/path-utils.js';\nimport type { ProjectDirectoryChoice } from './settings.js';\nimport { updateWorkspaceTrust } from './workspace-trust.js';\n\nconst log = createDebugLogger('additional-roots');\n\n/** Recorded decisions, keyed by the directory's real path. */\nexport type ProjectDirectoryStore = Record<string, ProjectDirectoryChoice>;\n\nexport type ProjectDirectoryState = ProjectDirectoryChoice | 'unknown';\n\n/** A project-declared directory, as the file tools will see it and as the repository wrote it. */\nexport interface DeclaredDirectory {\n /** The text the project layer declared, kept for display. */\n declared: string;\n /** The real path, which is also the key a decision is recorded under. */\n realPath: string;\n}\n\n/**\n * The real path a declared directory resolves to, or `undefined` when it is not usable: a path\n * that does not exist is ignored (with a debug log, since a checkout may not carry it yet), and\n * one that cannot be resolved is ignored the same way rather than trusted as written.\n *\n * Resolution follows links, so a declaration that points somewhere other than where it reads is\n * the thing the user is asked about.\n */\nexport function realPathOfDirectory(workspace: string, declared: string): string | undefined {\n const absolute = isAbsolute(declared) ? resolve(declared) : resolve(workspace, declared);\n if (!existsSync(absolute)) {\n log.debug('ignoring declared directory that does not exist', { declared, resolved: absolute });\n return undefined;\n }\n try {\n return realpathSync.native(absolute);\n } catch (error) {\n log.debug('ignoring declared directory that cannot be resolved', {\n declared,\n resolved: absolute,\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n }\n}\n\n/**\n * The project layer's declared directories, as `{declared, realPath}` pairs, deduplicated by\n * real path (two spellings of one directory are one decision) and in declaration order.\n */\nexport function collectDeclaredDirectories(\n workspace: string,\n declared: readonly string[] | undefined,\n): DeclaredDirectory[] {\n const seen = new Set<string>();\n const out: DeclaredDirectory[] = [];\n for (const entry of declared ?? []) {\n const realPath = realPathOfDirectory(workspace, entry);\n if (!realPath || seen.has(realPath)) continue;\n seen.add(realPath);\n out.push({ declared: entry, realPath });\n }\n return out;\n}\n\nexport function evaluateProjectDirectory(\n store: ProjectDirectoryStore | undefined,\n realPath: string,\n): ProjectDirectoryState {\n return store?.[realPath] ?? 'unknown';\n}\n\nexport interface ProjectDirectoryPartition {\n /** Approved by the user: these become honored roots. */\n approved: DeclaredDirectory[];\n /** No decision recorded: withheld until the user makes one. */\n pending: DeclaredDirectory[];\n /** Explicitly refused: withheld, and not re-offered. */\n rejected: DeclaredDirectory[];\n}\n\nexport function partitionProjectDirectories(\n directories: readonly DeclaredDirectory[],\n store: ProjectDirectoryStore | undefined,\n): ProjectDirectoryPartition {\n const partition: ProjectDirectoryPartition = { approved: [], pending: [], rejected: [] };\n for (const directory of directories) {\n const state = evaluateProjectDirectory(store, directory.realPath);\n if (state === 'approved') partition.approved.push(directory);\n else if (state === 'rejected') partition.rejected.push(directory);\n else partition.pending.push(directory);\n }\n return partition;\n}\n\n/**\n * Record an approve/reject decision for one project-declared directory, keyed by its real path,\n * leaving every other recorded decision untouched.\n */\nexport function persistProjectDirectoryChoice(\n workspace: string,\n realPath: string,\n choice: ProjectDirectoryChoice,\n options: { trustStorePath?: string } = {},\n): { ok: boolean; error?: string } {\n return updateWorkspaceTrust(\n workspace,\n (trust) => {\n trust.projectDirectories[realPath] = choice;\n },\n options.trustStorePath,\n );\n}\n\n/**\n * The honored roots the file tools and the permission scope use, for one evaluation.\n *\n * `declared` is the resolved settings list, which already carries every layer's entries the\n * resolver decided to release; nothing here re-reads a file. A declared directory inside the\n * workspace adds nothing — the workspace already serves it — so it is dropped rather than\n * resolved twice, and a path that vanished between resolution and now is dropped too.\n *\n * Resolved once per run and passed down through `ToolContext` and `WorkspaceScope`, so a managed\n * child and a subagent serve exactly the roots their parent does.\n */\nexport function resolveAdditionalRoots(\n workspace: string,\n declared: readonly string[] | undefined,\n): string[] {\n const realWorkspace = realWorkspaceRoot(workspace);\n const roots: string[] = [];\n const seen = new Set<string>();\n for (const entry of collectDeclaredDirectories(workspace, declared)) {\n // Inside the workspace — not merely equal to it: the workspace already serves everything under\n // it, so listing a subdirectory adds a root that widens nothing. The test is `..` followed by\n // a separator rather than a bare prefix, because a sibling whose name starts with two dots\n // (`/home/u/..foo`) is *outside* the workspace and `startsWith('..')` read it as inside. An\n // empty result means equal, and equal is inside.\n const fromWorkspace = relative(realWorkspace, entry.realPath);\n if (\n !isAbsolute(fromWorkspace) &&\n fromWorkspace !== '..' &&\n !fromWorkspace.startsWith(`..${sep}`)\n ) {\n log.debug('dropping declared directory the workspace already serves', {\n declared: entry.declared,\n realPath: entry.realPath,\n });\n continue;\n }\n if (seen.has(entry.realPath)) continue;\n seen.add(entry.realPath);\n roots.push(entry.realPath);\n }\n return roots;\n}\n\n/**\n * Whether a root is, or contains, a home directory — the OS home or Book's own `BOOK_HOME`.\n *\n * A home carries SSH and provider keys and Book's trust store, so a session rooted at one keeps\n * prompting for reads. Compared as written and after following links, so a root that is a link\n * to a home, or a home reached through a link, still counts. One function for the workspace and\n * for each honored directory, so the rule reads the same wherever it applies.\n */\nexport function rootHoldsHome(root: string, homes: readonly string[] = homeGuards()): boolean {\n return homes.some((home) => containsPath(root, home));\n}\n\n/**\n * The home directories a read is never auto-allowed in: Book's own `BOOK_HOME` and the OS home,\n * each resolved through links once, deduplicated.\n *\n * Resolved once per run matters — a guard is consulted for every Read, Glob and Grep, and the\n * alternative is a `realpath` walk per call — and it is what lets {@link pathHoldsHome} compare\n * against a short fixed list.\n */\nexport function homeGuards(): string[] {\n const homes: string[] = [];\n for (const home of [resolveBookHome(), homedir()]) {\n const real = canonicalizePath(home);\n if (real && !homes.includes(real)) homes.push(real);\n }\n return homes;\n}\n\n/**\n * Whether a canonical path is a home directory or is inside one.\n *\n * Deliberately a test of the path rather than of the root that authorized it: a root may hold a\n * home as a subdirectory, or reach one through a symlink, and either way the read lands on\n * `~/.ssh/id_rsa` whatever the operator approved. Checking the resolved target catches both.\n */\nexport function pathHoldsHome(\n canonicalPath: string,\n homes: readonly string[] = homeGuards(),\n): boolean {\n return homes.some((home) => containsPath(home, canonicalPath));\n}\n\n/**\n * Whether `root` is `target` or contains it, both sides compared in one canonical form.\n *\n * The whole of that rule: a root as the caller spelled it and a path from `realpath` are two\n * spellings of one directory at best, and two unrelated ones when the root was typed in 8.3 short\n * form — so the root is put through the same canonicalization as the path before the two are\n * compared. Both are canonicalized here rather than one of them assumed canonical, because a home\n * list, a root list and a target all arrive from different places.\n *\n * Exported because a Grep or Glob *scope* has to be compared against a home the same way a file\n * is (PR #334 finding 4).\n */\nexport function containsPath(root: string, target: string): boolean {\n const fromRoot = relative(canonicalizePath(root), canonicalizePath(target));\n return !(fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot));\n}\n"],"mappings":"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","names":[]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  writeFileAtomic
3
- } from "./chunk-574C6MQR.js";
3
+ } from "./chunk-REASC2QG.js";
4
4
  import {
5
5
  resolveBookHome
6
- } from "./chunk-24AXE6SP.js";
6
+ } from "./chunk-ZPSZZUWF.js";
7
7
 
8
8
  // src/model-window-store.ts
9
9
  import { existsSync, readFileSync } from "fs";
@@ -18,7 +18,7 @@ var learnedModelWindowEntrySchema = z.object({
18
18
  });
19
19
  var looseModelWindowStoreSchema = z.object({
20
20
  version: z.number().int().positive().default(MODEL_WINDOW_STORE_VERSION),
21
- models: z.record(z.unknown()).default({})
21
+ models: z.record(z.string(), z.unknown()).default({})
22
22
  });
23
23
  function emptyStore() {
24
24
  return { version: MODEL_WINDOW_STORE_VERSION, models: {} };
@@ -197,6 +197,8 @@ export {
197
197
  MODEL_WINDOW_STORE_VERSION,
198
198
  LEARNED_WINDOW_SAFETY_MARGIN,
199
199
  MIN_LEARNED_CONTEXT_WINDOW,
200
+ learnedModelWindowEntrySchema,
201
+ looseModelWindowStoreSchema,
200
202
  defaultModelWindowStorePath,
201
203
  readModelWindowStore,
202
204
  writeModelWindowStore,
@@ -205,4 +207,4 @@ export {
205
207
  MemoryModelWindowStore,
206
208
  createModelWindowStore
207
209
  };
208
- //# sourceMappingURL=chunk-CAGOB2N4.js.map
210
+ //# sourceMappingURL=chunk-62EEAUID.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/model-window-store.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport { resolveBookHome } from './book-home.js';\nimport { writeFileAtomic } from './settings-repository.js';\n\nexport const MODEL_WINDOW_STORE_VERSION = 1;\n\n/**\n * Fraction of the refused prompt history recorded as the learned window ceiling.\n *\n * When a provider rejects a request for exceeding the context limit, the history\n * tokens at that moment represent a size that was REFUSED, not a size the provider\n * accepts. Recording the refused size directly would set a ceiling larger than the\n * true context window (e.g. true window 100k, refused history 150k -> ceiling 150k),\n * causing repeated user-visible failures. Applying a safety margin ensures the\n * recorded ceiling is strictly below the refused size and converges rapidly to a\n * viable operating limit.\n */\nexport const LEARNED_WINDOW_SAFETY_MARGIN = 0.8;\n\n/**\n * Minimum context window ceiling recorded from an overflow refusal.\n *\n * Sane coding sessions require at least 16k tokens for tools, system prompt,\n * and working context. Any refusal below this floor is far more likely to be an\n * upstream proxy error (e.g. 413 Request Entity Too Large) or bad telemetry\n * than a real context limit. Refusals below this floor are ignored rather than\n * recorded, preventing a transient error from permanently bricking the model.\n */\nexport const MIN_LEARNED_CONTEXT_WINDOW = 16_384;\n\nexport interface LearnedModelWindowEntry {\n contextWindow: number;\n learnedAt: number;\n}\n\nexport interface ModelWindowStoreData {\n version: number;\n models: Record<string, LearnedModelWindowEntry>;\n}\n\nexport interface ModelWindowStoreOptions {\n /** Explicit BOOK_HOME directory root (used by tests to isolate storage). */\n home?: string;\n /** Explicit file path override (used by tests). */\n path?: string;\n /** Optional clock override for timestamps (used by tests). */\n now?: () => number;\n}\n\nexport interface ModelWindowStore {\n get(model: string): number | undefined;\n getEntry(model: string): LearnedModelWindowEntry | undefined;\n ratchet(model: string, ceiling: number): boolean;\n all(): Record<string, LearnedModelWindowEntry>;\n}\n\nexport const learnedModelWindowEntrySchema = z.object({\n contextWindow: z.number().int().positive(),\n learnedAt: z.number().int().nonnegative(),\n});\n\nexport const looseModelWindowStoreSchema = z.object({\n version: z.number().int().positive().default(MODEL_WINDOW_STORE_VERSION),\n models: z.record(z.string(), z.unknown()).default({}),\n});\n\nfunction emptyStore(): ModelWindowStoreData {\n return { version: MODEL_WINDOW_STORE_VERSION, models: {} };\n}\n\n/**\n * Resolved location for the user-global learned-window store.\n *\n * This file lives under BOOK_HOME (e.g. ~/.book/model-windows.json), NEVER inside\n * the workspace. A repository must never be able to declare or tamper with\n * learned window ceilings for itself.\n */\nexport function defaultModelWindowStorePath(home?: string): string {\n return home ? join(home, 'model-windows.json') : join(resolveBookHome(), 'model-windows.json');\n}\n\n/**\n * Read the learned-window store.\n * Tolerates missing, empty, or corrupt files by returning an empty store.\n * Never throws into a run.\n */\nexport function readModelWindowStore(options?: ModelWindowStoreOptions): ModelWindowStoreData {\n const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);\n if (!existsSync(filePath)) {\n return emptyStore();\n }\n try {\n const raw = readFileSync(filePath, 'utf-8').trim();\n if (!raw) return emptyStore();\n const parsed = JSON.parse(raw);\n const validated = looseModelWindowStoreSchema.safeParse(parsed);\n if (!validated.success) {\n return emptyStore();\n }\n const models: Record<string, LearnedModelWindowEntry> = {};\n for (const [key, value] of Object.entries(validated.data.models)) {\n const entryResult = learnedModelWindowEntrySchema.safeParse(value);\n if (entryResult.success) {\n models[key] = entryResult.data;\n }\n }\n return {\n version: validated.data.version,\n models,\n };\n } catch {\n return emptyStore();\n }\n}\n\n/**\n * Atomically persist the learned-window store.\n * Never throws into a run.\n */\nexport function writeModelWindowStore(\n data: ModelWindowStoreData,\n options?: ModelWindowStoreOptions,\n): boolean {\n if (data.version > MODEL_WINDOW_STORE_VERSION) {\n return false;\n }\n const filePath = options?.path ?? defaultModelWindowStorePath(options?.home);\n try {\n if (existsSync(filePath)) {\n const existing = readModelWindowStore(options);\n if (existing.version > MODEL_WINDOW_STORE_VERSION) {\n return false;\n }\n }\n const serialized = JSON.stringify(data, null, 2) + '\\n';\n writeFileAtomic(filePath, serialized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Records a learned context window ceiling for a model.\n *\n * Ratchet, downward only:\n * - If no entry exists for the model, records the new ceiling.\n * - If an entry exists with a larger ceiling, lowers it.\n * - If an entry exists with an equal or smaller ceiling, changes nothing.\n *\n * Monotonicity is strict: a later overflow at a smaller size lowers it further;\n * an overflow at a larger size does not raise it.\n *\n * Returns true if the store was updated with a new ceiling, false otherwise.\n */\nexport function ratchetModelWindow(\n model: string,\n ceiling: number,\n options?: ModelWindowStoreOptions,\n): boolean {\n return createModelWindowStore(options).ratchet(model, ceiling);\n}\n\n/**\n * File-backed implementation of ModelWindowStore.\n * Delegates to createModelWindowStore for closure-based, freeze-safe caching.\n */\nexport class FileModelWindowStore implements ModelWindowStore {\n private readonly inner: ModelWindowStore;\n\n constructor(options?: ModelWindowStoreOptions) {\n this.inner = createModelWindowStore(options);\n }\n\n get(model: string): number | undefined {\n return this.inner.get(model);\n }\n\n getEntry(model: string): LearnedModelWindowEntry | undefined {\n return this.inner.getEntry(model);\n }\n\n ratchet(model: string, ceiling: number): boolean {\n return this.inner.ratchet(model, ceiling);\n }\n\n all(): Record<string, LearnedModelWindowEntry> {\n return this.inner.all();\n }\n}\n\n/**\n * In-memory implementation of ModelWindowStore, useful for unit tests.\n */\nexport class MemoryModelWindowStore implements ModelWindowStore {\n private readonly models = new Map<string, LearnedModelWindowEntry>();\n private readonly now: () => number;\n\n constructor(initial?: Record<string, LearnedModelWindowEntry>, now = Date.now) {\n this.now = now;\n if (initial) {\n for (const [k, v] of Object.entries(initial)) {\n this.models.set(k, { ...v });\n }\n }\n }\n\n get(model: string): number | undefined {\n return this.models.get(model)?.contextWindow;\n }\n\n getEntry(model: string): LearnedModelWindowEntry | undefined {\n const entry = this.models.get(model);\n return entry ? { ...entry } : undefined;\n }\n\n ratchet(model: string, ceiling: number): boolean {\n if (\n !model ||\n typeof ceiling !== 'number' ||\n !Number.isFinite(ceiling) ||\n ceiling < MIN_LEARNED_CONTEXT_WINDOW\n ) {\n return false;\n }\n const normalized = Math.floor(ceiling);\n const existing = this.models.get(model);\n if (existing && existing.contextWindow <= normalized) {\n return false;\n }\n this.models.set(model, { contextWindow: normalized, learnedAt: this.now() });\n return true;\n }\n\n all(): Record<string, LearnedModelWindowEntry> {\n const result: Record<string, LearnedModelWindowEntry> = {};\n for (const [k, v] of this.models.entries()) {\n result[k] = { ...v };\n }\n return result;\n }\n}\n\n/**\n * Create a freeze-safe ModelWindowStore instance.\n *\n * Cache state is held in a closure rather than as object properties, ensuring\n * that Object.freeze / deepFreeze(config) does not break subsequent memoization.\n */\nexport function createModelWindowStore(options?: ModelWindowStoreOptions): ModelWindowStore {\n let cachedData: ModelWindowStoreData | undefined;\n\n function load(): ModelWindowStoreData {\n if (cachedData === undefined) {\n cachedData = readModelWindowStore(options);\n }\n return cachedData;\n }\n\n return {\n get(model: string): number | undefined {\n if (!model) return undefined;\n return load().models[model]?.contextWindow;\n },\n\n getEntry(model: string): LearnedModelWindowEntry | undefined {\n if (!model) return undefined;\n const entry = load().models[model];\n return entry ? { ...entry } : undefined;\n },\n\n ratchet(model: string, ceiling: number): boolean {\n if (\n !model ||\n typeof ceiling !== 'number' ||\n !Number.isFinite(ceiling) ||\n ceiling < MIN_LEARNED_CONTEXT_WINDOW\n ) {\n return false;\n }\n const fresh = readModelWindowStore(options);\n if (fresh.version > MODEL_WINDOW_STORE_VERSION) {\n return false;\n }\n const normalizedCeiling = Math.floor(ceiling);\n const existingEntry = fresh.models[model];\n const targetCeiling = existingEntry\n ? Math.min(existingEntry.contextWindow, normalizedCeiling)\n : normalizedCeiling;\n\n if (existingEntry && existingEntry.contextWindow === targetCeiling) {\n // If the merged value equals what is already on disk, write nothing and return false.\n return false;\n }\n\n const now = options?.now ? options.now() : Date.now();\n const updated: ModelWindowStoreData = {\n version: fresh.version,\n models: {\n ...fresh.models,\n [model]: {\n contextWindow: targetCeiling,\n learnedAt: now,\n },\n },\n };\n const written = writeModelWindowStore(updated, options);\n if (written) {\n cachedData = updated;\n return true;\n }\n return false;\n },\n\n all(): Record<string, LearnedModelWindowEntry> {\n const models = load().models;\n const result: Record<string, LearnedModelWindowEntry> = {};\n for (const [k, v] of Object.entries(models)) {\n result[k] = { ...v };\n }\n return result;\n },\n };\n}\n"],"mappings":"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","names":[]}