@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/tools/aliases.ts","../src/tools/primary-arg.ts","../src/tools/result.ts","../src/tools/tool-capabilities.ts","../src/tools/path-utils.ts"],"sourcesContent":["/** Legacy tool-name aliases, resolved to their canonical PascalCase names. */\nexport const TOOL_ALIASES: Record<string, string> = {\n read_file: 'Read',\n write_file: 'Write',\n edit_file: 'Edit',\n multi_edit: 'MultiEdit',\n apply_patch: 'ApplyPatch',\n glob: 'Glob',\n glob_files: 'Glob',\n grep: 'Grep',\n bash: 'Bash',\n // Legacy snake_case git tool names — backward compatible.\n git_status: 'GitStatus',\n git_diff: 'GitDiff',\n git_log: 'GitLog',\n git_commit: 'GitCommit',\n git_branch: 'GitBranch',\n};\n\n/** Map a (possibly aliased) tool name to its canonical name for display / matching. */\nexport function canonicalToolName(name: string): string {\n return TOOL_ALIASES[name] ?? name;\n}\n\n/**\n * Namespaces a model may write in front of a tool's name. Some providers send\n * `default:Glob`; models write the same namespaces with a dot (`default.Bash`),\n * and OpenAI's harmony format addresses every tool as `functions.<name>`. Each\n * is the tool's own name behind a prefix, so the registry runs the call as that\n * tool. The list stays explicit: `pnpm:list` and `pnpm.list` are commands, not\n * tools.\n */\nexport const PROVIDER_TOOL_PREFIXES = [\n 'parent:',\n 'default:',\n 'tool:',\n 'parent.',\n 'default.',\n 'tool.',\n 'functions.',\n] as const;\n\n/** The name behind a provider namespace (`default.Bash` → `Bash`), or the name unchanged. */\nexport function unwrapProviderPrefix(name: string): string {\n const prefix = PROVIDER_TOOL_PREFIXES.find((candidate) => name.startsWith(candidate));\n return prefix ? name.slice(prefix.length) : name;\n}\n\n/**\n * A tool's name as the transcript shows it: namespace unwrapped, alias\n * resolved. The transcript receives a call as the model streamed it, before the\n * registry canonicalizes it, so `default.Bash` would otherwise read as a tool\n * of its own while it ran as `Bash`. For display only: matching and permissions\n * see the call the registry already normalized.\n */\nexport function displayToolName(name: string): string {\n return canonicalToolName(unwrapProviderPrefix(name));\n}\n","/**\n * Extract the primary argument from a tool call for display and rule matching.\n *\n * Priority order follows Claude Code's convention:\n * command → shell_id → shellId → taskId → task_id → filePath → file_path\n * → notebook_path → pattern → path → agent → skill → message → url → query → subject\n * → old_string → first string key.\n *\n * `pattern` outranks `path`: for search tools (Grep) the pattern is the\n * rule-matching identity, while `path` is only an optional scope.\n *\n * For command-type args, only the first line is returned.\n */\nexport function getPrimaryArg(args: Record<string, unknown>): string {\n if (typeof args.command === 'string') return args.command.split('\\n')[0];\n if (typeof args.shell_id === 'string') return args.shell_id;\n if (typeof args.shellId === 'string') return args.shellId;\n if (typeof args.taskId === 'string') return args.taskId;\n if (typeof args.task_id === 'string') return args.task_id;\n if (typeof args.filePath === 'string') return args.filePath;\n if (typeof args.file_path === 'string') return args.file_path;\n if (typeof args.patch === 'string') {\n const match = args.patch.match(/^\\*\\*\\* (?:Update|Add|Delete) File: (.+)$/m);\n if (match) return match[1].trim();\n }\n if (typeof args.notebook_path === 'string') return args.notebook_path;\n if (typeof args.pattern === 'string') return args.pattern;\n if (typeof args.path === 'string') return args.path;\n if (typeof args.agent === 'string') return args.agent;\n if (typeof args.skill === 'string') return args.skill;\n if (typeof args.message === 'string') return args.message;\n if (typeof args.url === 'string') return args.url;\n if (typeof args.query === 'string') return args.query;\n if (typeof args.subject === 'string') return args.subject;\n if (typeof args.old_string === 'string') return args.old_string.slice(0, 60);\n const keys = Object.keys(args);\n if (keys.length > 0) {\n const v = args[keys[0]];\n return typeof v === 'string' ? v : '';\n }\n return '';\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n ToolResult,\n ToolResultArtifacts,\n ToolResultError,\n ToolResultPresentation,\n ToolResultStatus,\n} from '../types/tools.js';\nimport { canonicalToolName } from './aliases.js';\nimport { getPrimaryArg } from './primary-arg.js';\nimport { isFileMutatingTool } from './tool-capabilities.js';\nimport { resolveBookHome } from '../book-home.js';\n\nexport const TOOL_RESULT_MAX_BYTES = 50 * 1024;\n/**\n * What `Read` returns for a file with no lines in it. It lives here because the\n * row a transcript draws for a Read is reconstructed from its text as well as\n * from Read's own metadata, and both have to agree on these words.\n */\nexport const READ_EMPTY_FILE_NOTICE = '[Empty file: 0 lines.]';\n/**\n * Where clipped tool output is saved in full. The loop lets `Read` open each file\n * it clipped a result into, never the whole directory. A function, not a constant: the home\n * is read when a result is clipped, so a BOOK_HOME set after this module loads is honoured.\n */\nfunction toolOutputDirectory(): string {\n return join(resolveBookHome(), 'tool-output');\n}\n\ninterface ToolResultOptions<TData> {\n toolCallId?: string;\n data?: TData;\n presentation?: Partial<ToolResultPresentation>;\n artifacts?: ToolResultArtifacts;\n pagination?: ToolResult['pagination'];\n}\n\ninterface LegacyToolResult {\n version?: 2;\n toolCallId: string;\n status?: ToolResultStatus;\n content?: string;\n output?: string;\n success?: boolean;\n data?: unknown;\n error?: string;\n structuredError?: ToolResultError;\n presentation?: ToolResultPresentation;\n metrics?: ToolResult['metrics'];\n artifacts?: ToolResultArtifacts;\n durationMs?: number;\n retryAttempt?: number;\n fileMutation?: ToolResultArtifacts['fileMutation'];\n fileMutations?: ToolResultArtifacts['fileMutations'];\n fileObservations?: ToolResultArtifacts['fileObservations'];\n eventRef?: string;\n outputPath?: string;\n pagination?: ToolResult['pagination'];\n}\n\nfunction presentationFor(\n content: string,\n override?: Partial<ToolResultPresentation>,\n): ToolResultPresentation {\n return {\n kind: override?.kind ?? 'text',\n summary: override?.summary ?? content.split('\\n')[0]?.trim() ?? '',\n details: override?.details ?? content,\n metadata: override?.metadata,\n target: override?.target,\n };\n}\n\nexport function toolSuccess<TData = unknown>(\n content: string,\n options: ToolResultOptions<TData> = {},\n): ToolResult<TData> {\n return {\n version: 2,\n toolCallId: options.toolCallId ?? '',\n status: 'success',\n content,\n data: options.data,\n presentation: options.presentation ? presentationFor(content, options.presentation) : undefined,\n artifacts: options.artifacts,\n pagination: options.pagination,\n };\n}\n\nexport function toolFailure(\n message: string,\n options: ToolResultOptions<unknown> & {\n code?: string;\n status?: Exclude<ToolResultStatus, 'success'>;\n retryable?: boolean;\n remediation?: string;\n details?: Record<string, unknown>;\n content?: string;\n } = {},\n): ToolResult {\n const error: ToolResultError = {\n code: options.code ?? 'tool_error',\n message,\n retryable: options.retryable ?? false,\n remediation: options.remediation,\n details: options.details,\n };\n const content = options.content ?? '';\n return {\n version: 2,\n toolCallId: options.toolCallId ?? '',\n status: options.status ?? 'error',\n content,\n data: options.data,\n structuredError: error,\n presentation: options.presentation\n ? presentationFor(message, {\n summary: message.split('\\n')[0]?.trim(),\n details: message,\n ...options.presentation,\n })\n : undefined,\n artifacts: options.artifacts,\n pagination: options.pagination,\n };\n}\n\nexport function toolResultSucceeded(result: ToolResult): boolean {\n return result.status === 'success';\n}\n\nexport function toolResultErrorMessage(result: ToolResult): string | undefined {\n return result.structuredError?.message;\n}\n\nfunction rawToolResultModelContent(result: ToolResult): string {\n const content = result.content;\n if (toolResultSucceeded(result)) {\n return content.trim().length === 0 ? '(no output)' : content;\n }\n const remediation = result.structuredError?.remediation;\n const fix = remediation ? `\\nFix: ${remediation}` : '';\n const detail = content ? `\\n${content}` : '';\n return `ERROR [${result.structuredError?.code ?? result.status}]: ${toolResultErrorMessage(result) ?? 'tool failed'}${fix}${detail}`;\n}\n\nexport function toolResultModelContent(result: ToolResult): string {\n if (result.maskedPlaceholder !== undefined) return result.maskedPlaceholder;\n const raw = rawToolResultModelContent(result);\n if (Buffer.byteLength(raw) <= TOOL_RESULT_MAX_BYTES) return raw;\n // Reached only by callers that skipped `boundToolResultOutput`, which is\n // where an agent-loop result is clipped. Both places clip a failure to its\n // head and its tail, through the same helper.\n if (!toolResultSucceeded(result)) {\n const prefix = `ERROR [${result.structuredError?.code ?? result.status}]: `;\n const fix = result.structuredError?.remediation\n ? `\\nFix: ${result.structuredError.remediation}`\n : '';\n const budget = Math.max(\n 1,\n TOOL_RESULT_MAX_BYTES - Buffer.byteLength(prefix) - Buffer.byteLength(fix),\n );\n return `${prefix}${headTailPreview(failureText(result), result.artifacts?.outputPath, budget)}${fix}`;\n }\n return clippedOutputPreview(raw, result.artifacts?.outputPath, TOOL_RESULT_MAX_BYTES);\n}\n\nexport function utf8Prefix(text: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n const bytes = Buffer.from(text);\n if (bytes.byteLength <= maxBytes) return text;\n return bytes\n .subarray(0, maxBytes)\n .toString('utf8')\n .replace(/\\uFFFD$/u, '');\n}\n\nfunction utf8Suffix(text: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n const bytes = Buffer.from(text);\n if (bytes.byteLength <= maxBytes) return text;\n // Advance past any continuation bytes so the tail starts on a character\n // boundary. Cutting inside a 4-byte sequence otherwise strands three\n // continuation bytes, each of which decodes to its own replacement char.\n let start = bytes.byteLength - maxBytes;\n while (start < bytes.byteLength && (bytes[start] & 0xc0) === 0x80) start++;\n return bytes.subarray(start).toString('utf8');\n}\n\n/**\n * How much of a clipped failure's head survives: at most 8 KB, and never more\n * than a fifth of the budget, so the tail always has room to be worth keeping.\n */\nconst FAILURE_HEAD_MAX_BYTES = 8 * 1024;\n\n/** A failure's message and its output, as the two are read when the pair is clipped. */\nfunction failureText(result: ToolResult): string {\n const message = toolResultErrorMessage(result) ?? 'tool failed';\n return result.content ? `${message}\\n${result.content}` : message;\n}\n\n/**\n * A failure is judged on both ends, so a clipped one keeps both. The head\n * carries the error line, whatever framed the output — the Task tool's\n * `Partial result (the child was stopped; nothing below is final):` — and the\n * first diagnostics. The tail carries the verdict a test runner prints last.\n * Clipping to one end loses the other: a non-zero exit puts all of stderr in\n * the message and stdout in the content, and the summary is in the stdout, so a\n * tail-only clip of a large stderr reported the head instead of the verdict\n * (#308).\n */\nfunction headTailPreview(text: string, outputPath: string | undefined, maxBytes: number): string {\n const totalBytes = Buffer.byteLength(text);\n const headWindow = Math.min(FAILURE_HEAD_MAX_BYTES, Math.floor(maxBytes * 0.2));\n const window = utf8Prefix(text, headWindow);\n if (Buffer.byteLength(window) >= totalBytes) return text;\n // End the head on a whole line when one falls inside the kept window; a\n // half-line of output is harder to read than one line fewer.\n const lastBreak = window.lastIndexOf('\\n');\n const head = lastBreak >= 0 ? window.slice(0, lastBreak + 1) : window;\n // The newline that closed that line is not shown — the one that follows the\n // head here is a joiner — so it is part of what the notice has to count.\n const headShown = head.trimEnd();\n const headBytes = Buffer.byteLength(headShown);\n const omittedNotice = (omitted: number): string =>\n outputPath\n ? `[... ${omitted} bytes omitted. Full output: ${outputPath}]`\n : `[... ${omitted} bytes omitted. Full output unavailable.]`;\n // How much sits between the head and the tail is not known until the tail is,\n // but the notice's own width is: the widest count is the whole text, so that\n // notice is the room reserved before the tail is measured. The two newlines\n // that join the three parts are reserved with it, so the result stays inside\n // the budget the caller treats as final.\n const reserve = Buffer.byteLength(omittedNotice(totalBytes)) + 2;\n const tailRoom = maxBytes - headBytes - reserve;\n if (tailRoom <= 0) return [headShown, omittedNotice(totalBytes - headBytes)].join('\\n');\n // `utf8Suffix` lands on a character boundary; skip the rest of the line it\n // landed in so the tail starts on a line of its own.\n const tailWindow = utf8Suffix(text, tailRoom);\n const breakAt = tailWindow.indexOf('\\n');\n const tail = breakAt >= 0 ? tailWindow.slice(breakAt + 1) : tailWindow;\n const notice = omittedNotice(totalBytes - headBytes - Buffer.byteLength(tail));\n return [headShown, notice, tail].join('\\n');\n}\n\nfunction clippedOutputPreview(\n content: string,\n outputPath: string | undefined,\n maxBytes: number,\n): string {\n const notice = outputPath\n ? `\\n\\n[Output truncated at ${maxBytes} bytes. Full output: ${outputPath}]`\n : `\\n\\n[Output truncated at ${maxBytes} bytes. Full output unavailable.]`;\n const previewBudget = Math.max(0, maxBytes - Buffer.byteLength(notice));\n return `${utf8Prefix(content, previewBudget).trimEnd()}${notice}`;\n}\n\n/**\n * Bound every provider-facing tool result at the execution boundary. Complete output is stored in\n * Book's user-local data directory when possible, never in the tracked workspace.\n */\nexport async function boundToolResultOutput(\n input: ToolResult,\n _workspaceRoot: string,\n maxBytes = TOOL_RESULT_MAX_BYTES,\n artifactRoot?: string,\n): Promise<ToolResult> {\n const result = normalizeToolResult(input);\n const modelContent = rawToolResultModelContent(result);\n const modelContentBytes = Buffer.byteLength(modelContent);\n const contentBytes = Buffer.byteLength(result.content);\n const details = result.presentation?.details;\n const detailsBytes = details === undefined ? 0 : Buffer.byteLength(details);\n if (modelContentBytes <= maxBytes && detailsBytes <= maxBytes) return result;\n\n let outputPath = result.artifacts?.outputPath;\n if (!outputPath) {\n // Resolved here, not as a default: most results fit and never spill.\n const outputFile = join(artifactRoot ?? toolOutputDirectory(), `${crypto.randomUUID()}.txt`);\n try {\n await mkdir(dirname(outputFile), { recursive: true });\n await writeFile(\n outputFile,\n detailsBytes > modelContentBytes && details ? details : modelContent,\n 'utf8',\n );\n outputPath = outputFile.replace(/\\\\/g, '/');\n } catch {\n // Clipping must still succeed in read-only or otherwise restricted environments.\n }\n }\n\n const modelOverflow = modelContentBytes > maxBytes;\n const content = toolResultSucceeded(result)\n ? contentBytes > maxBytes\n ? clippedOutputPreview(result.content, outputPath, maxBytes)\n : result.content\n : modelOverflow\n ? ''\n : result.content;\n const errorPrefix = `ERROR [${result.structuredError?.code ?? result.status}]: `;\n // Reserve room for the \"\\nFix: <remediation>\" line appended at render time so\n // the remediation survives instead of being re-clipped off the tail.\n const fixReserve = result.structuredError?.remediation\n ? Buffer.byteLength(`\\nFix: ${result.structuredError.remediation}`)\n : 0;\n const errorBudget = Math.max(1, maxBytes - Buffer.byteLength(errorPrefix) - fixReserve);\n const structuredError = result.structuredError\n ? {\n ...result.structuredError,\n // The message and the output are clipped as one text, so a failure\n // keeps both ends: the error line and any framing at the top, and the\n // verdict at the bottom. The room the `Fix:` line needs is reserved\n // above, so the remediation is never what gets clipped.\n message: modelOverflow\n ? headTailPreview(failureText(result), outputPath, errorBudget)\n : result.structuredError.message,\n }\n : result.structuredError;\n const clippedDetails = details\n ? detailsBytes > maxBytes\n ? // The transcript row reads the same way the model does: a failure's\n // head and tail, not one end of it — the same reason as above.\n toolResultSucceeded(result)\n ? clippedOutputPreview(details, outputPath, maxBytes)\n : headTailPreview(details, outputPath, maxBytes)\n : details\n : toolResultSucceeded(result)\n ? content\n : structuredError?.message;\n\n return {\n ...result,\n content,\n structuredError,\n presentation: result.presentation\n ? { ...result.presentation, details: clippedDetails }\n : result.presentation,\n artifacts: outputPath ? { ...result.artifacts, outputPath } : result.artifacts,\n pagination: {\n ...result.pagination,\n truncated: true,\n omittedBytes: Math.max(\n result.pagination?.omittedBytes ?? 0,\n Math.max(modelContentBytes, contentBytes, detailsBytes) - maxBytes,\n ),\n },\n };\n}\n\nexport function replaceToolResult(\n result: ToolResult,\n patch: {\n status?: ToolResultStatus;\n content?: string;\n error?: ToolResultError;\n presentation?: Partial<ToolResultPresentation>;\n },\n): ToolResult {\n const status = patch.status ?? result.status;\n const content = patch.content ?? result.content;\n return {\n ...result,\n status,\n content,\n // New content replaces what the model read: a placeholder standing in for\n // the previous text would hide the rewrite (a PostToolUse hook's\n // `modifiedOutput`, the plan-approval note) behind the line the tool wrote.\n ...(patch.content !== undefined ? { maskedPlaceholder: undefined } : {}),\n structuredError: patch.error ?? (status === 'success' ? undefined : result.structuredError),\n presentation:\n result.presentation || patch.presentation\n ? presentationFor(content, {\n ...result.presentation,\n ...patch.presentation,\n })\n : undefined,\n };\n}\n\n/** Upgrade persisted pre-V2 results while keeping the runtime and SDK contract V2-only. */\nexport function normalizeToolResult(result: ToolResult | LegacyToolResult): ToolResult {\n const legacy = result as LegacyToolResult;\n const legacySuccess = legacy.success;\n const legacyError = typeof legacy.error === 'string' ? legacy.error : undefined;\n const status: ToolResultStatus = result.status\n ? result.status\n : legacySuccess\n ? 'success'\n : legacyError?.startsWith('SKIPPED')\n ? 'blocked'\n : legacyError?.startsWith('CANCELLED')\n ? 'cancelled'\n : legacyError?.startsWith('Tool timeout')\n ? 'timed_out'\n : 'error';\n const content = result.content ?? legacy.output ?? '';\n const error =\n status === 'success'\n ? undefined\n : {\n ...(result.structuredError ?? {}),\n code: result.structuredError?.code ?? (status === 'blocked' ? 'blocked' : status),\n message: legacyError ?? result.structuredError?.message ?? 'Tool failed',\n retryable: result.structuredError?.retryable ?? status === 'timed_out',\n };\n const artifacts: ToolResultArtifacts | undefined =\n result.artifacts ??\n (legacy.fileMutation ||\n legacy.fileMutations ||\n legacy.fileObservations ||\n legacy.eventRef ||\n legacy.outputPath\n ? {\n fileMutation: legacy.fileMutation,\n fileMutations: legacy.fileMutations,\n fileObservations: legacy.fileObservations,\n eventRef: legacy.eventRef,\n outputPath: legacy.outputPath,\n }\n : undefined);\n const metrics =\n result.metrics ??\n (legacy.durationMs !== undefined || legacy.retryAttempt !== undefined\n ? { durationMs: legacy.durationMs, retryAttempt: legacy.retryAttempt }\n : undefined);\n return {\n version: 2,\n toolCallId: result.toolCallId,\n status,\n content,\n // Masking's placeholder survives a resume, or a restored compaction's tail\n // would come back at full size.\n ...('maskedPlaceholder' in result && result.maskedPlaceholder !== undefined\n ? { maskedPlaceholder: result.maskedPlaceholder }\n : {}),\n data: result.data,\n structuredError: error,\n presentation: result.presentation,\n artifacts,\n metrics,\n pagination: result.pagination,\n };\n}\n\nfunction nonEmptyLines(content: string): number {\n return content.split('\\n').filter((line) => line.trim()).length;\n}\n\n/** A Read row's line metadata: how many lines of the file, and their range when the read started partway in. */\nexport function readLineMetadata(start: number, count: number): string[] {\n // A row counts whole lines: the values reach this from Read's own floor and\n // from a page reconstructed out of its text, and either can be fractional.\n const first = Math.max(1, Math.floor(start));\n const lines = Math.max(0, Math.floor(count));\n if (lines <= 0) return ['empty'];\n const size = lines === 1 ? '1 line' : `${lines} lines`;\n // `121 lines · 1-121` says the same thing twice. The range earns its place only when the read\n // started partway into the file.\n return first > 1 ? [size, `${first}-${first + lines - 1}`] : [size];\n}\n\n/**\n * A Read row's metadata reconstructed from its text, for results that carry none (outlines, and\n * results persisted before tool results had a presentation): the lines of the file it returned\n * and their range, or how many declarations an outline listed. A read that stops early ends with\n * a notice (`[Lines 3-6 of 20 shown. …]`, `[Line 1 (60000 bytes) was cut …]`), which is not a\n * line of the file, and an empty file is nothing but its notice. Read's own results carry exact\n * metadata (readLineMetadata). Without Read's own count, a result persisted by a build that\n * numbered the empty element after a final newline still reads one line long, so a trailing\n * `N: ` is read past — a file ending in a blank line now returns the same text, and its phantom\n * line is gone.\n */\nexport function readResultMetadata(args: Record<string, unknown>, content: string): string[] {\n // An outline lists declarations under a header, not lines of the file.\n if (args.outline === true) {\n const entries = content.split('\\n').filter((line) => /^\\d+: /.test(line)).length;\n return ['outline', entries === 1 ? '1 entry' : `${entries} entries`];\n }\n let body =\n content === READ_EMPTY_FILE_NOTICE ? '' : content.replace(/\\n\\[Lines? \\d[^\\n]*\\]$/, '');\n if (body === content) body = body.replace(/(?:^|\\n)\\d+: $/, '');\n const lineCount = body ? body.split('\\n').length : 0;\n const offset = Number(args.offset ?? 0);\n const start = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;\n return readLineMetadata(start, lineCount);\n}\n\n/**\n * A Grep row's count re-derived from its text, for results that carry none: a\n * result persisted by a build that did not have Grep set its own (#311). Grep\n * itself counts from the data it collected, which is exact where this is not —\n * a context line's text can hold a `12:30` of its own, and a match spanning\n * lines is several lines. In the unit `output_mode` asks for: matches in\n * `content` and `count` mode, files in `files_with_matches`.\n */\nexport function grepResultMetadata(args: Record<string, unknown>, content: string): string {\n const noun = args.output_mode === 'files_with_matches' ? 'file' : 'match';\n if (/^No matches found$/i.test(content.trim()))\n return `0 ${noun === 'file' ? 'files' : 'matches'}`;\n const lines = content.split('\\n');\n let count: number;\n if (noun === 'file') count = lines.filter((line) => line.trim()).length;\n else if (args.output_mode === 'count') {\n count = lines.reduce((total, line) => total + (Number(/:(\\d+)\\s*$/.exec(line)?.[1]) || 0), 0);\n } else count = lines.filter((line) => /:\\d+:/.test(line)).length || nonEmptyLines(content);\n return `${count} ${count === 1 ? noun : noun === 'file' ? 'files' : 'matches'}`;\n}\n\n/** Attach stable UI data while execution still has the tool name and arguments. */\nexport function enrichToolResultPresentation(\n input: ToolResult,\n toolName: string,\n args: Record<string, unknown>,\n): ToolResult {\n const result = normalizeToolResult(input);\n const name = canonicalToolName(toolName);\n const content = result.content;\n const explicitPresentation = result.presentation;\n const target = explicitPresentation?.target ?? (getPrimaryArg(args) || undefined);\n const metadata: string[] = explicitPresentation?.metadata\n ? [...explicitPresentation.metadata]\n : [];\n const inferKind = explicitPresentation?.kind === undefined;\n const inferSummary = explicitPresentation?.summary === undefined;\n const inferMetadata = explicitPresentation?.metadata === undefined;\n let kind: ToolResultPresentation['kind'] = explicitPresentation?.kind ?? 'text';\n let summary = explicitPresentation?.summary ?? content.split('\\n')[0]?.trim() ?? '';\n\n if (isFileMutatingTool(name)) {\n if (inferKind) {\n kind = result.artifacts?.fileMutation ? 'file' : /^@@/m.test(content) ? 'diff' : 'file';\n }\n const mutation = result.artifacts?.fileMutation ?? result.artifacts?.fileMutations?.[0];\n if (mutation) {\n if (inferMetadata) {\n if (mutation.addedLines) metadata.push(`+${mutation.addedLines}`);\n if (mutation.removedLines) metadata.push(`-${mutation.removedLines}`);\n if (!mutation.addedLines && !mutation.removedLines) metadata.push('no changes');\n }\n if (inferSummary) {\n summary = `${mutation.kind === 'create' ? 'Created' : mutation.kind === 'delete' ? 'Deleted' : 'Updated'} ${mutation.filePath}`;\n }\n }\n } else if (name === 'Read') {\n if (inferKind) kind = 'file';\n if (inferMetadata && result.status === 'success')\n metadata.push(...readResultMetadata(args, content));\n if (inferSummary) summary = target ? `Read ${target}` : summary;\n } else if (name === 'Glob') {\n if (inferKind) kind = 'search';\n const count = /^(?:No files found|No matches found)$/i.test(content.trim())\n ? 0\n : nonEmptyLines(content.replace(/\\n\\.\\.\\. \\(truncated[\\s\\S]*$/, ''));\n if (inferMetadata) metadata.push(`${count} ${count === 1 ? 'file' : 'files'}`);\n if (inferSummary) summary = `Found ${count} ${count === 1 ? 'file' : 'files'}`;\n } else if (name === 'Grep') {\n if (inferKind) kind = 'search';\n // Only a search that ran has matches to count; a failed one is a row with\n // the error, the way Read's is.\n if (result.status === 'success') {\n const label = grepResultMetadata(args, content);\n if (inferMetadata) metadata.push(label);\n if (inferSummary) summary = `Found ${label}`;\n }\n } else if (name === 'Bash' || name === 'BashOutput' || name === 'KillShell') {\n if (inferKind) kind = 'command';\n if (inferSummary) summary = target ? `${name}: ${target}` : summary;\n } else if (name === 'GitDiff') {\n if (inferKind) kind = 'diff';\n } else if (name.startsWith('Web') || name === 'ToolSearch' || name.startsWith('SessionHistory')) {\n if (inferKind) kind = name === 'ToolSearch' ? 'search' : 'markdown';\n } else if (name.startsWith('Task') || name === 'TodoWrite') {\n if (inferKind) kind = 'task';\n } else if (name.startsWith('Agent') || name.startsWith('Evidence')) {\n if (inferKind) kind = 'agent';\n }\n\n return {\n ...result,\n presentation: {\n kind,\n summary,\n details: explicitPresentation?.details ?? content,\n metadata,\n target: explicitPresentation?.target ?? result.artifacts?.fileMutation?.filePath ?? target,\n },\n };\n}\n","export const FILE_MUTATING_TOOLS = new Set([\n 'ApplyPatch',\n 'Write',\n 'Edit',\n 'MultiEdit',\n 'NotebookEdit',\n]);\n\nexport function isFileMutatingTool(canonicalName: string): boolean {\n return FILE_MUTATING_TOOLS.has(canonicalName);\n}\n","import { existsSync, lstatSync, realpathSync } from 'fs';\nimport { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'path';\nimport type { ToolResult } from '../types/tools.js';\nimport { reachesUncPath } from '../unc-path.js';\nimport { toolFailure } from './result.js';\n\n/**\n * The path semantics a containment comparison needs.\n *\n * Injectable so a caller whose paths are spelled by another `path` module — the\n * sandbox builder resolves a *test* workspace through `path.win32` or\n * `path.posix` and must judge containment in the same spelling — compares in the\n * same terms it resolved in. `sep` is part of the contract because `..` is only\n * a parent step in the separator of the module that produced the relative path.\n */\nexport interface PathComparison {\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n sep: string;\n}\n\nconst REAL_PATH_COMPARISON: PathComparison = { relative, isAbsolute, sep };\n\n/**\n * Whether `candidate` is `root` or lies outside it — the one containment test\n * the rest of the codebase makes.\n *\n * Note what it does *not* accept: a relative path that merely starts with `..`,\n * such as `..meta`. That is a name in the root's own directory, and treating it\n * as a parent step reads a directory called `..meta` as being outside the tree\n * it is in. `relative` can only produce a real parent step as `..` on its own or\n * as `../…` with the separator of the module that produced it, and the third\n * clause covers a relative path answering to two different drives.\n */\nexport function isOutside(\n root: string,\n candidate: string,\n path: PathComparison = REAL_PATH_COMPARISON,\n): boolean {\n const rel = path.relative(root, candidate);\n return rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel);\n}\n\nfunction nearestExistingPath(inputPath: string): string | null {\n let candidate = inputPath;\n while (!existsSync(candidate)) {\n const parent = dirname(candidate);\n if (parent === candidate) return null;\n candidate = parent;\n }\n return candidate;\n}\n\nexport function resolveWorkspacePath(\n workspaceRoot: string,\n inputPath: string,\n): { filePath: string; canonicalPath: string; relativePath: string } | null {\n const lexicalRoot = resolve(workspaceRoot);\n const filePath = resolve(isAbsolute(inputPath) ? inputPath : resolve(lexicalRoot, inputPath));\n // Containment is decided on the canonical pair — both sides after following links, and in one\n // separator — because one directory has more than one spelling. A root the user typed in 8.3\n // short form (`C:\\Users\\RUNNER~1\\…`) is a different string from the long form `realpath`,\n // a glob walk and ripgrep report, and reading \"outside\" between the two spellings of the *same*\n // directory is what let a `.book/settings.local.json` inside the workspace look like a file\n // somewhere else (#264). The canonical path is the one a read opens and a write lands on, so it\n // is the one that decides, and a link pointing out of a root still resolves outside it.\n const canonicalRoot = canonicalizePath(lexicalRoot);\n const canonicalPath = canonicalizePath(filePath);\n if (isOutside(canonicalRoot, canonicalPath)) return null;\n\n // The spelling shown to the model is relative to the root as written whenever the path was\n // written inside it, so a symlinked spelling keeps naming the file the way the caller reached it\n // (`link/notes.txt`) rather than by its real name.\n const asWrittenInside = !isOutside(lexicalRoot, filePath);\n const rel = asWrittenInside\n ? relative(lexicalRoot, filePath)\n : relative(canonicalRoot, canonicalPath);\n return { filePath, canonicalPath, relativePath: rel.replace(/\\\\/g, '/') };\n}\n\nexport interface ResolvedReadablePath {\n filePath: string;\n canonicalPath: string;\n relativePath: string;\n /**\n * The root that served this path, as the caller spelled it — the same string the file tools\n * glob and ripgrep from, and the one a result label should keep using, since it is the spelling\n * `Read` accepts back.\n */\n root: string;\n /**\n * Whether `root` is the workspace, which is what makes `relativePath` the workspace-relative\n * spelling rather than one relative to some other root.\n */\n inWorkspace: boolean;\n /**\n * Set when the path was served by the memory write root (`PathRoots.memoryWrite`): a `*.md` file\n * inside Book's project memory directory. A write tool must run it through\n * `checkMemoryWrite` before it changes the file.\n */\n memory?: true;\n}\n\n/** The roots a file tool may serve, in the order it tries them. */\nexport interface PathRoots {\n workspaceRoot: string;\n readOnlyRoots?: readonly string[];\n /**\n * Book's project memory directory as a search root (`ToolContext.memorySearchRoot`), for the\n * tools that walk a tree: a path in it is served unless a dot-entry lies between it and the root.\n */\n memorySearchRoot?: string;\n /**\n * Honored `additionalDirectories` (#300), as resolved real roots. Tried after the workspace, and\n * only for an absolute path: a relative path stays workspace-anchored.\n *\n * These are *read* roots. A write tool still resolves against the workspace alone\n * ({@link resolveWorkspacePath}), so widening this list never widens what Write, Edit,\n * MultiEdit, NotebookEdit or ApplyPatch may change.\n */\n additionalRoots?: readonly string[];\n /**\n * Book's project memory directory as a write root (`ToolContext.memoryWrite`). Only\n * {@link resolveMutationPath} reads it, and only for an absolute `*.md` path.\n */\n memoryWrite?: { root: string };\n}\n\n/**\n * Which resolved file the read tools may serve, or `unreachable` when no root contains it — the\n * refusal whose remedy is to widen the roots.\n */\nexport type ReadablePathDetail = { path: ResolvedReadablePath } | { reason: 'unreachable' };\n\n/**\n * Which resolved file the read tools may serve, and why not when they may not.\n *\n * The workspace is tried first, then the read-only roots, then the honored `additionalDirectories`,\n * then the memory search root — the same order {@link resolveReadablePath} has always used, with\n * the extra roots appended. Only an absolute path reaches the roots outside the workspace, so a\n * relative path never re-anchors to one. The root that matched travels with the answer, so a\n * caller labelling or re-searching the file does not have to work out which of the roots it was\n * found under by comparing paths again.\n *\n * A call the tool cannot serve is refused instead of prompted for, and the refusal names the\n * remedy, which is why the answer says so rather than being a bare `null`.\n */\nexport function resolveReadablePathDetail(\n context: PathRoots,\n inputPath: string,\n): ReadablePathDetail {\n const wsMatch = resolveWorkspacePath(context.workspaceRoot, inputPath);\n if (wsMatch) {\n return { path: { ...wsMatch, root: context.workspaceRoot, inWorkspace: true } };\n }\n\n if (!isAbsolute(inputPath)) return { reason: 'unreachable' };\n for (const root of [...(context.readOnlyRoots ?? []), ...(context.additionalRoots ?? [])]) {\n const match = resolveWorkspacePath(root, inputPath);\n if (match) return { path: { ...match, root, inWorkspace: false } };\n }\n const memory = context.memorySearchRoot\n ? resolveMemorySearchPath(context.memorySearchRoot, inputPath)\n : null;\n return memory ? { path: memory } : { reason: 'unreachable' };\n}\n\n/**\n * A path inside the memory search root, unless a dot-entry (`.format`, a backup directory) lies\n * between the root and the file, in either spelling of the path.\n */\nfunction resolveMemorySearchPath(\n memoryRoot: string,\n inputPath: string,\n): ResolvedReadablePath | null {\n const match = resolveWorkspacePath(memoryRoot, inputPath);\n if (!match) return null;\n const fromRoot = relative(canonicalizePath(resolve(memoryRoot)), match.canonicalPath);\n const segments = [...match.relativePath.split('/'), ...fromRoot.split(sep)];\n if (segments.some((segment) => segment.startsWith('.') && segment !== '.' && segment !== '..')) {\n return null;\n }\n return { ...match, root: memoryRoot, inWorkspace: false };\n}\n\n/**\n * The file an `@path` mention may inline (#567): what {@link resolveReadablePathDetail} serves for\n * `absolutePath` from the workspace and the honored `additionalDirectories`, so a mention reaches\n * exactly the files Read opens without a prompt. Read's other roots (the memory directory, clipped\n * tool output) are not a mention's to reach. Refused as `unc`, before anything resolves it, when\n * the path is, or reaches through a link, a UNC location on Windows (`reachesUncPath`). Read's own\n * resolution is unchanged.\n */\nexport function resolveMentionReadablePath(\n roots: Pick<PathRoots, 'workspaceRoot' | 'additionalRoots'>,\n absolutePath: string,\n): ReadablePathDetail | { reason: 'unc' } {\n if (reachesUncPath(absolutePath)) return { reason: 'unc' };\n return resolveReadablePathDetail(\n { workspaceRoot: roots.workspaceRoot, additionalRoots: roots.additionalRoots },\n absolutePath,\n );\n}\n\nexport function resolveReadablePath(\n context: PathRoots,\n inputPath: string,\n): ResolvedReadablePath | null {\n const detail = resolveReadablePathDetail(context, inputPath);\n return 'path' in detail ? detail.path : null;\n}\n\n/**\n * Where a write tool may put a file: the workspace, then the honored directories.\n *\n * A write to an absolute path inside an honored directory is served exactly the way the same write\n * inside the workspace is served — same permission verdict, same `.book/settings.local.json` guard,\n * same fresh-observation requirement — and a relative path stays anchored to the workspace, so a\n * bare `./notes.txt` cannot be re-anchored to a root the caller never named. Read-only roots are\n * deliberately not consulted: they are roots a *read* may cross, not a write target.\n *\n * A match under an honored root reports the **workspace**-relative spelling of its path, not the\n * root-relative one its own resolution produced. That is the spelling the observation ledger is\n * keyed on (`observeFile` derives it from the absolute path, so a file in an honored root and a\n * workspace file of the same name can never collide), and the fresh-observation check looks the\n * write up under it. Reporting the root-relative form instead would make the Read that licensed\n * the write and the write itself consult two different keys, and every honored write would report\n * `file_not_observed`.\n *\n * The memory write root (`PathRoots.memoryWrite`) is the one other place a write may land, and it\n * is narrower than the rest: see {@link resolveMemoryWritePath}. It is tried **first**, not after\n * the workspace: a workspace that holds the memory directory (Book run from `$HOME`) would\n * otherwise serve a memory file through the plain workspace branch and skip the memory guard\n * (secret check, `learnings` ownership, provenance stamp). A path the memory rules refuse falls\n * through to the workspace and the honored roots exactly as before.\n */\nexport function resolveMutationPath(\n context: PathRoots,\n inputPath: string,\n): ResolvedReadablePath | null {\n const memory = context.memoryWrite\n ? resolveMemoryWritePath(context.workspaceRoot, context.memoryWrite.root, inputPath)\n : null;\n if (memory) return memory;\n const inWorkspace = resolveWorkspacePath(context.workspaceRoot, inputPath);\n if (inWorkspace) return { ...inWorkspace, root: context.workspaceRoot, inWorkspace: true };\n if (!isAbsolute(inputPath)) return null;\n for (const root of context.additionalRoots ?? []) {\n const match = resolveWorkspacePath(root, inputPath);\n if (!match) continue;\n const rel = relative(resolve(context.workspaceRoot), match.filePath);\n return { ...match, relativePath: rel.replace(/\\\\/g, '/'), root, inWorkspace: false };\n }\n return null;\n}\n\n/**\n * Whether anything between the memory root and `filePath` (the file included) is a symbolic link,\n * a dangling one too. Checked with `lstat` on the path as written, because the canonical form has\n * already followed a link and would hide it.\n */\nfunction hasLinkBelow(root: string, filePath: string): boolean {\n let current = root;\n for (const segment of relative(root, filePath).split(sep)) {\n current = join(current, segment);\n try {\n if (lstatSync(current).isSymbolicLink()) return true;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n // Nothing exists from here down, so nothing below can be a link.\n if (code === 'ENOENT' || code === 'ENOTDIR') return false;\n return true;\n }\n }\n return false;\n}\n\n/**\n * The memory write root's resolution: an **absolute** path to a `*.md` file inside the memory\n * directory, with no dot-segment below the root (`.inbox/`, `.git/`) and no symbolic link anywhere\n * on the way down. A relative path never anchors here, so a bare `notes.md` stays a workspace file.\n *\n * `relativePath` is the workspace-relative spelling, as for an honored root, so the observation\n * ledger's keys line up with a Read of the same file.\n */\nfunction resolveMemoryWritePath(\n workspaceRoot: string,\n memoryRoot: string,\n inputPath: string,\n): ResolvedReadablePath | null {\n if (!isAbsolute(inputPath)) return null;\n const filePath = resolve(inputPath);\n if (!basename(filePath).toLowerCase().endsWith('.md')) return null;\n const lexicalRoot = resolve(memoryRoot);\n const canonicalRoot = canonicalizePath(lexicalRoot);\n const canonicalPath = canonicalizePath(filePath);\n if (canonicalPath === canonicalRoot || isOutside(canonicalRoot, canonicalPath)) return null;\n // The path as written must itself sit under the root (in either spelling of it), or a link above\n // the root would be what carried it there.\n const writtenRoot = [lexicalRoot, canonicalRoot].find((root) => !isOutside(root, filePath));\n if (!writtenRoot) return null;\n const below = relative(writtenRoot, filePath).split(sep);\n if (below.some((segment) => segment.startsWith('.'))) return null;\n if (\n relative(canonicalRoot, canonicalPath)\n .split(sep)\n .some((s) => s.startsWith('.'))\n )\n return null;\n if (hasLinkBelow(writtenRoot, filePath)) return null;\n const rel = relative(resolve(workspaceRoot), filePath);\n return {\n filePath,\n canonicalPath,\n relativePath: rel.replace(/\\\\/g, '/'),\n root: memoryRoot,\n inWorkspace: false,\n memory: true,\n };\n}\n\n/**\n * How a mutation's file is named in its summary (the tool row and the diff): the workspace-relative\n * path, except that a memory file is `memory/<name>.md`, relative to the memory directory — its\n * workspace-relative spelling is `../home/.book/projects/<slug>/memory/x.md`.\n */\nexport function mutationDisplayPath(resolved: ResolvedReadablePath): string {\n if (!resolved.memory) return resolved.relativePath;\n return memoryDisplayName(resolved.root, resolved.canonicalPath);\n}\n\nfunction memoryDisplayName(memoryRoot: string, canonicalPath: string): string {\n const below = relative(canonicalizePath(resolve(memoryRoot)), canonicalPath);\n return `memory/${below.split(sep).join('/')}`;\n}\n\n/**\n * How the Read row names a file: `memory/<name>.md` when the file lies in the project memory\n * directory, otherwise `undefined` (the row keeps the path the model wrote).\n *\n * Decided from the memory directory itself rather than from `resolved.memory`: Read reaches memory\n * through `readOnlyRoots`, or through the workspace when Book runs from a directory that holds it,\n * and neither sets that flag.\n */\nexport function memoryReadDisplayPath(\n roots: Pick<PathRoots, 'memorySearchRoot' | 'memoryWrite'>,\n resolved: ResolvedReadablePath,\n): string | undefined {\n const memoryRoot = roots.memorySearchRoot ?? roots.memoryWrite?.root;\n if (!memoryRoot) return undefined;\n const canonicalRoot = canonicalizePath(resolve(memoryRoot));\n if (\n resolved.canonicalPath === canonicalRoot ||\n isOutside(canonicalRoot, resolved.canonicalPath)\n ) {\n return undefined;\n }\n return memoryDisplayName(memoryRoot, resolved.canonicalPath);\n}\n\n/**\n * How a file is named in text the **model** reads (an error, a `Created …` line, a diff header).\n *\n * This is deliberately not {@link mutationDisplayPath}. That one is for the TUI row, where\n * `memory/<name>.md` is short and unambiguous. The model would take `memory/x.md` as relative to\n * the workspace and read the wrong file, so a memory file is named by its absolute path — the path\n * it can pass straight back to Read — and its workspace-relative spelling\n * (`../home/.book/projects/<slug>/memory/x.md`) is never shown. A workspace file keeps\n * `relativePath`. On Windows the absolute path stays native, as the model passes native paths there.\n */\nexport function messagePathFor(resolved: ResolvedReadablePath): string {\n return resolved.memory ? resolved.filePath : resolved.relativePath;\n}\n\n/**\n * The refusal a read tool gives a path no root serves.\n *\n * The message names the directory list as well as the workspace: after #300 the workspace is not\n * the only root a read may cross, so \"outside workspace\" alone sent the model looking for a rule\n * that would never work. The code is unchanged, so a caller that classifies refusals by code\n * (the agent loop's streak remedy, the managed-child notice) is unaffected.\n */\nexport function pathOutsideWorkspaceResult(inputPath: unknown): ToolResult {\n return toolFailure(`Path outside workspace and additionalDirectories: ${inputPath}`, {\n code: 'path_outside_workspace',\n remediation:\n 'Add its directory to additionalDirectories, or start Book in a directory that contains it. ' +\n 'No permission rule and no permission mode can change which roots a read may cross.',\n });\n}\n\n/** The workspace root after following links, or the root as given when it cannot be resolved. */\nexport function realWorkspaceRoot(root: string): string {\n try {\n return realpathSync.native(root);\n } catch {\n return resolve(root);\n }\n}\n\n/**\n * A path with its links followed, the way {@link resolveWorkspacePath} canonicalises one: the\n * nearest existing ancestor is resolved and the rest of the path re-appended, so a path that\n * does not exist yet still comes back through a link above it.\n *\n * `realWorkspaceRoot` alone is not enough for a comparison against a *file* path — a read target\n * that has been deleted, or one named before it is written, would fall back to its lexical form\n * and quietly fail to see through the link that reaches it.\n */\nexport function canonicalizePath(inputPath: string): string {\n const absolute = resolve(inputPath);\n const existing = nearestExistingPath(absolute);\n if (!existing) return absolute;\n try {\n return resolve(realpathSync.native(existing), relative(existing, absolute));\n } catch {\n return absolute;\n }\n}\n\n/**\n * Whether a canonical path is a root or under it, both sides in one canonical form.\n *\n * The comparison every \"is this path inside that root\" test outside a resolution makes: which\n * root served a resolved file, whether a Glob label belongs to the workspace, which root a\n * guarded read was served by. The path arrives canonical — from `realpath`, a glob walk or\n * ripgrep, or from {@link resolveWorkspacePath} — so the root has to be put in that form before\n * the two are compared. An as-given root and a canonical path are two spellings of one directory at\n * best, and two unrelated ones when the root was typed in 8.3 short form.\n */\nexport function isUnderRoot(canonicalPath: string, root: string): boolean {\n return !isOutside(canonicalizePath(root), canonicalPath);\n}\n\n/**\n * A path in the one spelling two *strings* are compared in, when neither is touched on disk: the\n * separator is `/`, and on Windows the case is folded (the same fold `isBookLocalSettingsPath`\n * makes, since NTFS answers to any case).\n *\n * `\\` is a separator only on Windows. On POSIX it is an ordinary file-name character, and reading\n * it as a separator would let `/home/u/memory\\a.md` — a file named `memory\\a.md` in `/home/u` —\n * pass for one inside `/home/u/memory`. The platform is a parameter so that the Windows spelling is\n * judged on any host.\n */\nexport function comparablePath(\n value: string,\n platform: NodeJS.Platform = process.platform,\n): string {\n if (platform !== 'win32') return value;\n return value.replaceAll('\\\\', '/').toLowerCase();\n}\n\n/**\n * Whether a path has the *shape* of a root's Book-local settings file or directory, whatever root\n * is being asked about: the last segment is `.book`, or `settings.local.json` under one.\n *\n * The cheap half of {@link isBookLocalSettingsPath}, and the half worth running for every file a\n * Grep lists, because the rest of that answer costs a `realpath` per root. Sound rather than\n * exact — a `.book` somewhere below a root has the shape without being that root's — which is\n * what a prefilter has to be.\n */\nfunction hasBookLocalSettingsShape(\n canonicalPath: string,\n options: { directory?: boolean },\n): boolean {\n const parts = canonicalPath.toLowerCase().split(/[\\\\/]/);\n if (parts[parts.length - 1] === '.book') return options.directory === true;\n return parts[parts.length - 2] === '.book' && parts[parts.length - 1] === 'settings.local.json';\n}\n\n/**\n * Whether a path is Book's project-local settings file, `<root>/.book/settings.local.json`, which\n * can hold an API key; with `directory`, also the `.book` directory that holds it.\n *\n * Both sides are put in one canonical form before they are compared, and compared\n * case-insensitively on every platform: on a case-sensitive file system that only makes the check\n * stricter, while a case-insensitive Linux mount (WSL on /mnt/c) serves any spelling from the same\n * file. The root is canonicalized because it is the side a caller spells freely — 8.3 short form,\n * a lowercase drive letter, forward slashes — and the path because this is the guard that leaked\n * an API key when a root's spelling and a resolved path's spelling disagreed, and a check that\n * silently stops matching when a caller hands it a path in another form is the same failure with\n * a smaller blast radius. Callers that check every file a Grep listed run\n * {@link hasBookLocalSettingsShape} first, which is what keeps this off the per-file path.\n */\nexport function isBookLocalSettingsPath(\n path: string,\n root: string,\n options: { directory?: boolean } = {},\n): boolean {\n if (!hasBookLocalSettingsShape(path, options)) return false;\n const target = canonicalizePath(path).toLowerCase();\n const bookDir = join(canonicalizePath(root), '.book').toLowerCase();\n return (\n target === join(bookDir, 'settings.local.json') ||\n (options.directory === true && target === bookDir)\n );\n}\n\n/**\n * Whether a path is Book's own local settings under *any* root a read may cross.\n *\n * After #300 a read can cross a directory the workspace does not contain, and that directory can\n * carry its own `.book/settings.local.json` with an API key in it — the same file, with the same\n * reason to stay closed, reached by a path the workspace-root check would not recognize. One\n * function so the read tools and the permission scope agree on which paths are guarded. The shape\n * test comes first, so the roots are only put in canonical form for a file that could be one,\n * which is what keeps this free for the per-file call a Grep makes.\n */\nexport function isBookLocalSettingsUnderRoots(\n path: string,\n roots: PathRoots,\n options: { directory?: boolean } = {},\n): boolean {\n if (!hasBookLocalSettingsShape(path, options)) return false;\n return [\n roots.workspaceRoot,\n ...(roots.readOnlyRoots ?? []),\n ...(roots.additionalRoots ?? []),\n ].some((root) => isBookLocalSettingsPath(path, root, options));\n}\n"],"mappings":"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","names":["dirname","join","dirname","join"]}
@@ -9,4 +9,4 @@ function resolveBookHome(env = process.env, systemHome = homedir()) {
9
9
  export {
10
10
  resolveBookHome
11
11
  };
12
- //# sourceMappingURL=chunk-24AXE6SP.js.map
12
+ //# sourceMappingURL=chunk-ZPSZZUWF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/book-home.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Resolve Book's user-global state root, allowing evaluation runs to isolate it explicitly. */\nexport function resolveBookHome(\n env: NodeJS.ProcessEnv = process.env,\n systemHome = homedir(),\n): string {\n const configured = env.BOOK_HOME?.trim();\n return configured ? resolve(configured) : join(systemHome, '.book');\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,MAAM,eAAe;AAGvB,SAAS,gBACd,MAAyB,QAAQ,KACjC,aAAa,QAAQ,GACb;AACR,QAAM,aAAa,IAAI,WAAW,KAAK;AACvC,SAAO,aAAa,QAAQ,UAAU,IAAI,KAAK,YAAY,OAAO;AACpE;","names":[]}
@@ -9,10 +9,12 @@ import {
9
9
  persistProjectCommandChoice,
10
10
  projectCommandFingerprint,
11
11
  shellSafeCommandName
12
- } from "./chunk-5KLVH3PY.js";
13
- import "./chunk-DGMQEI6Y.js";
14
- import "./chunk-574C6MQR.js";
15
- import "./chunk-24AXE6SP.js";
12
+ } from "./chunk-SWPDKLNM.js";
13
+ import "./chunk-2AAOF5B5.js";
14
+ import "./chunk-HFB74CLM.js";
15
+ import "./chunk-REASC2QG.js";
16
+ import "./chunk-IDYWN37T.js";
17
+ import "./chunk-ZPSZZUWF.js";
16
18
  export {
17
19
  ProjectCommandApprovalError,
18
20
  assertProjectCommandApproved,
@@ -25,4 +27,4 @@ export {
25
27
  projectCommandFingerprint,
26
28
  shellSafeCommandName
27
29
  };
28
- //# sourceMappingURL=command-approvals-6Y57VCWZ.js.map
30
+ //# sourceMappingURL=command-approvals-ZRY66LCW.js.map
@@ -0,0 +1,41 @@
1
+ import {
2
+ AgentContextCache,
3
+ buildMessages,
4
+ buildSystemPrompt,
5
+ buildSystemPromptZones
6
+ } from "./chunk-FPR7SKIE.js";
7
+ import "./chunk-N2G4Q7ME.js";
8
+ import "./chunk-XJRJVXJR.js";
9
+ import "./chunk-AHC7OU75.js";
10
+ import "./chunk-DNFQTSMJ.js";
11
+ import "./chunk-UCFJ43OS.js";
12
+ import "./chunk-HOZXKOE3.js";
13
+ import "./chunk-EFXVSQ5J.js";
14
+ import "./chunk-62EEAUID.js";
15
+ import "./chunk-AJZWLKLQ.js";
16
+ import "./chunk-KXAYUIKP.js";
17
+ import "./chunk-SWPDKLNM.js";
18
+ import "./chunk-2AAOF5B5.js";
19
+ import "./chunk-W7SLYS3M.js";
20
+ import "./chunk-32WBCUAN.js";
21
+ import "./chunk-RT5SATF7.js";
22
+ import "./chunk-XHAO3K7I.js";
23
+ import "./chunk-SBTU5BEC.js";
24
+ import "./chunk-4DKHLZRI.js";
25
+ import "./chunk-OJBMDM5H.js";
26
+ import "./chunk-YIL6HTJX.js";
27
+ import "./chunk-EEX6QNXV.js";
28
+ import "./chunk-MV5PQNIA.js";
29
+ import "./chunk-B5FC3MYZ.js";
30
+ import "./chunk-WV3O5PGX.js";
31
+ import "./chunk-HFB74CLM.js";
32
+ import "./chunk-REASC2QG.js";
33
+ import "./chunk-IDYWN37T.js";
34
+ import "./chunk-ZPSZZUWF.js";
35
+ export {
36
+ AgentContextCache,
37
+ buildMessages,
38
+ buildSystemPrompt,
39
+ buildSystemPromptZones
40
+ };
41
+ //# sourceMappingURL=context-C3RAP6K5.js.map
@@ -6,10 +6,11 @@ import {
6
6
  partitionProjectHooks,
7
7
  persistProjectHookChoice,
8
8
  renderUntrustedText
9
- } from "./chunk-ANSJPYGI.js";
10
- import "./chunk-DGMQEI6Y.js";
11
- import "./chunk-574C6MQR.js";
12
- import "./chunk-24AXE6SP.js";
9
+ } from "./chunk-WV3O5PGX.js";
10
+ import "./chunk-HFB74CLM.js";
11
+ import "./chunk-REASC2QG.js";
12
+ import "./chunk-IDYWN37T.js";
13
+ import "./chunk-ZPSZZUWF.js";
13
14
  export {
14
15
  collectDeclaredHooks,
15
16
  describeDeclaredHook,
@@ -19,4 +20,4 @@ export {
19
20
  persistProjectHookChoice,
20
21
  renderUntrustedText
21
22
  };
22
- //# sourceMappingURL=hook-approvals-CTUFKEJT.js.map
23
+ //# sourceMappingURL=hook-approvals-DPQD5H65.js.map