@letrquan/book 0.3.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 (148) hide show
  1. package/CHANGELOG.md +1024 -0
  2. package/README.md +6 -3
  3. package/dist/{additional-roots-H7UNAEHS.js → additional-roots-VAWRQ625.js} +8 -7
  4. package/dist/{app-GE4OHJOC.js → app-J7BVPSUH.js} +5397 -2953
  5. package/dist/app-J7BVPSUH.js.map +1 -0
  6. package/dist/chunk-32WBCUAN.js +660 -0
  7. package/dist/chunk-32WBCUAN.js.map +1 -0
  8. package/dist/{chunk-525CAZPY.js → chunk-4DKHLZRI.js} +6 -6
  9. package/dist/{chunk-4AS7LKKQ.js → chunk-62EEAUID.js} +2 -2
  10. package/dist/{chunk-AQEQX4FF.js → chunk-AHC7OU75.js} +385 -55
  11. package/dist/chunk-AHC7OU75.js.map +1 -0
  12. package/dist/{chunk-EFJ2NH6E.js → chunk-AJZWLKLQ.js} +63 -14
  13. package/dist/chunk-AJZWLKLQ.js.map +1 -0
  14. package/dist/chunk-B5FC3MYZ.js +395 -0
  15. package/dist/chunk-B5FC3MYZ.js.map +1 -0
  16. package/dist/{chunk-67UZEWWF.js → chunk-DNFQTSMJ.js} +53 -11
  17. package/dist/chunk-DNFQTSMJ.js.map +1 -0
  18. package/dist/{chunk-TLX3H2CL.js → chunk-E5WLQSKA.js} +231 -40
  19. package/dist/chunk-E5WLQSKA.js.map +1 -0
  20. package/dist/chunk-ECLC4CIJ.js +1181 -0
  21. package/dist/chunk-ECLC4CIJ.js.map +1 -0
  22. package/dist/chunk-EEX6QNXV.js +127 -0
  23. package/dist/chunk-EEX6QNXV.js.map +1 -0
  24. package/dist/chunk-EFXVSQ5J.js +931 -0
  25. package/dist/chunk-EFXVSQ5J.js.map +1 -0
  26. package/dist/{chunk-RFJ6Q6LE.js → chunk-ENLB7NYV.js} +577 -164
  27. package/dist/chunk-ENLB7NYV.js.map +1 -0
  28. package/dist/{chunk-IME7R2UI.js → chunk-FPR7SKIE.js} +1268 -883
  29. package/dist/chunk-FPR7SKIE.js.map +1 -0
  30. package/dist/{chunk-VPNLCMVR.js → chunk-HFB74CLM.js} +13 -5
  31. package/dist/chunk-HFB74CLM.js.map +1 -0
  32. package/dist/{chunk-5RDRAO4B.js → chunk-HOZXKOE3.js} +37 -1
  33. package/dist/chunk-HOZXKOE3.js.map +1 -0
  34. package/dist/chunk-IDYWN37T.js +36 -0
  35. package/dist/chunk-IDYWN37T.js.map +1 -0
  36. package/dist/{chunk-YC6W4EJH.js → chunk-JGF5HLSI.js} +193 -75
  37. package/dist/chunk-JGF5HLSI.js.map +1 -0
  38. package/dist/{chunk-5YHTKWJN.js → chunk-KDNWMKXV.js} +2447 -907
  39. package/dist/chunk-KDNWMKXV.js.map +1 -0
  40. package/dist/{chunk-Y2VHN7TC.js → chunk-MEOB2DZA.js} +11 -1
  41. package/dist/chunk-MEOB2DZA.js.map +1 -0
  42. package/dist/{chunk-KBQS35OV.js → chunk-MV5PQNIA.js} +2 -2
  43. package/dist/chunk-N2G4Q7ME.js +2672 -0
  44. package/dist/chunk-N2G4Q7ME.js.map +1 -0
  45. package/dist/{chunk-62QL5QUP.js → chunk-OJBMDM5H.js} +17 -1
  46. package/dist/chunk-OJBMDM5H.js.map +1 -0
  47. package/dist/{chunk-56SIEMDX.js → chunk-PTDC7ZUP.js} +2 -2
  48. package/dist/{chunk-X4NJT436.js → chunk-REASC2QG.js} +67 -28
  49. package/dist/chunk-REASC2QG.js.map +1 -0
  50. package/dist/chunk-SBTU5BEC.js +114 -0
  51. package/dist/chunk-SBTU5BEC.js.map +1 -0
  52. package/dist/{chunk-HQI7NYGX.js → chunk-SWPDKLNM.js} +2 -2
  53. package/dist/{chunk-W3XFHEF4.js → chunk-UCFJ43OS.js} +140 -28
  54. package/dist/chunk-UCFJ43OS.js.map +1 -0
  55. package/dist/{chunk-QYBD22H5.js → chunk-W7SLYS3M.js} +80 -16
  56. package/dist/chunk-W7SLYS3M.js.map +1 -0
  57. package/dist/{chunk-SOW4WRQP.js → chunk-WV3O5PGX.js} +3 -3
  58. package/dist/{chunk-JTPOI3KU.js → chunk-XJRJVXJR.js} +262 -38
  59. package/dist/chunk-XJRJVXJR.js.map +1 -0
  60. package/dist/{chunk-224FJZLH.js → chunk-YIL6HTJX.js} +228 -5
  61. package/dist/chunk-YIL6HTJX.js.map +1 -0
  62. package/dist/{command-approvals-VBIU6BLM.js → command-approvals-ZRY66LCW.js} +5 -4
  63. package/dist/context-C3RAP6K5.js +41 -0
  64. package/dist/{hook-approvals-ZVT4V7IM.js → hook-approvals-DPQD5H65.js} +5 -4
  65. package/dist/index.js +657 -754
  66. package/dist/index.js.map +1 -1
  67. package/dist/{interactive-assets-T4I4SEHU.js → interactive-assets-73AQO5WP.js} +11 -6
  68. package/dist/{interactive-assets-T4I4SEHU.js.map → interactive-assets-73AQO5WP.js.map} +1 -1
  69. package/dist/job-runner.js +10 -3
  70. package/dist/job-runner.js.map +1 -1
  71. package/dist/job-supervisor.js +13 -4
  72. package/dist/job-supervisor.js.map +1 -1
  73. package/dist/loader-DQNPDKOX.js +27 -0
  74. package/dist/{mcp-approvals-HJ2VDYMC.js → mcp-approvals-27LJYADQ.js} +5 -4
  75. package/dist/{mcp-config-RNB5LEVE.js → mcp-config-GXSODNCM.js} +3 -2
  76. package/dist/memory-store-I6Z54VUN.js +51 -0
  77. package/dist/{model-window-store-MTSQ5JDJ.js → model-window-store-6VKXXTPJ.js} +4 -3
  78. package/dist/{openai-compatible-2OB54YST.js → openai-compatible-IPUNBJ53.js} +5 -3
  79. package/dist/{permission-approvals-YLV6UC5W.js → permission-approvals-Q5IMFEZI.js} +5 -4
  80. package/dist/{permissions-DHOKF3W2.js → permissions-7Y35X6HZ.js} +26 -9
  81. package/dist/provider-approvals-5AGH3RVT.js +76 -0
  82. package/dist/{sandbox-WRYTZH3D.js → sandbox-N2RY72GN.js} +6 -5
  83. package/dist/{scrollback-35ME2GFX.js → scrollback-GD76GNM4.js} +36 -28
  84. package/dist/scrollback-GD76GNM4.js.map +1 -0
  85. package/dist/sdk.d.ts +876 -64
  86. package/dist/sdk.js +57 -37
  87. package/dist/sdk.js.map +1 -1
  88. package/dist/settings-loader-KDBH45AQ.js +43 -0
  89. package/dist/{settings-removed-JWWPT4WA.js → settings-removed-3ATWJEF3.js} +2 -2
  90. package/dist/shell-selection-Z5WORZU4.js +31 -0
  91. package/dist/shell-selection-Z5WORZU4.js.map +1 -0
  92. package/package.json +5 -5
  93. package/dist/app-GE4OHJOC.js.map +0 -1
  94. package/dist/chunk-224FJZLH.js.map +0 -1
  95. package/dist/chunk-5RDRAO4B.js.map +0 -1
  96. package/dist/chunk-5YHTKWJN.js.map +0 -1
  97. package/dist/chunk-62QL5QUP.js.map +0 -1
  98. package/dist/chunk-67UZEWWF.js.map +0 -1
  99. package/dist/chunk-6OQQOMZR.js +0 -785
  100. package/dist/chunk-6OQQOMZR.js.map +0 -1
  101. package/dist/chunk-AQEQX4FF.js.map +0 -1
  102. package/dist/chunk-C7ADUNPR.js +0 -273
  103. package/dist/chunk-C7ADUNPR.js.map +0 -1
  104. package/dist/chunk-EFJ2NH6E.js.map +0 -1
  105. package/dist/chunk-IME7R2UI.js.map +0 -1
  106. package/dist/chunk-J62VZYOB.js +0 -1339
  107. package/dist/chunk-J62VZYOB.js.map +0 -1
  108. package/dist/chunk-JTPOI3KU.js.map +0 -1
  109. package/dist/chunk-QYBD22H5.js.map +0 -1
  110. package/dist/chunk-RFJ6Q6LE.js.map +0 -1
  111. package/dist/chunk-S4JMID4G.js +0 -259
  112. package/dist/chunk-S4JMID4G.js.map +0 -1
  113. package/dist/chunk-TLX3H2CL.js.map +0 -1
  114. package/dist/chunk-VPNLCMVR.js.map +0 -1
  115. package/dist/chunk-W3XFHEF4.js.map +0 -1
  116. package/dist/chunk-X4NJT436.js.map +0 -1
  117. package/dist/chunk-X66I2NQO.js +0 -137
  118. package/dist/chunk-X66I2NQO.js.map +0 -1
  119. package/dist/chunk-Y2VHN7TC.js.map +0 -1
  120. package/dist/chunk-YC6W4EJH.js.map +0 -1
  121. package/dist/context-CBOBDY5Q.js +0 -38
  122. package/dist/loader-S77OVTHH.js +0 -18
  123. package/dist/memory-store-DZUT5HUH.js +0 -75
  124. package/dist/scrollback-35ME2GFX.js.map +0 -1
  125. package/dist/settings-loader-JUNPGAAM.js +0 -35
  126. package/dist/shell-selection-CDHGJ4GC.js +0 -17
  127. /package/dist/{additional-roots-H7UNAEHS.js.map → additional-roots-VAWRQ625.js.map} +0 -0
  128. /package/dist/{chunk-525CAZPY.js.map → chunk-4DKHLZRI.js.map} +0 -0
  129. /package/dist/{chunk-4AS7LKKQ.js.map → chunk-62EEAUID.js.map} +0 -0
  130. /package/dist/{chunk-KBQS35OV.js.map → chunk-MV5PQNIA.js.map} +0 -0
  131. /package/dist/{chunk-56SIEMDX.js.map → chunk-PTDC7ZUP.js.map} +0 -0
  132. /package/dist/{chunk-HQI7NYGX.js.map → chunk-SWPDKLNM.js.map} +0 -0
  133. /package/dist/{chunk-SOW4WRQP.js.map → chunk-WV3O5PGX.js.map} +0 -0
  134. /package/dist/{command-approvals-VBIU6BLM.js.map → command-approvals-ZRY66LCW.js.map} +0 -0
  135. /package/dist/{context-CBOBDY5Q.js.map → context-C3RAP6K5.js.map} +0 -0
  136. /package/dist/{hook-approvals-ZVT4V7IM.js.map → hook-approvals-DPQD5H65.js.map} +0 -0
  137. /package/dist/{loader-S77OVTHH.js.map → loader-DQNPDKOX.js.map} +0 -0
  138. /package/dist/{mcp-approvals-HJ2VDYMC.js.map → mcp-approvals-27LJYADQ.js.map} +0 -0
  139. /package/dist/{mcp-config-RNB5LEVE.js.map → mcp-config-GXSODNCM.js.map} +0 -0
  140. /package/dist/{memory-store-DZUT5HUH.js.map → memory-store-I6Z54VUN.js.map} +0 -0
  141. /package/dist/{model-window-store-MTSQ5JDJ.js.map → model-window-store-6VKXXTPJ.js.map} +0 -0
  142. /package/dist/{openai-compatible-2OB54YST.js.map → openai-compatible-IPUNBJ53.js.map} +0 -0
  143. /package/dist/{permission-approvals-YLV6UC5W.js.map → permission-approvals-Q5IMFEZI.js.map} +0 -0
  144. /package/dist/{permissions-DHOKF3W2.js.map → permissions-7Y35X6HZ.js.map} +0 -0
  145. /package/dist/{sandbox-WRYTZH3D.js.map → provider-approvals-5AGH3RVT.js.map} +0 -0
  146. /package/dist/{settings-loader-JUNPGAAM.js.map → sandbox-N2RY72GN.js.map} +0 -0
  147. /package/dist/{settings-removed-JWWPT4WA.js.map → settings-loader-KDBH45AQ.js.map} +0 -0
  148. /package/dist/{shell-selection-CDHGJ4GC.js.map → settings-removed-3ATWJEF3.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/memory-store.ts","../src/secret-detect.ts"],"sourcesContent":["import {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from 'fs';\nimport { createHash } from 'crypto';\nimport { basename, isAbsolute, join, relative, resolve } from 'path';\nimport { parseFrontmatter } from './frontmatter.js';\nimport { writeFileAtomic } from './settings-repository.js';\nimport { permissionRuleMatchesCall } from './permissions.js';\nimport { resolveBookHome } from './book-home.js';\nimport { looksLikeSecretOrUnfit } from './secret-detect.js';\nimport type { SessionDriver } from './types/runtime.js';\n\nexport const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const;\nexport type MemoryType = (typeof MEMORY_TYPES)[number];\nexport type MemoryStatus = 'approved' | 'pending' | 'discarded' | 'superseded';\nexport type MemoryOrigin = 'model-tool' | 'extraction' | 'user-text';\n\nexport const MAX_BODY_CHARS = 1600;\n\nexport function sanitizeMemorySlug(rawSlug: string): string | null {\n const cleaned = rawSlug\n .replace(/[\\x00-\\x1f\\x7f]/g, '')\n .replace(/[/\\\\]/g, '')\n // Index entries are parsed by their `](file)` link; these must not appear in a file name.\n .replace(/[[\\]()]/g, '')\n .trim()\n .replace(/^\\.+/, '')\n .trim();\n return cleaned.length > 0 ? cleaned : null;\n}\n\n/**\n * The last segment of a slug. Both separators are split on, so a Windows-style\n * `C:\\notes\\memory.md` names its file rather than being pasted onto `dir`.\n */\nfunction lastSlugSegment(raw: string): string {\n return raw.split(/[/\\\\]/).pop() ?? '';\n}\n\n/**\n * Resolve a raw slug to the one file it names inside the memory directory.\n *\n * The last path segment is taken first, so a path-like slug (`memory/build-cmd.md`,\n * an absolute path) names its file instead of being pasted onto `dir`. This is the\n * only place a slug is sanitized: every caller passes raw model or user input and\n * gets back a resolved filename, or the reason it cannot be used.\n */\nexport function memoryFileForSlug(\n dir: string,\n raw: string,\n): { filename: string; target: string } | { error: string } {\n const cleanSlug = sanitizeMemorySlug(lastSlugSegment(raw.trim()));\n if (!cleanSlug) return { error: 'Invalid memory slug.' };\n const filename = cleanSlug.toLowerCase().endsWith('.md') ? cleanSlug : `${cleanSlug}.md`;\n if (filename.toLowerCase() === INDEX_FILE.toLowerCase()) {\n return { error: 'Target filename cannot be the index file.' };\n }\n const target = join(dir, filename);\n const rel = relative(dir, target);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {\n return { error: 'Target path must stay inside the project memory directory.' };\n }\n return { filename, target };\n}\n\n/** Whether a permission rule list names MemorySave (a bare `MemorySave` or a pattern on it). */\nexport function rulesNameMemorySave(rules: readonly string[]): boolean {\n return rules.some((rule) =>\n permissionRuleMatchesCall(rule, { id: 'memory', name: 'MemorySave', arguments: {} }),\n );\n}\n\nexport function isMemorySaveAvailable(settings?: {\n memory?: { enabled?: boolean; autoSave?: boolean };\n}): boolean {\n if (!settings?.memory) return true;\n return settings.memory.enabled !== false && settings.memory.autoSave !== false;\n}\n\n/**\n * What the model may write to memory in this session — the one rule the catalog, the prompt, the\n * tool and `/memory status` all read:\n *\n * - `none`: the settings turn model writes off.\n * - `learnings`: another agent drives the session. Its task prompt is that agent's per-task\n * contract — saving from it is how a spec's \"Ground rules\" became permanent repository memory —\n * but what the run learned by doing the work (a decision and its reason, an outcome with a\n * lesson, a verified fact) is worth keeping. So only `DELEGATED_MEMORY_TYPES`, marked\n * `driver: agent`, and only delegated entries may be changed or deleted.\n * - `full`: a person drives it.\n */\nexport type MemoryWriteScope = 'none' | 'learnings' | 'full';\n\nexport function memoryWriteScope(config: {\n settings?: { memory?: { enabled?: boolean; autoSave?: boolean } };\n sessionDriver?: SessionDriver;\n}): MemoryWriteScope {\n if (!isMemorySaveAvailable(config.settings)) return 'none';\n return config.sessionDriver === 'agent' ? 'learnings' : 'full';\n}\n\nexport function canModelWriteMemory(config: Parameters<typeof memoryWriteScope>[0]): boolean {\n return memoryWriteScope(config) !== 'none';\n}\n\n/** The types a session another agent drives may save: what it learned, never a person's preferences. */\nexport const DELEGATED_MEMORY_TYPES: readonly MemoryType[] = ['project', 'reference'];\n\nexport function sanitizeMemoryTitle(rawTitle: string): string {\n return rawTitle\n .replace(/[\\x00-\\x1f\\x7f-\\x9f]/g, ' ')\n .replace(/[[\\]()]/g, '')\n .replace(/^[\\s#]+/, '')\n .replace(/\\s+/g, ' ')\n .trim()\n .slice(0, 120)\n .trim();\n}\n\nexport function shouldRejectMemoryText(text: string): string | null {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 'empty';\n if (trimmed.length > MAX_BODY_CHARS * 2) return 'too long';\n return looksLikeSecretOrUnfit(trimmed);\n}\n\nexport interface MemoryFileSummary {\n name: string;\n path: string;\n type?: MemoryType;\n status?: MemoryStatus;\n title?: string;\n created?: string;\n updated?: string;\n size: number;\n driver?: SessionDriver;\n}\n\nexport interface LoadedMemoryContext {\n dir: string;\n indexFile: string | null;\n indexLoaded: boolean;\n indexLineCount: number;\n loadedLineCount: number;\n indexText: string;\n files: MemoryFileSummary[];\n candidates: MemoryFileSummary[];\n}\n\nexport interface MemoryCandidate {\n type: MemoryType;\n title: string;\n body: string;\n origin: MemoryOrigin;\n source?: 'auto' | 'manual';\n confidence?: 'low' | 'medium' | 'high';\n tags?: string[];\n sessionId?: string;\n externalContext: boolean;\n evidence?: string[];\n targetSlug?: string;\n /** File name of an approved memory this one replaces; it is kept on disk, out of the index. */\n supersedes?: string;\n /** `agent` when a session another agent drove saved it (a delegated run's learning). */\n driver?: SessionDriver;\n}\n\nexport interface MemoryWriteInput extends Partial<MemoryCandidate> {\n supersededBy?: string;\n type: MemoryType;\n title: string;\n body: string;\n status?: MemoryStatus;\n created?: string;\n targetSlug?: string;\n}\n\nexport interface MemoryStoreOptions {\n bookRoot?: string;\n now?: Date;\n maxIndexLines?: number;\n dir?: string;\n}\n\nexport interface MemoryWriteResult {\n ok: boolean;\n path?: string;\n /** Approve only: the memory this approval retired, or why a recorded retirement was skipped. */\n retired?: string;\n retireSkipped?: string;\n error?: string;\n}\n\nexport interface SaveMemoryOptions extends MemoryStoreOptions {\n requireApproval?: boolean;\n quarantineExternal?: boolean;\n slug?: string;\n}\n\nexport interface SaveMemoryResult {\n ok: boolean;\n path?: string;\n indexLine?: string;\n /** Lines in MEMORY.md after the write — the model is asked to consolidate near the load limit. */\n indexLineCount?: number;\n /** File name of the memory this save retired (superseded). */\n retired?: string;\n status: MemoryStatus;\n quarantined?: boolean;\n error?: string;\n}\n\nexport const DEFAULT_MAX_INDEX_LINES = 200;\nconst INDEX_FILE = 'MEMORY.md';\nconst INBOX_DIR = '.inbox';\nconst DISCARDED_DIR = 'discarded';\n\nexport function slugifyWorkspace(workspace: string): string {\n return (\n workspace\n .replace(/[/\\\\:]+/g, '-')\n .replace(/[^a-zA-Z0-9._-]/g, '-')\n .replace(/^-+|-+$/g, '') || 'workspace'\n );\n}\n\nfunction bookRoot(opts?: MemoryStoreOptions): string {\n return opts?.bookRoot ?? resolveBookHome();\n}\n\nexport function getProjectMemoryDir(workspace: string, opts?: MemoryStoreOptions): string {\n return join(bookRoot(opts), 'projects', slugifyWorkspace(workspace), 'memory');\n}\n\nexport function getMemoryInboxDir(workspace: string, opts?: MemoryStoreOptions): string {\n return opts?.dir\n ? join(opts.dir, INBOX_DIR)\n : join(getProjectMemoryDir(workspace, opts), INBOX_DIR);\n}\n\nexport function getMemoryExtractionStatePath(workspace: string, opts?: MemoryStoreOptions): string {\n return join(getProjectMemoryDir(workspace, opts), '.extraction-state.json');\n}\n\nexport function getMemoryExtractionLockPath(workspace: string, opts?: MemoryStoreOptions): string {\n return join(getProjectMemoryDir(workspace, opts), '.extraction.lock');\n}\n\nfunction isMemoryType(value: unknown): value is MemoryType {\n return typeof value === 'string' && (MEMORY_TYPES as readonly string[]).includes(value);\n}\n\nfunction isMemoryStatus(value: unknown): value is MemoryStatus {\n return (\n value === 'approved' || value === 'pending' || value === 'discarded' || value === 'superseded'\n );\n}\n\nfunction titleFromBody(body: string): string | undefined {\n const firstHeading = body.split('\\n').find((line) => line.startsWith('# '));\n return firstHeading?.replace(/^#\\s+/, '').trim();\n}\n\nfunction summarizeMemoryFile(path: string, name = basename(path)): MemoryFileSummary | null {\n try {\n const st = statSync(path);\n if (!st.isFile()) return null;\n const raw = readFileSync(path, 'utf-8');\n const { body, frontmatter } = parseFrontmatter(raw);\n const proposedTitle =\n typeof frontmatter.proposedTitle === 'string' ? frontmatter.proposedTitle : undefined;\n const title =\n proposedTitle ??\n (typeof frontmatter.title === 'string' ? frontmatter.title : undefined) ??\n titleFromBody(body);\n return {\n name,\n path,\n type: isMemoryType(frontmatter.type) ? frontmatter.type : undefined,\n status: isMemoryStatus(frontmatter.status) ? frontmatter.status : undefined,\n title,\n created: typeof frontmatter.created === 'string' ? frontmatter.created : undefined,\n updated: typeof frontmatter.updated === 'string' ? frontmatter.updated : undefined,\n size: st.size,\n ...(frontmatter.driver === 'agent' ? { driver: 'agent' as const } : {}),\n };\n } catch {\n return null;\n }\n}\n\nexport function listMemoryFiles(workspace: string, opts?: MemoryStoreOptions): MemoryFileSummary[] {\n const dir = getProjectMemoryDir(workspace, opts);\n if (!existsSync(dir)) return [];\n const files: MemoryFileSummary[] = [];\n try {\n for (const entry of readdirSync(dir).sort()) {\n if (!entry.toLowerCase().endsWith('.md')) continue;\n if (entry.toLowerCase() === INDEX_FILE.toLowerCase()) continue; // MEMORY.md is the index, not an approved memory file.\n const full = join(dir, entry);\n const summary = summarizeMemoryFile(full, entry);\n // A superseded memory stays on disk as history but is no longer an active entry.\n if (summary && summary.status !== 'superseded') files.push(summary);\n }\n } catch {\n return files;\n }\n return files;\n}\n\nexport function listMemoryCandidates(\n workspace: string,\n opts?: MemoryStoreOptions,\n): MemoryFileSummary[] {\n const dir = getMemoryInboxDir(workspace, opts);\n if (!existsSync(dir)) return [];\n const files: MemoryFileSummary[] = [];\n try {\n for (const entry of readdirSync(dir).sort()) {\n if (!entry.endsWith('.md')) continue;\n const full = join(dir, entry);\n const summary = summarizeMemoryFile(full, entry);\n if (summary) files.push(summary);\n }\n } catch {\n return files;\n }\n return files;\n}\n\n// Deliberately cheaper than listMemoryCandidates(): readdir only, no frontmatter parse.\nexport function countMemoryCandidates(workspace: string, opts?: MemoryStoreOptions): number {\n const dir = getMemoryInboxDir(workspace, opts);\n if (!existsSync(dir)) return 0;\n try {\n return readdirSync(dir).filter((entry) => !entry.startsWith('.') && entry.endsWith('.md'))\n .length;\n } catch {\n return 0;\n }\n}\n\nfunction readFirstLines(\n path: string,\n maxLines: number,\n): { text: string; totalLines: number; loadedLines: number } {\n const raw = readFileSync(path, 'utf-8');\n const lines = raw.split('\\n');\n const loaded = lines.slice(0, maxLines);\n return {\n text: loaded.join('\\n').trim(),\n totalLines: lines.filter((line) => line.trim().length > 0).length,\n loadedLines: loaded.filter((line) => line.trim().length > 0).length,\n };\n}\n\nexport function loadMemoryContext(\n workspace: string,\n opts?: MemoryStoreOptions,\n): LoadedMemoryContext {\n const dir = getProjectMemoryDir(workspace, opts);\n const maxIndexLines = opts?.maxIndexLines ?? DEFAULT_MAX_INDEX_LINES;\n const result: LoadedMemoryContext = {\n dir,\n indexFile: null,\n indexLoaded: false,\n indexLineCount: 0,\n loadedLineCount: 0,\n indexText: '',\n files: [],\n candidates: [],\n };\n\n if (!existsSync(dir)) return result;\n\n const indexFile = join(dir, INDEX_FILE);\n if (existsSync(indexFile)) {\n try {\n const st = statSync(indexFile);\n if (st.isFile()) {\n const index = readFirstLines(indexFile, maxIndexLines);\n result.indexFile = indexFile;\n result.indexLoaded = index.text.length > 0;\n result.indexLineCount = index.totalLines;\n result.loadedLineCount = index.loadedLines;\n result.indexText = index.text;\n }\n } catch {\n result.indexFile = indexFile;\n }\n }\n\n result.files = listMemoryFiles(workspace, opts);\n result.candidates = listMemoryCandidates(workspace, opts);\n return result;\n}\n\nexport interface MemoryHealth {\n approvedCount: number;\n supersededCount: number;\n inboxCount: number;\n indexLineCount: number;\n lastWrite: Date | null;\n}\n\nexport function getNewestMemoryWriteTime(memoryDir: string): Date | null {\n if (!existsSync(memoryDir)) return null;\n let newestMs = 0;\n\n function scanEntries(dir: string, isInbox: boolean): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (entry.startsWith('.')) {\n if (!isInbox && entry === INBOX_DIR) {\n scanEntries(join(dir, entry), true);\n }\n continue;\n }\n if (isInbox && entry === DISCARDED_DIR) {\n continue;\n }\n const full = join(dir, entry);\n try {\n const st = lstatSync(full);\n if (st.isSymbolicLink()) continue;\n if (st.isFile()) {\n if (st.mtimeMs > newestMs) newestMs = st.mtimeMs;\n }\n } catch {\n // ignore unreadable entries\n }\n }\n }\n\n scanEntries(memoryDir, false);\n return newestMs > 0 ? new Date(newestMs) : null;\n}\n\nexport function getMemoryHealth(\n workspaceOrContext: string | LoadedMemoryContext,\n opts?: MemoryStoreOptions,\n): MemoryHealth {\n const dir =\n typeof workspaceOrContext === 'string'\n ? (opts?.dir ?? getProjectMemoryDir(workspaceOrContext, opts))\n : workspaceOrContext.dir;\n const workspace = typeof workspaceOrContext === 'string' ? workspaceOrContext : '';\n\n let approvedCount = 0;\n let supersededCount = 0;\n if (existsSync(dir)) {\n try {\n for (const entry of readdirSync(dir)) {\n // Same rule as listMemoryFiles, so Health agrees with the listing.\n if (\n !entry.toLowerCase().endsWith('.md') ||\n entry.toLowerCase() === INDEX_FILE.toLowerCase()\n )\n continue;\n const full = join(dir, entry);\n try {\n const st = lstatSync(full);\n if (st.isSymbolicLink() || !st.isFile()) continue;\n const summary = summarizeMemoryFile(full, entry);\n if (summary && (summary.status === 'approved' || summary.status === undefined)) {\n approvedCount++;\n } else if (summary?.status === 'superseded') {\n supersededCount++;\n }\n } catch {\n // ignore unreadable file\n }\n }\n } catch {\n // ignore unreadable directory\n }\n }\n\n const inboxCount = countMemoryCandidates(workspace, { ...opts, dir });\n\n // Counted the way a save counts it, so Health and the pressure warning agree.\n let indexLineCount = 0;\n try {\n indexLineCount = readIndexLines(dir).length;\n } catch {\n // unreadable index\n }\n\n const lastWrite = getNewestMemoryWriteTime(dir);\n return {\n approvedCount,\n supersededCount,\n inboxCount,\n indexLineCount,\n lastWrite,\n };\n}\n\nfunction safeTitle(title: string): string {\n return (\n title\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n .slice(0, 60) || 'memory'\n );\n}\n\nfunction shortHash(input: string): string {\n return createHash('sha256').update(input).digest('hex').slice(0, 8);\n}\n\nfunction formatTags(tags: string[] | undefined): string {\n if (!tags?.length) return '';\n return `tags:\\n${tags.map((tag) => `- ${tag}`).join('\\n')}\\n`;\n}\n\nfunction renderMemoryMarkdown(input: MemoryWriteInput, status: MemoryStatus, now: Date): string {\n const created = input.created ?? now.toISOString();\n const updated = now.toISOString();\n const sanitizedTitle = sanitizeMemoryTitle(input.title);\n const title = status === 'pending' ? `Candidate: ${sanitizedTitle}` : sanitizedTitle;\n const origin: MemoryOrigin = input.origin ?? 'user-text';\n const source = input.source ?? (origin === 'user-text' ? 'manual' : 'auto');\n const externalContext = input.externalContext ?? false;\n\n // Build frontmatter fields, dropping optional ones that are absent so we\n // don't rely on a blanket ''.filter() that would also strip the blank\n // spacer lines between the closing fence, the heading, and the body.\n const fm: string[] = [\n `type: ${input.type}`,\n `status: ${status}`,\n `origin: ${origin}`,\n `source: ${source}`,\n `externalContext: ${externalContext}`,\n `created: ${created}`,\n `updated: ${updated}`,\n ];\n if (input.sessionId) fm.push(`sessionId: ${input.sessionId}`);\n // Only the delegated case is marked: a person's memories keep the shape they always had.\n if (input.driver === 'agent') fm.push('driver: agent');\n if (input.evidence?.length) {\n fm.push(`evidence:\\n${input.evidence.map((e) => `- ${e}`).join('\\n')}`);\n }\n // Already a resolved file name: `memoryFileForSlug` sanitized it (and\n // `readMemoryFile` did the same for a candidate read back from disk).\n if (input.targetSlug) fm.push(`targetSlug: ${input.targetSlug}`);\n if (input.supersedes) fm.push(`supersedes: ${input.supersedes}`);\n if (input.supersededBy) fm.push(`supersededBy: ${input.supersededBy}`);\n if ('confidence' in input && input.confidence) fm.push(`confidence: ${input.confidence}`);\n if (status === 'pending') fm.push(`proposedTitle: ${sanitizedTitle}`);\n if (input.tags?.length) {\n fm.push(formatTags(input.tags).trimEnd());\n }\n return ['---', ...fm, '---', '', `# ${title}`, '', input.body.trim(), ''].join('\\n');\n}\n\nexport function writeMemoryCandidate(\n workspace: string,\n candidate: MemoryWriteInput,\n opts?: MemoryStoreOptions,\n): MemoryWriteResult {\n try {\n const title = sanitizeMemoryTitle(candidate.title);\n if (!title) {\n return { ok: false, error: 'title must not be empty after sanitizing.' };\n }\n const now = opts?.now ?? new Date();\n const inbox = getMemoryInboxDir(workspace, opts);\n mkdirSync(inbox, { recursive: true });\n const stamp = now.toISOString().replace(/[-:.]/g, '').slice(0, 15);\n const hash = shortHash(`${candidate.type}\\n${title}\\n${candidate.body}\\n${now.toISOString()}`);\n const filename = `${stamp}-cand-${safeTitle(title)}-${hash}.md`;\n const path = join(inbox, filename);\n writeFileSync(path, renderMemoryMarkdown({ ...candidate, title }, 'pending', now), 'utf-8');\n return { ok: true, path };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\nfunction resolveCandidatePath(\n workspace: string,\n candidateFile: string,\n opts?: MemoryStoreOptions,\n): string | null {\n const inbox = resolve(getMemoryInboxDir(workspace, opts));\n const raw = isAbsolute(candidateFile) ? candidateFile : join(inbox, candidateFile);\n const resolved = resolve(raw);\n const rel = relative(inbox, resolved);\n if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) return null;\n return resolved;\n}\n\nfunction activeMemoryFilename(\n input: { type: MemoryType; title: string; body: string },\n now: Date,\n): string {\n const date = now.toISOString().slice(0, 10);\n const hash = shortHash(`${input.type}\\n${input.title}\\n${input.body}`);\n return `${date}-${input.type}-${safeTitle(input.title)}-${hash}.md`;\n}\n\n/** A slug read back from frontmatter: last path segment, then sanitized (as on write). */\nfunction frontmatterSlug(raw: string | undefined): string | undefined {\n return raw?.trim() ? (sanitizeMemorySlug(lastSlugSegment(raw.trim())) ?? undefined) : undefined;\n}\n\nexport function readMemoryFile(\n path: string,\n): (MemoryCandidate & { created?: string; updated?: string; status?: MemoryStatus }) | null {\n try {\n const raw = readFileSync(path, 'utf-8');\n const { body, frontmatter } = parseFrontmatter(raw);\n if (!isMemoryType(frontmatter.type)) return null;\n const rawProposed =\n typeof frontmatter.proposedTitle === 'string' ? frontmatter.proposedTitle : undefined;\n const proposedTitle = rawProposed ? sanitizeMemoryTitle(rawProposed) || undefined : undefined;\n // `||`, not `??`: a title that sanitizes to '' falls through to the next source.\n const heading = titleFromBody(body)?.replace(/^Candidate:\\s*/i, '');\n const title =\n proposedTitle ||\n (typeof frontmatter.title === 'string' ? sanitizeMemoryTitle(frontmatter.title) : '') ||\n (heading ? sanitizeMemoryTitle(heading) : '') ||\n basename(path, '.md');\n const tags = Array.isArray(frontmatter.tags)\n ? frontmatter.tags.filter((t): t is string => typeof t === 'string')\n : undefined;\n const evidence = Array.isArray(frontmatter.evidence)\n ? frontmatter.evidence.filter((e): e is string => typeof e === 'string')\n : undefined;\n const cleanBody = body.replace(/^#\\s+[^\\n]*\\n+/i, '').trim() || body.trim();\n const origin: MemoryOrigin =\n frontmatter.origin === 'model-tool' ||\n frontmatter.origin === 'extraction' ||\n frontmatter.origin === 'user-text'\n ? frontmatter.origin\n : 'user-text';\n const source: 'auto' | 'manual' =\n frontmatter.source === 'manual' || frontmatter.source === 'auto'\n ? frontmatter.source\n : origin === 'user-text'\n ? 'manual'\n : 'auto';\n const externalContext =\n frontmatter.externalContext === true || frontmatter.externalContext === 'true';\n const sessionId = typeof frontmatter.sessionId === 'string' ? frontmatter.sessionId : undefined;\n const rawTargetSlug =\n typeof frontmatter.targetSlug === 'string' ? frontmatter.targetSlug : undefined;\n // The last path segment, the same rule `memoryFileForSlug` applies on write:\n // a legacy `targetSlug: notes/build` names a file in the memory directory,\n // and sanitizing alone would glue the segments together.\n const targetSlug = frontmatterSlug(rawTargetSlug);\n const status = isMemoryStatus(frontmatter.status) ? frontmatter.status : undefined;\n const created = typeof frontmatter.created === 'string' ? frontmatter.created : undefined;\n const updated = typeof frontmatter.updated === 'string' ? frontmatter.updated : undefined;\n const supersedes = frontmatterSlug(\n typeof frontmatter.supersedes === 'string' ? frontmatter.supersedes : undefined,\n );\n return {\n supersedes,\n type: frontmatter.type,\n title,\n body: cleanBody,\n origin,\n source,\n externalContext,\n sessionId,\n evidence,\n targetSlug,\n status,\n confidence:\n frontmatter.confidence === 'low' ||\n frontmatter.confidence === 'medium' ||\n frontmatter.confidence === 'high'\n ? frontmatter.confidence\n : undefined,\n tags,\n created,\n updated,\n ...(frontmatter.driver === 'agent' ? { driver: 'agent' as const } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction parseCandidateFile(path: string): (MemoryCandidate & { created?: string }) | null {\n return readMemoryFile(path);\n}\n\n/**\n * Characters index text must not carry: control characters, and `[]()<>` — an entry is parsed\n * by its `](file)` link, and the index is embedded in a <memory-index> block.\n */\nconst INDEX_UNSAFE = /[\\x00-\\x1f\\x7f-\\x9f[\\]()<>]/g;\n\n/** The first line of a memory's body — the fact itself — as a short index hook. */\nfunction indexHook(body: string): string {\n const first =\n body.trim().split('\\n')[0]?.replace(INDEX_UNSAFE, ' ').replace(/\\s+/g, ' ').trim() ?? '';\n return first.length > 100 ? `${first.slice(0, 100)}…` : first;\n}\n\nconst sameFile = (a: string, b: string) => a.toLowerCase() === b.toLowerCase();\n\n/** The file an index entry links to; undefined for the header or any free-form line. */\nfunction indexEntryFile(line: string): string | undefined {\n return /^- \\[[^\\]]*\\]\\(([^()]+)\\)/.exec(line)?.[1];\n}\n\n/** The index's non-empty lines; throws when it exists but cannot be read, so a caller never\n * rewrites (and wipes) an index it could not read. */\nfunction readIndexLines(dir: string): string[] {\n const indexPath = join(dir, INDEX_FILE);\n if (!existsSync(indexPath)) return [];\n return readFileSync(indexPath, 'utf-8')\n .split('\\n')\n .filter((line) => line.trim().length > 0);\n}\n\n/** Atomic: a crash mid-write must not leave a truncated index that silently loads nothing. */\nfunction writeIndexLines(dir: string, lines: string[]): void {\n writeFileAtomic(\n join(dir, INDEX_FILE),\n lines.length > 0 ? lines.join('\\n') + '\\n' : '# Book memory index\\n',\n );\n}\n\n/**\n * The index lines that still describe a loadable memory. Entries whose file is gone or\n * superseded are pruned on every rewrite — so a line a failed write left behind does not load\n * forever — and so are entries for the files in `drop`.\n */\nfunction liveIndexLines(dir: string, lines: string[], drop: readonly string[]): string[] {\n return lines.filter((line) => {\n const file = indexEntryFile(line);\n if (!file) return true;\n if (drop.some((name) => sameFile(name, file))) return false;\n const path = join(dir, file);\n if (!existsSync(path)) return false;\n return summarizeMemoryFile(path, file)?.status !== 'superseded';\n });\n}\n\n/** Drop a file's line from MEMORY.md; no index means nothing to drop (and none is created). */\nfunction removeIndexEntry(dir: string, filename: string): void {\n if (!existsSync(join(dir, INDEX_FILE))) return;\n writeIndexLines(dir, liveIndexLines(dir, readIndexLines(dir), [filename]));\n}\n\n/**\n * Write or replace a file's line in MEMORY.md in one read-modify-write, also dropping the line\n * of a memory it supersedes; returns the line and the index's entry count.\n */\nfunction updateMemoryIndex(\n dir: string,\n title: string,\n filename: string,\n type: MemoryType,\n now: Date,\n body: string,\n supersedes?: string,\n): { entry: string; lineCount: number } {\n const hook = indexHook(body);\n const entry = `- [${title}](${filename}) — ${type} — ${now.toISOString().slice(0, 10)}${hook && hook !== title ? ` — ${hook}` : ''}`;\n const drop = supersedes ? [filename, supersedes] : [filename];\n let lines = liveIndexLines(dir, readIndexLines(dir), drop);\n if (lines.length === 0) {\n lines = ['# Book memory index', entry];\n } else if (lines[0].startsWith('#')) {\n lines = [lines[0], entry, ...lines.slice(1)];\n } else {\n lines = [entry, ...lines];\n }\n writeIndexLines(dir, lines);\n return { entry, lineCount: lines.length };\n}\n\n/** Set frontmatter fields in place, leaving every other field, the headings and the body as they are. */\nfunction setFrontmatterFields(raw: string, fields: Record<string, string>): string {\n const match = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/.exec(raw);\n const entries = Object.entries(fields);\n if (!match) {\n return `---\\n${entries.map(([k, v]) => `${k}: ${v}`).join('\\n')}\\n---\\n\\n${raw}`;\n }\n let frontmatter = match[1];\n for (const [key, value] of entries) {\n const line = new RegExp(`^${key}:.*$`, 'm');\n frontmatter = line.test(frontmatter)\n ? frontmatter.replace(line, `${key}: ${value}`)\n : `${frontmatter}\\n${key}: ${value}`;\n }\n return `---\\n${frontmatter}\\n---\\n${raw.slice(match[0].length)}`;\n}\n\n/**\n * Retire a memory another replaced: only its status, `supersededBy` and `updated` change, so the\n * file stays intact as history. Best effort, after the index already dropped its line — if the\n * rewrite fails, the file stays `approved` on disk, and the next index write prunes it anyway.\n */\nfunction markSuperseded(dir: string, oldFilename: string, byFilename: string, now: Date): void {\n try {\n const target = join(dir, oldFilename);\n if (isSymlink(target)) return;\n const raw = readFileSync(target, 'utf-8');\n writeFileAtomic(\n target,\n setFrontmatterFields(raw, {\n status: 'superseded',\n supersededBy: byFilename,\n updated: now.toISOString(),\n }),\n );\n } catch {\n // see above\n }\n}\n\n/**\n * Resolve a `supersedes` slug to the on-disk name of an active memory other than `own`. A name\n * that matches no file, or one already superseded, is an error the caller reports — never a\n * silent no-op that leaves two versions loading.\n */\nfunction resolveSupersedes(\n dir: string,\n raw: string | undefined,\n own: string | undefined,\n): { filename?: string } | { error: string } {\n if (!raw?.trim()) return {};\n const named = memoryFileForSlug(dir, raw);\n if ('error' in named) return { error: `supersedes: ${named.error}` };\n // The on-disk spelling: a case-insensitive file system finds `Deploy.md` for `deploy.md`,\n // and the index and frontmatter must name the file exactly as it is.\n let actual: string | undefined;\n try {\n actual = readdirSync(dir).find((f) => sameFile(f, named.filename));\n } catch {\n actual = undefined;\n }\n if (!actual || isSymlink(join(dir, actual))) {\n return {\n error: `supersedes: no memory file ${named.filename}. Use the file name from <memory-index>.`,\n };\n }\n if (own && sameFile(actual, own)) return {};\n const current = summarizeMemoryFile(join(dir, actual), actual);\n if (current?.status === 'superseded') {\n return {\n error: `supersedes: ${actual} is already superseded; supersede its replacement instead.`,\n };\n }\n return { filename: actual };\n}\n\n/** True for a symlink, dangling or not: `existsSync` follows links, `lstat` does not. */\nfunction isSymlink(path: string): boolean {\n try {\n return lstatSync(path).isSymbolicLink();\n } catch {\n return false;\n }\n}\n\n/**\n * Where an approved memory lands — the named slug's file, or a generated name — with the\n * original creation time when it updates an entry. Refuses a symlinked target, including a\n * dangling one, which `writeFileSync` would follow out of the memory directory.\n */\nfunction approvedTarget(\n dir: string,\n input: { type: MemoryType; title: string },\n slug: string | undefined,\n now: Date,\n): { filename: string; target: string; existingCreated?: string } | { error: string } {\n const named = slug?.trim() ? memoryFileForSlug(dir, slug) : null;\n if (named && 'error' in named) return { error: named.error };\n const filename = named?.filename ?? activeMemoryFilename(input as MemoryCandidate, now);\n const target = named?.target ?? join(dir, filename);\n if (isSymlink(target)) return { error: 'Refusing to overwrite symlinked memory file.' };\n const existingCreated = named && existsSync(target) ? readMemoryFile(target)?.created : undefined;\n return { filename, target, existingCreated };\n}\n\nexport function saveMemory(\n workspace: string,\n candidate: MemoryWriteInput,\n opts?: SaveMemoryOptions,\n): SaveMemoryResult {\n const quarantineExternal = opts?.quarantineExternal ?? true;\n const isQuarantined = candidate.externalContext === true && quarantineExternal;\n const requireApproval = (opts?.requireApproval ?? false) || isQuarantined;\n const title = sanitizeMemoryTitle(candidate.title);\n if (!title) {\n return {\n ok: false,\n error: 'title must not be empty after sanitizing.',\n status: requireApproval ? 'pending' : 'approved',\n quarantined: isQuarantined,\n };\n }\n\n if (requireApproval) {\n const rawSlug = opts?.slug?.trim()\n ? opts.slug\n : candidate.targetSlug?.trim()\n ? candidate.targetSlug\n : undefined;\n let targetSlug: string | undefined;\n if (rawSlug) {\n const named = memoryFileForSlug(getProjectMemoryDir(workspace, opts), rawSlug);\n if ('error' in named) {\n return { ok: false, error: named.error, status: 'pending', quarantined: isQuarantined };\n }\n targetSlug = named.filename;\n }\n // Resolved against the entry's own target; re-checked when the candidate is approved.\n const retire = resolveSupersedes(\n getProjectMemoryDir(workspace, opts),\n candidate.supersedes,\n targetSlug,\n );\n if ('error' in retire) {\n return { ok: false, error: retire.error, status: 'pending', quarantined: isQuarantined };\n }\n const candidateInput: MemoryWriteInput = {\n ...candidate,\n title,\n targetSlug,\n supersedes: retire.filename,\n };\n const writeResult = writeMemoryCandidate(workspace, candidateInput, opts);\n if (!writeResult.ok) {\n return { ok: false, error: writeResult.error, status: 'pending', quarantined: isQuarantined };\n }\n return { ok: true, path: writeResult.path, status: 'pending', quarantined: isQuarantined };\n }\n\n try {\n const now = opts?.now ?? new Date();\n const dir = getProjectMemoryDir(workspace, opts);\n mkdirSync(dir, { recursive: true });\n\n const placed = approvedTarget(dir, { ...candidate, title }, opts?.slug, now);\n if ('error' in placed) return { ok: false, error: placed.error, status: 'approved' };\n const { filename, target, existingCreated } = placed;\n // Against the real target, generated name included, so a save never retires itself.\n const retire = resolveSupersedes(dir, candidate.supersedes, filename);\n if ('error' in retire) return { ok: false, error: retire.error, status: 'approved' };\n // Fail before writing anything when the index cannot be read, rather than leave a memory\n // on disk that no index line will ever load.\n readIndexLines(dir);\n const memoryInput: MemoryWriteInput = {\n ...candidate,\n title,\n supersedes: retire.filename,\n created: existingCreated ?? candidate.created,\n };\n writeFileSync(target, renderMemoryMarkdown(memoryInput, 'approved', now), 'utf-8');\n const index = updateMemoryIndex(\n dir,\n title,\n filename,\n candidate.type,\n now,\n candidate.body,\n memoryInput.supersedes,\n );\n if (memoryInput.supersedes) markSuperseded(dir, memoryInput.supersedes, filename, now);\n return {\n ok: true,\n path: target,\n indexLine: index.entry,\n indexLineCount: index.lineCount,\n retired: memoryInput.supersedes,\n status: 'approved',\n };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e), status: 'approved' };\n }\n}\n\n/**\n * Who wrote an existing memory entry: `agent` for a delegated run's, `human` for any other, and\n * null when there is no such entry. A delegated run may change only its own kind of entry.\n */\nexport function memoryEntryDriver(\n workspace: string,\n slug: string,\n opts?: MemoryStoreOptions,\n): SessionDriver | null {\n const named = memoryFileForSlug(getProjectMemoryDir(workspace, opts), slug);\n if ('error' in named || !existsSync(named.target)) return null;\n return readMemoryFile(named.target)?.driver === 'agent' ? 'agent' : 'human';\n}\n\nexport function deleteMemoryEntry(\n workspace: string,\n slug: string,\n opts?: MemoryStoreOptions,\n): { ok: boolean; path?: string; error?: string } {\n try {\n const dir = getProjectMemoryDir(workspace, opts);\n const named = memoryFileForSlug(dir, slug);\n if ('error' in named) return { ok: false, error: named.error };\n const { filename, target } = named;\n if (!existsSync(target)) {\n return { ok: false, error: `Memory file not found: ${filename}` };\n }\n if (lstatSync(target).isSymbolicLink()) {\n return { ok: false, error: 'Refusing to delete symlinked file.' };\n }\n\n unlinkSync(target);\n try {\n removeIndexEntry(dir, filename);\n } catch {\n // The file is gone either way; a stale index line points at nothing and is dropped by the\n // next index write.\n }\n return { ok: true, path: target };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\nexport function approveMemoryCandidate(\n workspace: string,\n candidateFile: string,\n opts?: MemoryStoreOptions,\n): MemoryWriteResult {\n try {\n const candidatePath = resolveCandidatePath(workspace, candidateFile, opts);\n if (!candidatePath)\n return { ok: false, error: 'Candidate path must stay inside the memory inbox.' };\n if (!existsSync(candidatePath))\n return { ok: false, error: `Candidate not found: ${candidateFile}` };\n if (lstatSync(candidatePath).isSymbolicLink())\n return { ok: false, error: 'Refusing to approve symlinked candidate.' };\n\n const candidate = parseCandidateFile(candidatePath);\n if (!candidate)\n return { ok: false, error: 'Candidate has invalid or missing memory frontmatter.' };\n\n const now = opts?.now ?? new Date();\n const dir = getProjectMemoryDir(workspace, opts);\n mkdirSync(dir, { recursive: true });\n\n if (!candidate.title.trim()) return { ok: false, error: 'Candidate has an empty title.' };\n const placed = approvedTarget(dir, candidate, candidate.targetSlug, now);\n if ('error' in placed) return { ok: false, error: placed.error };\n const { filename, target, existingCreated } = placed;\n\n // A candidate's `supersedes` was resolved when it was saved; re-check it now, since the\n // target may have gone or be the entry being approved.\n const retire = resolveSupersedes(dir, candidate.supersedes, filename);\n const supersedes = 'error' in retire ? undefined : retire.filename;\n const retireSkipped = 'error' in retire ? retire.error : undefined;\n const memoryInput: MemoryWriteInput = {\n ...candidate,\n supersedes,\n created: existingCreated ?? candidate.created,\n };\n const rendered = renderMemoryMarkdown(memoryInput, 'approved', now);\n // Move the candidate to discarded BEFORE writing the approved file and\n // updating the index, so a rename failure cannot leave an orphan approved\n // memory + index entry behind on disk. If the rename throws, nothing has\n // been committed yet and the caller can safely retry.\n // An unreadable index fails the approval here, while the candidate is still in the inbox.\n readIndexLines(dir);\n renameSync(candidatePath, join(getDiscardedDir(workspace, opts), basename(candidatePath)));\n writeFileSync(target, rendered, 'utf-8');\n updateMemoryIndex(\n dir,\n candidate.title,\n filename,\n candidate.type,\n now,\n candidate.body,\n supersedes,\n );\n if (supersedes) markSuperseded(dir, supersedes, filename, now);\n return { ok: true, path: target, retired: supersedes, retireSkipped };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\nfunction getDiscardedDir(workspace: string, opts?: MemoryStoreOptions): string {\n const dir = join(getMemoryInboxDir(workspace, opts), DISCARDED_DIR);\n mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nexport function discardMemoryCandidate(\n workspace: string,\n candidateFile: string,\n opts?: MemoryStoreOptions,\n): MemoryWriteResult {\n try {\n const candidatePath = resolveCandidatePath(workspace, candidateFile, opts);\n if (!candidatePath)\n return { ok: false, error: 'Candidate path must stay inside the memory inbox.' };\n if (!existsSync(candidatePath))\n return { ok: false, error: `Candidate not found: ${candidateFile}` };\n if (lstatSync(candidatePath).isSymbolicLink())\n return { ok: false, error: 'Refusing to discard symlinked candidate.' };\n const discardedPath = join(getDiscardedDir(workspace, opts), basename(candidatePath));\n renameSync(candidatePath, discardedPath);\n return { ok: true, path: discardedPath };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n","/**\n * Shared secret / high-entropy text detector.\n *\n * Used by the memory auto-capture path to avoid persisting secrets as memory\n * candidates. Intended to be reused by future log redactors and tool-output\n * scrubbers so the pattern list lives in one place.\n */\n\nexport const SECRET_PATTERNS = [\n /-----BEGIN [A-Z ]*PRIVATE KEY-----/i,\n /\\b(api[_-]?key|token|password|passwd|secret)\\s*[:=]\\s*\\S{8,}/i,\n /https?:\\/\\/[^\\s/]+:[^\\s/]+@/i,\n /\\bgh[pousr]_[A-Za-z0-9_]{20,}\\b/,\n /\\bsk-[A-Za-z0-9_-]{20,}\\b/,\n];\n\n/**\n * Whether `text` contains an explicit credential shape (a private key, an\n * `api_key=`/`token=` assignment, basic-auth in a URL, a GitHub or `sk-` token).\n * Unlike `looksLikeSecretOrUnfit` this does not apply the high-entropy rule,\n * which is sized for a sentence and flags any run of text that names a long\n * path -- the wrong test for a whole user turn.\n */\nexport function containsSecretPattern(text: string): boolean {\n return SECRET_PATTERNS.some((re) => re.test(text));\n}\n\n/**\n * Returns a short reason string when `text` looks like a secret or is\n * otherwise unfit to persist, or `null` when the text is acceptable.\n */\nexport function looksLikeSecretOrUnfit(text: string): string | null {\n const trimmed = text.trim();\n if (trimmed.length === 0) return 'empty';\n if (SECRET_PATTERNS.some((re) => re.test(trimmed))) return 'looks like a secret';\n const longTokens = trimmed.match(/[A-Za-z0-9+/=_-]{48,}/g) ?? [];\n if (longTokens.some((token) => new Set(token).size > 20)) return 'contains high-entropy text';\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/provider/reliability.ts","../src/provider/openai-compatible.ts"],"sourcesContent":["import type { RetryConfig } from '../types/runtime.js';\n\ninterface RetryLogger {\n warn(message: string, data?: Record<string, unknown>): void;\n}\n\nimport { systemClock, type Clock, type MonotonicMs } from '../clock.js';\n\nexport type ProviderErrorCode =\n | 'network'\n | 'timeout'\n | 'context_overflow'\n | 'rate_limited'\n | 'overloaded'\n | 'server_error'\n | 'auth'\n | 'bad_request'\n | 'not_found'\n | 'unprocessable'\n | 'quota'\n | 'unknown';\n\nexport function classifyHttpStatus(status: number): {\n code: ProviderErrorCode;\n retryable: boolean;\n} {\n if (status === 413) return { code: 'context_overflow', retryable: false };\n if (status === 429) return { code: 'rate_limited', retryable: true };\n if (status === 529) return { code: 'overloaded', retryable: true };\n if (status >= 500 && status < 600) return { code: 'server_error', retryable: true };\n if (status === 408) return { code: 'timeout', retryable: true };\n if (status === 401 || status === 403) return { code: 'auth', retryable: false };\n if (status === 402) return { code: 'quota', retryable: false };\n if (status === 400) return { code: 'bad_request', retryable: false };\n if (status === 404) return { code: 'not_found', retryable: false };\n if (status === 422) return { code: 'unprocessable', retryable: false };\n return { code: 'unknown', retryable: false };\n}\n\n/**\n * The two 404 wordings a router hands back when its route has nothing left to\n * serve a model with (#383). Recognized only when the body also states a\n * `(reset after …)` cooldown, so a permanent \"No endpoints found for\n * unknown-model\" — a missing model, not a capacity moment — stays a terminal\n * 404.\n */\nconst CAPACITY_OUTAGE_404 = /no\\s+(?:endpoints|accounts)\\s+found/i;\n\n/** The cooldown statement the capacity outage arrives with. */\nconst RESET_AFTER = /\\(reset after\\s*[^)]+\\)/i;\n\n/**\n * The upstream HTTP status a router quoted inside its own error body, if any.\n *\n * 9router wraps an upstream 4xx as a 503 plus a cooldown, so the wrapper's status\n * says \"transient\" while the body says the request itself is invalid. Only a 4xx\n * is ever taken from the body: a quoted 5xx says nothing the wrapper did not.\n *\n * Only two shapes count as a quote: 9router's own `[<route>] [400]:` prefix, and\n * a JSON `\"error\"` object whose `code` is a 4xx. A status mentioned in prose\n * (`upstream sent HTTP 403`, `chunk [404]`) or a longer number that merely starts\n * like one (`\"code\": 4001`) is not: misreading an outage body ends the run after\n * a single request, or parks it as a rejected credential.\n *\n * A quoted 404 whose body says no endpoints or no accounts were found — and\n * states a cooldown — is a capacity outage (#383): the router turns an upstream\n * `[CommandCode error: No endpoints found for <model>.]` into a 503 with a\n * `(reset after …)` cooldown, so it is transient and the quote must not end the\n * retries. The read ends there: such a body can also carry a JSON error object\n * with some other code, and reading that quote would park the run as if the\n * credential had been refused. A cooldown-less \"no endpoints found\" is kept as\n * the 404 it is.\n */\nexport function quotedUpstreamStatus(body: string): number | undefined {\n if (!body) return undefined;\n const text = body.length > 65536 ? body.slice(0, 65536) : body;\n const patterns = [\n /\\[[^\\]\\s]+\\]\\s*\\[(4\\d\\d)\\]:/,\n /\"error\"\\s*:\\s*\\{[^{}]*?\"code\"\\s*:\\s*\"?(4\\d\\d)(?![\\d.])/,\n ];\n const capacityOutage = CAPACITY_OUTAGE_404.test(text) && RESET_AFTER.test(text);\n if (capacityOutage) return undefined;\n for (const pattern of patterns) {\n const match = text.match(pattern);\n if (match) return Number(match[1]);\n }\n return undefined;\n}\n\n/**\n * The upstream 4xx quoted inside a retryable response, when that quote is what\n * classifies it: 9router answers 503 and names the real status in the body. A\n * non-retryable status is its own answer, and its body is never re-read.\n */\nexport function wrappedUpstreamStatus(status: number, body: string): number | undefined {\n return classifyHttpStatus(status).retryable ? quotedUpstreamStatus(body) : undefined;\n}\n\n/** `error.code` or `error.type` values that name a context overflow outright. */\nconst CONTEXT_OVERFLOW_ERROR_NAMES: ReadonlySet<string> = new Set([\n 'context_length_exceeded',\n 'request_too_large',\n // llama.cpp's server.\n 'exceed_context_size_error',\n]);\n\n/**\n * The message and the `code` / `type` names of a provider's error body. The\n * message is `error.message` (or `error` itself when it is a string), else a\n * top-level `message` or `detail`; a body that is not JSON is its own message.\n * A body that reached the read cap and starts like JSON but does not parse is\n * read for its first `\"message\"`, `\"error\"` or `\"detail\"` string and its first\n * `\"code\"` / `\"type\"` names alone, never for the rest, which may echo the\n * request. `raw` is the upstream's own error body that OpenRouter forwards in\n * `error.metadata.raw`, as a string or as an object. `parsed` says the body\n * read as a JSON object, so a reader can tell a body with no message of its own\n * from a body that is not JSON at all.\n */\nfunction errorBodyParts(body: string): {\n message: string;\n names: string[];\n raw?: string;\n parsed: boolean;\n} {\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(body);\n } catch {\n // Only a body that reached the read cap can be JSON the cap cut; a short malformed one\n // (a Python-repr `{'error': …}`) is plain text, read whole. A router's plain text can open\n // with a bracket of its own (9router's `[antigravity/x] [400]: …`), so only a `{` or an\n // array of objects counts as JSON.\n // The `- 3` allows for a multi-byte character the cap cut and the decoder replaced.\n const reachedCap = new TextEncoder().encode(body).byteLength >= ERROR_BODY_MAX_BYTES - 3;\n if (!reachedCap || !/^\\s*(?:\\{|\\[\\s*\\{)/.test(body)) {\n return { message: body, names: [], parsed: false };\n }\n // Cut JSON: the first string of `\"message\"`, else of `\"error\"`, else of `\"detail\"`, and\n // the first `\"code\"` / `\"type\"` names — never the rest, which may echo the request.\n const readString = (key: string): string => {\n const match = body.match(new RegExp(`\"${key}\"\\\\s*:\\\\s*\"((?:[^\"\\\\\\\\]|\\\\\\\\.)*)\"`));\n if (!match) return '';\n try {\n return JSON.parse(`\"${match[1]}\"`) as string;\n } catch {\n return '';\n }\n };\n const message = readString('message') || readString('error') || readString('detail');\n const names = [body.match(/\"code\"\\s*:\\s*\"([^\"]*)\"/), body.match(/\"type\"\\s*:\\s*\"([^\"]*)\"/)]\n .filter((name): name is RegExpMatchArray => name !== null)\n .map((name) => name[1]);\n return { message, names, parsed: true };\n }\n const root: unknown = Array.isArray(parsedJson) ? parsedJson[0] : parsedJson;\n if (typeof root !== 'object' || root === null) {\n return { message: body, names: [], parsed: false };\n }\n const error: unknown = (root as { error?: unknown }).error;\n if (typeof error === 'string') return { message: error, names: [], parsed: true };\n const source = (typeof error === 'object' && error !== null ? error : root) as Record<\n string,\n unknown\n >;\n const fallback = (root as { detail?: unknown }).detail;\n const message =\n typeof source.message === 'string'\n ? source.message\n : typeof fallback === 'string'\n ? fallback\n : '';\n const metadata = source.metadata as { raw?: unknown } | null | undefined;\n const raw = metadata?.raw;\n return {\n message,\n names: [source.code, source.type].filter((name): name is string => typeof name === 'string'),\n raw:\n typeof raw === 'string'\n ? raw\n : typeof raw === 'object' && raw !== null\n ? JSON.stringify(raw)\n : undefined,\n parsed: true,\n };\n}\n\n/**\n * True when an error body states that the input is too large: its `error.code`\n * or `error.type` names an overflow, or its message says so\n * (`isContextOverflowError`). Only the message is read for wording, never the\n * rest of the body: a field path such as `contents[413]` or an id such as\n * `req-413-x` elsewhere in a 400 says nothing about the context window, and a\n * stated overflow permanently lowers the model's learned window. OpenRouter's\n * own message is generic (`Provider returned error`), so the upstream body it\n * forwards is read the same way; each level is shorter than the last.\n */\nfunction statesContextOverflow(body: string): boolean {\n const { message, names, raw } = errorBodyParts(body);\n return (\n names.some((name) => CONTEXT_OVERFLOW_ERROR_NAMES.has(name.toLowerCase())) ||\n isContextOverflowError(message) ||\n (raw !== undefined && statesContextOverflow(raw))\n );\n}\n\nexport function classifyApiError(status: number, body: string): ProviderErrorCode {\n const code = classifyHttpStatus(wrappedUpstreamStatus(status, body) ?? status).code;\n return code === 'bad_request' && statesContextOverflow(body) ? 'context_overflow' : code;\n}\n\nexport function classifyProviderError(error: unknown): ProviderErrorCode {\n if (error instanceof DOMException && error.name === 'TimeoutError') return 'timeout';\n const message = (error instanceof Error ? error.message : String(error)).toLowerCase();\n if (message.includes('timed out') || message.includes('timeout')) return 'timeout';\n if (isContextOverflowError(message)) return 'context_overflow';\n return 'network';\n}\n\nexport function formatApiError(status: number, body: string): string {\n const code = classifyApiError(status, body);\n const detail = safeErrorDetail(body);\n const base = `API Error: ${status}`;\n switch (code) {\n case 'context_overflow':\n return `${base} ${detail || 'Input exceeds the model context window.'} Reduce the conversation or tool output and try again.`;\n case 'rate_limited':\n return isOversizedForRateLimit(detail)\n ? `${base} ${detail}. The request is larger than the rate limit allows at once, so it has to shrink; waiting will not help.`\n : `${base} ${detail}. This may be a temporary capacity issue. Try again in a moment.`;\n case 'overloaded':\n return `${base} Repeated 529 Overloaded errors. The API is at capacity — this is usually temporary. Try again in a moment.`;\n case 'server_error':\n return `${base} ${detail}. This is a server-side issue, usually temporary — try again in a moment.`;\n case 'timeout':\n return 'Request timed out. Check your network connection and try again.';\n case 'auth':\n return `${base} ${detail}. Check BOOK_API_KEY, or provider.<id>.apiKey in settings.`;\n case 'quota':\n return `${base} ${detail}. Check your usage/credits.`;\n default:\n return `${base} ${detail}`;\n }\n}\n\n/**\n * Detect provider/router responses that mean the input, rather than transport, is\n * too large. A 413 counts only where it is named as a status (`API Error: 413`,\n * `HTTP 413`, `status 413`, `Error code: 413`), never as a bare number: a field\n * path such as `contents[413]` or an id such as `req-413-x` is not one.\n */\nexport function isContextOverflowError(error: unknown): boolean {\n const normalized = (error instanceof Error ? error.message : String(error)).toLowerCase();\n return (\n /\\b(?:api\\s+error|http|(?:error|status)(?:\\s+code)?)\\s*:?\\s*413\\b/.test(normalized) ||\n normalized.includes('request entity too large') ||\n normalized.includes('payload too large') ||\n normalized.includes('request too large') ||\n normalized.includes('context_length_exceeded') ||\n normalized.includes('maximum context length') ||\n normalized.includes('context window') ||\n normalized.includes('prompt too long') ||\n /prompt\\s+is\\s+too\\s+long/.test(normalized) ||\n /input\\s+(?:is\\s+)?too\\s+long/.test(normalized) ||\n normalized.includes('too many tokens') ||\n // Gemini names the count: `The input token count (1196266) exceeds the maximum …`.\n /input\\s+(?:token\\s+count\\s+)?(?:\\(\\d+\\)\\s+)?exceeds?.*(?:context|limit|maximum)/.test(\n normalized,\n ) ||\n /input\\s+exceeds?.*(?:context\\s+window|context\\s+length|token\\s+limit)/.test(normalized) ||\n /maximum\\s+context|context\\s+(?:length|window|size).*(?:exceed|overflow|too\\s+(?:long|large)|maximum)/.test(\n normalized,\n ) ||\n /too\\s+many\\s+(?:input\\s+)?tokens|request\\s+too\\s+large.*token/.test(normalized)\n );\n}\n\n/**\n * True when a rate-limit error says the request itself is larger than the limit allows at\n * once, which no amount of waiting fixes: OpenAI's `Request too large for <model> … on tokens\n * per min (TPM): Limit 30000, Requested 45000.` A transient per-minute limit reads `Rate limit\n * reached …` and is not this, and neither is a bare \"too many tokens\". When the text gives\n * both numbers, the request must exceed the limit.\n */\nexport function isOversizedForRateLimit(text: string): boolean {\n if (!/\\brequest too large\\b/i.test(text)) return false;\n const limit = text.match(/\\blimit:?\\s*(\\d+)/i);\n const requested = text.match(/\\brequested:?\\s*(\\d+)/i);\n return !limit || !requested || Number(requested[1]) > Number(limit[1]);\n}\n\n/** The per-request limit an oversized rate-limit error states (`Limit 30000`), if any. */\nexport function statedRateLimit(text: string): number | undefined {\n if (!isOversizedForRateLimit(text)) return undefined;\n const limit = text.match(/\\blimit:?\\s*(\\d+)/i);\n return limit ? Number(limit[1]) : undefined;\n}\n\n/**\n * The longest text still read as an error envelope rather than an answer when a\n * model may have written it. OpenAI's server-error sentence is about 260\n * characters, and a JSON-rendered upstream error rarely passes 1,000.\n */\nconst ERROR_ENVELOPE_MAX_CHARS = 2_000;\n\ntype EnvelopeUsage = {\n promptTokens: number;\n completionTokens: number;\n cacheReadInputTokens?: number;\n cacheCreationInputTokens?: number;\n} | null;\n\n/**\n * A usage block that reports zero tokens both ways: what a router reports for\n * text it wrote itself. A provider that sends no usage block at all gives\n * `undefined`, which is not this.\n */\nfunction isZeroUsage(usage?: EnvelopeUsage): boolean {\n // A full cache hit leaves `promptTokens` at 0 on its own; the cache counts are usage too.\n return (\n usage != null &&\n usage.promptTokens === 0 &&\n usage.completionTokens === 0 &&\n (usage.cacheReadInputTokens ?? 0) === 0 &&\n (usage.cacheCreationInputTokens ?? 0) === 0\n );\n}\n\n/**\n * True when `text` has the shape a router gives an upstream error it renders as\n * the answer. 9router's Responses translator writes an upstream `error` or\n * `response.failed` event as `[Error] <message>` (or the error object as JSON\n * when it has no message) and ends the stream there. When a model may have\n * written the text, the envelope must be the whole answer: one `[Error] …` line.\n * An answer that quotes such a line and goes on to explain it is an answer. With\n * a usage block that reports zero tokens both ways any answer that opens with\n * `[Error]` is read as the router's, however many lines it runs to.\n */\nexport function isErrorEnvelopeShape(text: string, usage?: EnvelopeUsage): boolean {\n if (isZeroUsage(usage)) return /^\\s*\\[Error\\]/i.test(text);\n const trimmed = text.trim();\n return (\n trimmed.length <= ERROR_ENVELOPE_MAX_CHARS &&\n /^\\[Error\\]/i.test(trimmed) &&\n !/[\\r\\n]/.test(trimmed)\n );\n}\n\n/**\n * A router that answers 200 with the upstream's error as the assistant text has\n * not answered. The envelope shape (`isErrorEnvelopeShape`) is required; then\n * either a usage block reporting zero tokens both ways or the upstream's own\n * server-error wording confirms it. The supported shapes are the table in\n * `reliability.test.ts`:\n * - OpenAI's `[Error] An error occurred while processing your request. …\n * help.openai.com … Please include the request ID <id> in your message.`,\n * whatever the usage says;\n * - any other one-line server error that names its request ID;\n * - with a usage block that reports zero tokens both ways, any answer that\n * opens with `[Error]`, one line or many.\n */\nexport function isUpstreamErrorEnvelope(text: string, usage?: EnvelopeUsage): boolean {\n if (!isErrorEnvelopeShape(text, usage)) return false;\n return (\n isZeroUsage(usage) ||\n /\\brequest id\\b/i.test(text) ||\n /help\\.openai\\.com/i.test(text) ||\n /error occurred while processing/i.test(text)\n );\n}\n\n/**\n * How long a retryable response's error body may take to arrive. The body only\n * informs the retry decision (a quoted upstream 4xx stops the retries), so a\n * router that sends its headers and then stalls must not hold every attempt for\n * the full `requestTimeoutMs`: ten attempts at 600 s each by default. After this\n * long the decision is made on what arrived.\n */\nexport const ERROR_BODY_READ_TIMEOUT_MS = 5_000;\n\n/** The most of an error body that is ever read; the rest is cancelled unread. */\nconst ERROR_BODY_MAX_BYTES = 65_536;\n\n/**\n * Read at most ERROR_BODY_MAX_BYTES of an error body, for at most\n * ERROR_BODY_READ_TIMEOUT_MS. What arrived before the cap, the deadline, an\n * abort or a stream error is kept, and the rest of the body is cancelled.\n *\n * Every error body goes through here, retryable or not: a non-retryable one is\n * what the classification and the text the user is shown are both read from, so\n * an unbounded read there is a socket held for as long as the provider sends.\n */\nexport async function readErrorBody(response: Response, signal?: AbortSignal): Promise<string> {\n if (signal?.aborted || !response.body) return '';\n let reader: ReadableStreamDefaultReader<Uint8Array>;\n try {\n reader = response.body.getReader();\n } catch {\n // A body something else already locked has nothing left to read here.\n return '';\n }\n const decoder = new TextDecoder();\n let text = '';\n let received = 0;\n let finished = false;\n // Cancelling resolves the pending read as done, which ends the loop below.\n const stop = (): void => {\n reader.cancel().catch(() => {});\n };\n const timer = setTimeout(stop, ERROR_BODY_READ_TIMEOUT_MS);\n signal?.addEventListener('abort', stop, { once: true });\n try {\n while (received < ERROR_BODY_MAX_BYTES) {\n const chunk = await reader.read();\n if (chunk.done) {\n finished = true;\n break;\n }\n const bytes = chunk.value.subarray(0, ERROR_BODY_MAX_BYTES - received);\n received += bytes.byteLength;\n text += decoder.decode(bytes, { stream: true });\n }\n } catch {\n // A body that fails part-way still leaves what arrived before it.\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener('abort', stop);\n if (!finished) stop();\n try {\n reader.releaseLock();\n } catch {\n // Nothing is pending once the loop has ended.\n }\n }\n return text + decoder.decode();\n}\n\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit,\n retry: RetryConfig,\n signal?: AbortSignal,\n onRetry?: (attempt: number, max: number, delayMs: number) => void,\n logger?: RetryLogger,\n // The retry budget is a duration, so it reads the monotonic clock. On the\n // wall clock an NTP step or a resumed VM could hand a week-long run either\n // failure mode: a backwards correction makes the budget never expire and the\n // call retries forever, a forwards one exhausts it on the first attempt.\n clock: Clock = systemClock,\n): Promise<Response> {\n const startMs = clock.monotonicNowMs();\n const maxAttempts = retry.watchdog ? Number.MAX_SAFE_INTEGER : retry.maxAttempts;\n let lastError: string | null = null;\n\n for (let attempt = 0; attempt <= maxAttempts; attempt++) {\n if (attempt > 0 && signal?.aborted) throw abortError(signal);\n const fetchSignal = requestSignal(signal, retry.requestTimeoutMs);\n let response: Response;\n try {\n response = await fetch(url, { ...init, signal: fetchSignal });\n } catch (error) {\n if (signal?.aborted) throw error;\n lastError = error instanceof Error ? error.message : String(error);\n if (attempt >= maxAttempts || budgetExhausted(clock, startMs, retry.totalBudgetMs)) break;\n const delay = boundedDelay(clock, backoffMs(attempt, retry), startMs, retry.totalBudgetMs);\n logger?.warn('retry network error', {\n attempt: attempt + 1,\n delayMs: delay,\n error: lastError,\n });\n onRetry?.(attempt + 1, maxAttempts === Number.MAX_SAFE_INTEGER ? -1 : maxAttempts, delay);\n await sleep(delay, signal);\n continue;\n }\n\n const classification = classifyHttpStatus(response.status);\n if (!classification.retryable) return response;\n lastError = `API error ${response.status}`;\n\n const bodyText = await readErrorBody(response, signal);\n const quoted = quotedUpstreamStatus(bodyText);\n if (quoted !== undefined && !classifyHttpStatus(quoted).retryable) {\n logger?.warn('upstream error quoted in retryable status; not retrying', {\n status: response.status,\n upstreamStatus: quoted,\n });\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n // A 429 that states the request itself is too large (OpenAI's per-minute cap on one request\n // that can never fit under it) is refused the same way however long the retries wait: the\n // loop's overflow recovery is what lets it through, so it gets the response now. Tested on\n // the same formatted text the loop reads, so a statement buried in `metadata.raw` — which\n // that text does not carry — leaves the retries running. A router may wrap that 429 in\n // another status (`503 [route] [429]: …`), so the quoted one counts.\n if (\n (quoted ?? response.status) === 429 &&\n isOversizedForRateLimit(formatApiError(response.status, bodyText))\n ) {\n logger?.warn('rate limit on a request too large to ever fit; not retrying', {\n status: response.status,\n });\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n const watchdogRetry = retry.watchdog && (response.status === 429 || response.status === 529);\n const effectiveMax = watchdogRetry ? Number.MAX_SAFE_INTEGER : maxAttempts;\n if (attempt >= effectiveMax || budgetExhausted(clock, startMs, retry.totalBudgetMs)) {\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n\n const delay = boundedDelay(\n clock,\n backoffMs(attempt, retry, response.headers.get('retry-after'), bodyText),\n startMs,\n retry.totalBudgetMs,\n );\n logger?.warn('retry http status', {\n attempt: attempt + 1,\n status: response.status,\n delayMs: delay,\n });\n onRetry?.(attempt + 1, effectiveMax === Number.MAX_SAFE_INTEGER ? -1 : effectiveMax, delay);\n try {\n await sleep(delay, signal);\n } catch {\n return new Response(bodyText, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n }\n try {\n await response.body?.cancel();\n } catch {\n // A custom fetch implementation may expose an already-locked body.\n }\n }\n\n throw new Error(lastError ?? 'request failed after retries');\n}\n\nexport async function readStreamChunk(\n reader: ReadableStreamDefaultReader<Uint8Array>,\n signal: AbortSignal | undefined,\n stallTimeoutMs: number,\n): Promise<\n | { tag: 'read'; done: boolean; value: Uint8Array | undefined }\n | { tag: 'stall' }\n | { tag: 'abort' }\n> {\n if (signal?.aborted) return { tag: 'abort' };\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let onAbort: (() => void) | undefined;\n try {\n return await Promise.race([\n reader.read().then((result) => ({\n tag: 'read' as const,\n done: result.done,\n value: result.value,\n })),\n ...(stallTimeoutMs > 0\n ? [\n new Promise<{ tag: 'stall' }>((resolve) => {\n timeout = setTimeout(() => resolve({ tag: 'stall' }), stallTimeoutMs);\n }),\n ]\n : []),\n ...(signal\n ? [\n new Promise<{ tag: 'abort' }>((resolve) => {\n onAbort = () => resolve({ tag: 'abort' });\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]\n : []),\n ]);\n } finally {\n if (timeout) clearTimeout(timeout);\n if (signal && onAbort) signal.removeEventListener('abort', onAbort);\n }\n}\n\nfunction requestSignal(\n signal: AbortSignal | undefined,\n timeoutMs: number,\n): AbortSignal | undefined {\n if (timeoutMs <= 0) return signal;\n const timeout = AbortSignal.timeout(timeoutMs);\n return signal ? AbortSignal.any([signal, timeout]) : timeout;\n}\n\nfunction abortError(signal: AbortSignal): Error {\n return signal.reason instanceof Error ? signal.reason : new Error('Aborted');\n}\n\n/**\n * The message `errorBodyParts` reads, so the text shown and the text classified are the\n * same. A body with no message of its own (a nested `error.error.message`) or that is not\n * JSON shows its first characters instead.\n */\nfunction safeErrorDetail(body: string): string {\n const { message, parsed } = errorBodyParts(body);\n if (parsed && message) return message.slice(0, 2000);\n return body.slice(0, 200);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal));\n return;\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(timer);\n reject(signal ? abortError(signal) : new Error('Aborted'));\n };\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nfunction backoffMs(\n attempt: number,\n retry: RetryConfig,\n retryAfter?: string | null,\n body?: string,\n): number {\n const retryAfterMs = parseRetryAfter(retryAfter) ?? parseCooldownReset(body);\n if (retryAfterMs !== undefined) return Math.min(retryAfterMs, retry.maxDelayMs);\n const exponential = Math.min(retry.baseDelayMs * 2 ** attempt, retry.maxDelayMs);\n return Math.round(exponential * (0.5 + Math.random()));\n}\n\nfunction parseRetryAfter(value?: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds > 0) return seconds * 1000;\n const timestamp = Date.parse(value);\n if (!Number.isFinite(timestamp)) return undefined;\n // Wall clock on purpose, and the one place in this file that should be: the\n // server sent an HTTP date, which is a point on the wall clock by definition.\n // Subtracting a monotonic reading from it would be meaningless.\n return Math.max(0, timestamp - Date.now());\n}\n\n/**\n * How many milliseconds each unit word in a stated cooldown is worth. Keyed by\n * every spelling the matcher accepts.\n */\nconst COOLDOWN_UNIT_MS: Record<string, number> = {\n ms: 1,\n s: 1_000,\n sec: 1_000,\n secs: 1_000,\n second: 1_000,\n seconds: 1_000,\n m: 60_000,\n min: 60_000,\n mins: 60_000,\n minute: 60_000,\n minutes: 60_000,\n h: 3_600_000,\n hr: 3_600_000,\n hrs: 3_600_000,\n hour: 3_600_000,\n hours: 3_600_000,\n};\n\n// Longest unit spellings first, and no unit may be followed by another letter:\n// the boundary is what keeps `500ms` from being read as 500 minutes — the `m`\n// would otherwise swallow the `ms` — while still reading compound forms such as\n// `1m26s`, where a digit follows the unit.\nconst COOLDOWN_UNIT = /(\\d+)\\s*(ms|sec(?:ond)?s?|min(?:ute)?s?|h(?:ours?|rs?)?|s|m)(?![a-z])/gi;\n\n/**\n * The cooldown a router states in its error body (`(reset after 5s)`), read when\n * there is no Retry-After header to wait by (#383). Forms seen in the wild:\n * `5s`, `42s`, `1m26s`, `1 min 26 s`, and millisecond durations such as `500ms`\n * and `1s 200ms`. Units are ms, s/sec/seconds, m/min/minutes and h/hours, so `m`\n * is never read when it belongs to `ms`. Returns milliseconds, or undefined\n * when the body states no duration.\n */\nfunction parseCooldownReset(body?: string | null): number | undefined {\n if (!body) return undefined;\n const stated = body.match(/\\(reset after\\s*([^)]+)\\)/i);\n if (!stated) return undefined;\n let totalMs = 0;\n let statedUnits = 0;\n for (const unit of stated[1].matchAll(COOLDOWN_UNIT)) {\n totalMs += Number(unit[1]) * COOLDOWN_UNIT_MS[unit[2].toLowerCase()];\n statedUnits++;\n }\n return statedUnits > 0 && totalMs > 0 ? totalMs : undefined;\n}\n\nfunction budgetExhausted(clock: Clock, startMs: MonotonicMs, budgetMs: number): boolean {\n return budgetMs > 0 && clock.monotonicNowMs() - startMs >= budgetMs;\n}\n\nfunction boundedDelay(\n clock: Clock,\n delayMs: number,\n startMs: MonotonicMs,\n budgetMs: number,\n): number {\n if (budgetMs <= 0) return delayMs;\n return Math.min(delayMs, Math.max(0, budgetMs - (clock.monotonicNowMs() - startMs)));\n}\n","import type { AgentConfig } from '../types/runtime.js';\nimport type {\n ProviderMessage,\n ProviderStreamEvent,\n SystemPromptZones,\n} from '../types/providers.js';\nimport type { ToolDefinition } from '../types/tools.js';\nimport type { Usage } from '../types/messages.js';\nimport { createDebugLogger, isDebugEnabled } from '../debug-log.js';\nimport { escapeInvisibleCharacters } from '../control-characters.js';\nimport {\n classifyApiError,\n classifyProviderError,\n fetchWithRetry,\n formatApiError,\n readErrorBody,\n readStreamChunk,\n wrappedUpstreamStatus,\n} from './reliability.js';\n\nconst log = createDebugLogger('provider');\n\nfunction parseToolArguments(raw: string): {\n arguments: Record<string, unknown>;\n unparsedArguments?: { raw: string; error: string };\n} {\n if (!raw.trim()) return { arguments: {} };\n try {\n const parsed = JSON.parse(raw);\n return {\n arguments: parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {},\n };\n } catch (error) {\n return {\n arguments: {},\n unparsedArguments: { raw, error: error instanceof Error ? error.message : String(error) },\n };\n }\n}\n\nfunction parseReasoningDelta(delta: Record<string, unknown>): string | undefined {\n for (const key of ['reasoning_content', 'reasoning', 'thinking', 'analysis']) {\n const value = delta[key];\n if (typeof value === 'string' && value) return value;\n }\n const details = delta.reasoning_details;\n if (!Array.isArray(details)) return undefined;\n const text = details\n .map((detail) => {\n if (typeof detail === 'string') return detail;\n if (!detail || typeof detail !== 'object') return '';\n const item = detail as Record<string, unknown>;\n for (const key of ['text', 'reasoning', 'content']) {\n if (typeof item[key] === 'string') return item[key] as string;\n }\n return '';\n })\n .filter(Boolean)\n .join('');\n return text || undefined;\n}\n\nfunction isSystemPromptZones(content: ProviderMessage['content']): content is SystemPromptZones {\n return (\n !!content &&\n typeof content === 'object' &&\n 'cachedPrefix' in content &&\n 'dynamicSuffix' in content\n );\n}\n\nfunction flattenMessages(messages: ProviderMessage[]): Array<{\n role: string;\n content:\n | string\n | null\n | Array<{ type: 'text'; text: string } | { type: 'image_url'; image_url: { url: string } }>;\n tool_calls?: ProviderMessage['tool_calls'];\n tool_call_id?: string;\n}> {\n return messages.map(({ providerMetadata: _providerMetadata, reasoningContent, ...msg }) => ({\n ...msg,\n content: isSystemPromptZones(msg.content)\n ? [msg.content.cachedPrefix, msg.content.dynamicSuffix].filter(Boolean).join('\\n\\n')\n : Array.isArray(msg.content)\n ? msg.content.map((part) =>\n part.type === 'text'\n ? part\n : {\n type: 'image_url' as const,\n image_url: { url: `data:${part.mediaType};base64,${part.data}` },\n },\n )\n : reasoningContent\n ? ['<reasoning_context>', reasoningContent, '</reasoning_context>', msg.content ?? '']\n .filter(Boolean)\n .join('\\n')\n : msg.content,\n }));\n}\n\n/**\n * Map an OpenAI-compatible `usage` object onto Book's `Usage`, prompt-cache tokens included.\n *\n * Book's `promptTokens` is the uncached input, as on the Anthropic path, with cache reads and\n * writes counted separately. Where the cache counts sit depends on who reported them:\n *\n * - `prompt_tokens_details.cached_tokens` / `cache_creation_tokens` / `cache_write_tokens`\n * (OpenAI, xAI, 9router, OpenRouter) and DeepSeek's `prompt_cache_hit_tokens`, plus Moonshot's\n * top-level `cached_tokens` and DashScope's `prompt_tokens_details.cache_creation_input_tokens`,\n * are part of `prompt_tokens`, so they are subtracted from it. `total_tokens` already covers them,\n * so `contextTokens` stays unset and compaction pressure is `total_tokens`, as before.\n * - Anthropic's own top-level names (`cache_read_input_tokens`, `cache_creation_input_tokens`)\n * from a proxy that sends no `prompt_tokens_details` are Anthropic's numbers passed through,\n * where the input count excludes the cache: they are added on top, and `contextTokens` carries\n * the whole prompt. A proxy that reports an OpenAI-style cache field beside\n * them, even at zero (LiteLLM's `prompt_tokens_details.cached_tokens`), has normalised\n * `prompt_tokens` to include them.\n *\n * Counts that cannot fit inside `prompt_tokens` are treated as on top of it whatever their\n * source. A usage with no cache tokens maps exactly as before, with no cache fields.\n */\nexport function parseCompatibleUsage(raw: unknown): Usage | null {\n if (!raw || typeof raw !== 'object') return null;\n const usage = raw as Record<string, unknown>;\n const details =\n usage.prompt_tokens_details && typeof usage.prompt_tokens_details === 'object'\n ? (usage.prompt_tokens_details as Record<string, unknown>)\n : {};\n const tokens = (value: unknown): number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;\n const firstCount = (...values: unknown[]): number => {\n for (const value of values) {\n const count = tokens(value);\n if (count > 0) return count;\n }\n return 0;\n };\n const prompt = tokens(usage.prompt_tokens);\n const base: Usage = {\n promptTokens: prompt,\n completionTokens: tokens(usage.completion_tokens),\n // A provider that omits `total_tokens` would otherwise read as zero context pressure.\n totalTokens: tokens(usage.total_tokens) || prompt + tokens(usage.completion_tokens),\n };\n const topRead = tokens(usage.cache_read_input_tokens);\n const topWrite = tokens(usage.cache_creation_input_tokens);\n // Inside `prompt_tokens` by the OpenAI convention: OpenAI, xAI, 9router, OpenRouter, DeepSeek,\n // Moonshot (top-level `cached_tokens`) and DashScope (`cache_creation_input_tokens` in details).\n const normalisedRead = firstCount(\n details.cached_tokens,\n usage.prompt_cache_hit_tokens,\n usage.cached_tokens,\n );\n const normalisedWrite = firstCount(\n details.cache_creation_tokens,\n details.cache_write_tokens,\n details.cache_creation_input_tokens,\n );\n const read = normalisedRead || topRead;\n const write = normalisedWrite || topWrite;\n if (read === 0 && write === 0) return base;\n // Anthropic's top-level names with no OpenAI-style cache field beside them (not even a zero)\n // are Anthropic's numbers passed through, where the input count excludes the cache. A field\n // beside them, even `cached_tokens: 0` on LiteLLM's cold turn, says `prompt_tokens` was\n // normalised to include them.\n const reportsOpenAiCacheField = [\n details.cached_tokens,\n details.cache_creation_tokens,\n details.cache_write_tokens,\n details.cache_creation_input_tokens,\n usage.prompt_cache_hit_tokens,\n usage.cached_tokens,\n ].some((value) => typeof value === 'number');\n const anthropicPassThrough = !reportsOpenAiCacheField && topRead + topWrite > 0;\n if (!anthropicPassThrough && read + write <= prompt) {\n return {\n ...base,\n promptTokens: prompt - read - write,\n cacheReadInputTokens: read,\n cacheCreationInputTokens: write,\n };\n }\n return {\n ...base,\n cacheReadInputTokens: read,\n cacheCreationInputTokens: write,\n contextTokens: prompt + read + write,\n };\n}\n\nexport function convertTools(tools: ToolDefinition[]): Array<{\n type: 'function';\n function: { name: string; description: string; parameters: Record<string, unknown> };\n}> {\n return tools.map((tool) => ({\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema ?? tool.parameters,\n },\n }));\n}\n\nexport async function* chatCompletionStream(\n config: AgentConfig,\n messages: ProviderMessage[],\n tools: ToolDefinition[],\n options?: {\n signal?: AbortSignal;\n onRetry?: (attempt: number, max: number, delayMs: number) => void;\n onStreamStall?: (countdownMs: number) => void;\n onStreamResume?: () => void;\n maxOutputTokens?: number;\n },\n): AsyncGenerator<ProviderStreamEvent> {\n const retry = config.retry;\n const signal = options?.signal;\n const url = `${config.baseUrl}/chat/completions`;\n\n const body: Record<string, unknown> = {\n model: config.model,\n messages: flattenMessages(messages),\n stream: true,\n // Request token usage in the final SSE chunk so we can track cost.\n stream_options: { include_usage: true },\n };\n if (options?.maxOutputTokens || config.maxTokensExplicit || config.modelInfo?.maxOutputTokens) {\n body.max_tokens = options?.maxOutputTokens ?? config.maxTokens;\n }\n if (config.effortExplicit && config.effort) body.reasoning_effort = config.effort;\n\n // Whether this request expects the model to reason before answering: either we\n // asked it to, or the model's catalog entry declares an effort range. An entry\n // of `false` means the model does not reason at all; an absent entry is unknown,\n // and stays on the chat regime rather than guessing.\n const reasoningEnabled =\n body.reasoning_effort !== undefined || typeof config.modelInfo?.effort === 'object';\n\n if (tools.length > 0) {\n body.tools = convertTools(tools);\n }\n\n log.debug('chatCompletionStream request', {\n model: config.model,\n messageCount: messages.length,\n toolCount: tools.length,\n maxTokens: config.maxTokens,\n });\n\n let response: Response;\n try {\n response = await fetchWithRetry(\n url,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(body),\n },\n retry,\n signal,\n options?.onRetry,\n log,\n );\n } catch (e) {\n if (signal?.aborted) return;\n log.warn('fetchWithRetry failed', e instanceof Error ? e.message : String(e));\n yield {\n type: 'error',\n error: e instanceof Error ? e.message : String(e),\n errorCode: classifyProviderError(e),\n };\n return;\n }\n\n if (signal?.aborted) {\n await response.body?.cancel();\n return;\n }\n\n log.debug('response received', { status: response.status, ok: response.ok });\n\n if (!response.ok) {\n const errorText = await readErrorBody(response, signal);\n yield {\n type: 'error',\n error: formatApiError(response.status, errorText),\n errorCode: classifyApiError(response.status, errorText),\n upstreamStatus: wrappedUpstreamStatus(response.status, errorText),\n };\n return;\n }\n\n const reader = response.body?.getReader();\n if (!reader) {\n yield { type: 'error', error: 'No response body', errorCode: 'protocol_error' };\n return;\n }\n\n const decoder = new TextDecoder();\n let buffer = '';\n const toolCallParts: Array<{\n index: number;\n id: string;\n name: string;\n arguments: string;\n /** Deltas that carried an arguments fragment: a wire format that sends one, vs many. */\n fragments: number;\n }> = [];\n let currentUsage: Usage | null = null;\n let responseModel: string | undefined;\n let responseId: string | undefined;\n const finishReasons = new Set<string>();\n\n const emitToolCalls = function* (): Generator<ProviderStreamEvent> {\n for (const part of [...toolCallParts].sort((a, b) => a.index - b.index)) {\n if (!part.id && !part.name) continue;\n const { arguments: arguments_, unparsedArguments } = parseToolArguments(part.arguments);\n if (unparsedArguments) {\n // The head, not the whole text: enough to see whether the call arrived with its\n // first fragment missing (#260), which is a route's wire format, not the model's JSON.\n log.warn('tool call arguments are not valid JSON', {\n index: part.index,\n id: part.id,\n name: escapeInvisibleCharacters(part.name),\n fragments: part.fragments,\n length: part.arguments.length,\n head: escapeInvisibleCharacters(part.arguments.slice(0, 120)),\n });\n }\n yield {\n type: 'tool_call',\n toolCall: {\n id: part.id || `tool-${part.index}`,\n name: part.name,\n arguments: arguments_,\n ...(unparsedArguments ? { unparsedArguments } : {}),\n },\n };\n }\n };\n\n const emitDone = function* (\n terminal: '[DONE]' | 'finish_reason',\n ): Generator<ProviderStreamEvent> {\n yield* emitToolCalls();\n log.info('stream done', {\n terminal,\n promptTokens: currentUsage?.promptTokens ?? 0,\n completionTokens: currentUsage?.completionTokens ?? 0,\n totalTokens: currentUsage?.totalTokens ?? 0,\n cacheReadInputTokens: currentUsage?.cacheReadInputTokens ?? 0,\n cacheCreationInputTokens: currentUsage?.cacheCreationInputTokens ?? 0,\n });\n yield {\n type: 'done',\n usage: currentUsage ?? undefined,\n ...(responseModel ? { responseModel } : {}),\n ...(responseId ? { responseId } : {}),\n ...(finishReasons.size > 0 ? { finishReasons: [...finishReasons] } : {}),\n };\n };\n\n const processSseLine = function* (line: string): Generator<ProviderStreamEvent, boolean> {\n const trimmed = line.trim();\n if (!trimmed || !trimmed.startsWith('data:')) return false;\n\n const data = trimmed.slice(5).replace(/^ /, '');\n if (data === '[DONE]') {\n yield* emitDone('[DONE]');\n return true;\n }\n\n try {\n const parsed = JSON.parse(data);\n if (typeof parsed.model === 'string') responseModel = parsed.model;\n if (typeof parsed.id === 'string') responseId = parsed.id;\n // OpenAI sends usage on the final chunk when stream_options.include_usage is set.\n const usage = parseCompatibleUsage(parsed.usage);\n if (usage) currentUsage = usage;\n const choice = parsed.choices?.[0];\n if (!choice) return false;\n const finishReason: unknown = choice.finish_reason;\n if (typeof finishReason === 'string' && finishReason) finishReasons.add(finishReason);\n\n const delta = choice.delta as\n | (Record<string, unknown> & {\n content?: string;\n tool_calls?: Array<{\n index?: number;\n id?: string;\n function?: { name?: string; arguments?: string };\n }>;\n })\n | undefined;\n if (!delta) return false;\n\n const reasoning = parseReasoningDelta(delta);\n if (reasoning) {\n // The model is reasoning, whatever the request could not say (#379), so\n // this phase waits out the thinking ceiling. Only this phase does.\n if (retry.thinkingStallTimeoutMs) {\n stallTimeoutMs = Math.max(stallTimeoutMs, retry.thinkingStallTimeoutMs);\n }\n log.debug('stream reasoning', { len: reasoning.length });\n yield { type: 'reasoning', reasoning };\n }\n\n if (delta.content) {\n // The model has started answering, so the chat ceiling bounds this\n // phase again — reasoning can resume and promote the ceiling back.\n stallTimeoutMs = startingStallTimeoutMs;\n log.debug('stream text', { len: delta.content.length });\n yield { type: 'text', content: delta.content };\n }\n\n if (delta.tool_calls) {\n // A tool call is the answer phase too, and the one where a stall costs\n // most: the turn is waiting on a function call that is not coming.\n stallTimeoutMs = startingStallTimeoutMs;\n for (const tc of delta.tool_calls) {\n const explicitIndex = Number.isInteger(tc.index) ? tc.index : undefined;\n let index = explicitIndex;\n\n if (index === undefined && tc.id) {\n index = toolCallParts.find((part) => part.id === tc.id)?.index;\n }\n if (index === undefined) {\n index = toolCallParts.length;\n }\n\n let part = toolCallParts.find((p) => p.index === index);\n if (!part) {\n part = { index, id: '', name: '', arguments: '', fragments: 0 };\n toolCallParts.push(part);\n }\n\n if (tc.id) part.id = tc.id;\n if (tc.function?.name) part.name = tc.function.name;\n if (tc.function?.arguments) {\n // The head of each call's first fragment, not of every one: a call that arrives\n // missing its opening `{\"filePath\": ` is visible here (#260) and nowhere else.\n // Escaped, because the text is the model's and a debug log on stderr may be a\n // terminal; behind the flag, so a large streamed Write does no string work and\n // writes no lines when debugging is off.\n if (part.fragments === 0 && isDebugEnabled()) {\n log.debug('stream tool_call delta', {\n index,\n id: tc.id,\n name: escapeInvisibleCharacters(tc.function.name ?? ''),\n argumentsLength: tc.function.arguments.length,\n argumentsHead: escapeInvisibleCharacters(tc.function.arguments.slice(0, 120)),\n });\n }\n part.arguments += tc.function.arguments;\n part.fragments++;\n }\n }\n }\n } catch {\n // Skip unparseable lines.\n }\n\n return false;\n };\n\n // Stream stall detection: if no data arrives for the ceiling in force, call\n // the onStreamStall callback and yield a visible error instead of leaving the\n // TUI stuck in a pending read forever.\n //\n // The ceiling is chosen per stream and follows its phase, not just per\n // request. It starts at what the request can already prove: the thinking\n // ceiling when we asked for reasoning or the model's catalog entry declares an\n // effort range, the chat-tuned `streamStallTimeoutMs` otherwise. What a\n // request cannot prove is whether the model reasons, because plenty of routes\n // serve a reasoning model Book has no catalog entry for, and send it no\n // `reasoning_effort` — there is no evidence before the stream opens, so the\n // first thinking pause lands on the chat ceiling and a healthy request is\n // cancelled mid-thought and reported as a stalled stream (#379), the single\n // most common way a run \"just stops\". A reasoning delta is that evidence\n // arriving late, so it promotes that phase to the thinking ceiling.\n //\n // A content or tool-call delta ends the thinking phase, and the ceiling goes\n // back to where it started. Keeping the promotion for the rest of the stream\n // would apply fifteen minutes to the answer phase, where a dead socket is the\n // commonest fault by far and is worth noticing in twenty seconds; reasoning\n // that resumes mid-answer promotes the ceiling again. A stream that never\n // reasons keeps the chat ceiling throughout, and a request with an explicit\n // effort or a catalogued one is unchanged.\n const startingStallTimeoutMs =\n reasoningEnabled && retry.thinkingStallTimeoutMs\n ? Math.max(retry.streamStallTimeoutMs, retry.thinkingStallTimeoutMs)\n : retry.streamStallTimeoutMs;\n let stallTimeoutMs = startingStallTimeoutMs;\n\n try {\n while (true) {\n const result = await readStreamChunk(reader, signal, stallTimeoutMs);\n\n if (result.tag === 'abort') {\n try {\n await reader.cancel();\n } catch {\n // ignore cancellation failures\n }\n return;\n }\n\n if (result.tag === 'stall') {\n log.warn('stream stalled', { timeoutMs: stallTimeoutMs });\n options?.onStreamStall?.(stallTimeoutMs);\n try {\n await reader.cancel();\n } catch {\n // ignore cancellation failures\n }\n yield {\n type: 'error',\n error: 'Stream stalled: no data received for ' + stallTimeoutMs + 'ms',\n errorCode: 'stream_stall',\n };\n return;\n }\n\n if (result.done) break;\n\n const value = result.value;\n if (!value) continue;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n if (yield* processSseLine(line)) return;\n }\n }\n\n buffer += decoder.decode();\n for (const line of buffer.split('\\n')) {\n if (yield* processSseLine(line)) return;\n }\n\n // Some OpenAI-compatible gateways omit [DONE] and close after a finish_reason chunk.\n if (finishReasons.size > 0) {\n yield* emitDone('finish_reason');\n return;\n }\n } catch (e) {\n if (signal?.aborted) return;\n // Unexpected stream error — surface it instead of silently completing,\n // otherwise the TUI can show an empty completed assistant message.\n yield {\n type: 'error',\n error: e instanceof Error ? e.message : String(e),\n errorCode: classifyProviderError(e),\n };\n return;\n } finally {\n try {\n reader.releaseLock();\n } catch {\n // ignore: the reader may already have been cancelled/released\n }\n }\n\n yield {\n type: 'error',\n error: 'Provider stream ended before its terminal event.',\n errorCode: 'transport_interrupted',\n };\n}\n"],"mappings":"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","names":["message","delay","part"]}
@@ -1,273 +0,0 @@
1
- import {
2
- systemClock
3
- } from "./chunk-RT5SATF7.js";
4
- import {
5
- buildChildEnv
6
- } from "./chunk-XHAO3K7I.js";
7
- import {
8
- toolFailure,
9
- toolSuccess
10
- } from "./chunk-224FJZLH.js";
11
-
12
- // src/jobs/process-tree.ts
13
- import { execFile } from "child_process";
14
-
15
- // src/system32.ts
16
- import { existsSync } from "fs";
17
- import { join } from "path";
18
- function system32Executable(name, platform = process.platform, env = process.env) {
19
- if (platform !== "win32") return name;
20
- const fileName = name.toLowerCase().endsWith(".exe") ? name : `${name}.exe`;
21
- const systemRoot = env.SystemRoot || env.windir || "C:\\Windows";
22
- const targetPath = join(systemRoot, "System32", fileName);
23
- return existsSync(targetPath) ? targetPath : name;
24
- }
25
-
26
- // src/jobs/process-tree.ts
27
- var GROUP_POLL_INTERVAL_MS = 25;
28
- var TERMINATE_GRACE_MS = 1500;
29
- function signalTargetExists(target) {
30
- try {
31
- process.kill(target, 0);
32
- return true;
33
- } catch (error) {
34
- return error.code === "EPERM";
35
- }
36
- }
37
- function isProcessAlive(pid) {
38
- return pid === void 0 ? false : signalTargetExists(pid);
39
- }
40
- function isProcessGroupAlive(pgid) {
41
- if (pgid === void 0 || process.platform === "win32") return false;
42
- return signalTargetExists(-pgid);
43
- }
44
- function signalProcessGroup(proc, pgid, signal) {
45
- try {
46
- process.kill(-pgid, signal);
47
- } catch {
48
- try {
49
- proc?.kill(signal);
50
- } catch {
51
- }
52
- }
53
- }
54
- async function waitForProcessGroupExit(pgid, timeoutMs, clock = systemClock) {
55
- const deadline = clock.monotonicNowMs() + timeoutMs;
56
- while (isProcessGroupAlive(pgid)) {
57
- if (clock.monotonicNowMs() >= deadline) return false;
58
- await new Promise((resolve) => setTimeout(resolve, GROUP_POLL_INTERVAL_MS));
59
- }
60
- return true;
61
- }
62
- function waitForProcessClose(proc, timeoutMs) {
63
- if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve(true);
64
- return new Promise((resolve) => {
65
- const timer = setTimeout(() => {
66
- cleanup();
67
- resolve(proc.exitCode !== null || proc.signalCode !== null);
68
- }, timeoutMs);
69
- const onClose = () => {
70
- cleanup();
71
- resolve(true);
72
- };
73
- const cleanup = () => {
74
- clearTimeout(timer);
75
- proc.off("close", onClose);
76
- };
77
- proc.once("close", onClose);
78
- });
79
- }
80
- async function runTaskkill(pid, timeoutMs = TERMINATE_GRACE_MS) {
81
- return new Promise((resolve) => {
82
- execFile(
83
- system32Executable("taskkill"),
84
- ["/PID", String(pid), "/T", "/F"],
85
- // Book's own helper, but it is a command like any other and is handed no more of Book's
86
- // environment than the next one would be.
87
- { windowsHide: true, timeout: timeoutMs, env: buildChildEnv() },
88
- (error) => resolve(!error)
89
- );
90
- });
91
- }
92
- async function terminateWindowsProcessTree(proc, pid, signal, treeKill = runTaskkill) {
93
- const alreadyExited = proc.exitCode !== null || proc.signalCode !== null;
94
- if (alreadyExited) return true;
95
- if (await treeKill(pid)) return true;
96
- try {
97
- proc.kill(signal);
98
- } catch {
99
- }
100
- return false;
101
- }
102
- async function terminateProcessTree(proc, pid, hasClosed) {
103
- if (!proc || pid === void 0) return true;
104
- if (process.platform !== "win32") {
105
- signalProcessGroup(proc, pid, "SIGTERM");
106
- if (await waitForProcessGroupExit(pid, TERMINATE_GRACE_MS)) return true;
107
- signalProcessGroup(proc, pid, "SIGKILL");
108
- return waitForProcessGroupExit(pid, TERMINATE_GRACE_MS);
109
- }
110
- const confirmed = await terminateWindowsProcessTree(proc, pid, "SIGTERM");
111
- if (await hasClosed(TERMINATE_GRACE_MS)) return confirmed;
112
- try {
113
- proc.kill("SIGKILL");
114
- } catch {
115
- return false;
116
- }
117
- return await hasClosed(TERMINATE_GRACE_MS) && confirmed;
118
- }
119
- async function terminateForegroundProcess(proc) {
120
- await terminateProcessTree(proc, proc.pid, (timeoutMs) => waitForProcessClose(proc, timeoutMs));
121
- }
122
-
123
- // src/tools/git.ts
124
- import { execFile as execFile2 } from "child_process";
125
- var GIT_HARDENING_ARGS = [
126
- "-c",
127
- "core.fsmonitor=false",
128
- "-c",
129
- "core.pager=cat",
130
- "-c",
131
- "core.hooksPath=",
132
- "-c",
133
- "core.untrackedCache=false",
134
- "-c",
135
- "gc.auto=0",
136
- "-c",
137
- "maintenance.auto=false",
138
- "-c",
139
- "log.showSignature=false",
140
- "--no-optional-locks"
141
- ];
142
- var HARDENED_DIFF_ARGS = ["--no-ext-diff", "--no-textconv"];
143
- function hardenedGitEnv() {
144
- return { GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0" };
145
- }
146
- function hardenedGitArgs(args) {
147
- return [...GIT_HARDENING_ARGS, ...args];
148
- }
149
- async function runGit(args, ctx, execute = execFile2) {
150
- return new Promise((resolve) => {
151
- const options = {
152
- cwd: ctx.workspaceRoot,
153
- encoding: "utf-8",
154
- timeout: 3e4,
155
- signal: ctx.signal,
156
- // A hook git runs is a project command like any other, so it must not inherit a NODE_ENV
157
- // Book defaulted for its own renderer.
158
- env: buildChildEnv(process.env, ctx.env)
159
- };
160
- execute("git", args, options, (error, stdout, stderr) => {
161
- if (!error) {
162
- resolve({ success: true, output: stdout || "(no output)" });
163
- return;
164
- }
165
- if (ctx.signal?.aborted) {
166
- resolve({ success: false, output: "", error: "CANCELLED: Git command was cancelled" });
167
- return;
168
- }
169
- resolve({
170
- success: false,
171
- output: "",
172
- error: stderr || error.message || "Git command failed"
173
- });
174
- });
175
- });
176
- }
177
- function readOnlyGit(args, ctx) {
178
- return runGit(hardenedGitArgs(args), {
179
- ...ctx,
180
- env: { ...ctx.env, ...hardenedGitEnv() }
181
- });
182
- }
183
- function gitResult(result) {
184
- return result.success ? toolSuccess(result.output) : toolFailure(result.error ?? "Git command failed", { content: result.output });
185
- }
186
- async function gitStatus(_args, ctx) {
187
- const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitStatus, ctx);
188
- return gitResult(result);
189
- }
190
- async function gitDiff(_args, ctx) {
191
- const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitDiff, ctx);
192
- return gitResult(result);
193
- }
194
- async function gitLog(_args, ctx) {
195
- const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitLog, ctx);
196
- return gitResult(result);
197
- }
198
- async function gitCommit(args, ctx) {
199
- const message = args.message;
200
- const result = await runGit(["commit", "-m", message], ctx);
201
- return gitResult(result);
202
- }
203
- async function gitBranch(_args, ctx) {
204
- const result = await readOnlyGit(READ_ONLY_GIT_ARGS.GitBranch, ctx);
205
- return gitResult(result);
206
- }
207
- var READ_ONLY_GIT_ARGS = {
208
- GitStatus: ["status", "--short"],
209
- GitDiff: ["diff", ...HARDENED_DIFF_ARGS],
210
- GitLog: ["log", "--oneline", "-20", "--no-show-signature"],
211
- GitBranch: ["branch", "-a"]
212
- };
213
- var gitTools = [
214
- {
215
- name: "GitStatus",
216
- idempotent: true,
217
- policy: { concurrency: "parallel" },
218
- description: "Show the working tree status (git status --short)",
219
- parameters: { type: "object", properties: {}, required: [] },
220
- execute: gitStatus
221
- },
222
- {
223
- name: "GitDiff",
224
- idempotent: true,
225
- policy: { concurrency: "parallel" },
226
- description: "Show changes between commits, commit and working tree, etc.",
227
- parameters: { type: "object", properties: {}, required: [] },
228
- execute: gitDiff
229
- },
230
- {
231
- name: "GitLog",
232
- idempotent: true,
233
- policy: { concurrency: "parallel" },
234
- description: "Show recent commit logs (last 20, oneline)",
235
- parameters: { type: "object", properties: {}, required: [] },
236
- execute: gitLog
237
- },
238
- {
239
- name: "GitCommit",
240
- description: "Create a new commit with a message",
241
- parameters: {
242
- type: "object",
243
- properties: { message: { type: "string", description: "Commit message" } },
244
- required: ["message"]
245
- },
246
- execute: gitCommit
247
- },
248
- {
249
- name: "GitBranch",
250
- idempotent: true,
251
- policy: { concurrency: "parallel" },
252
- description: "List branches",
253
- parameters: { type: "object", properties: {}, required: [] },
254
- execute: gitBranch
255
- }
256
- ];
257
-
258
- export {
259
- TERMINATE_GRACE_MS,
260
- isProcessAlive,
261
- signalProcessGroup,
262
- waitForProcessGroupExit,
263
- waitForProcessClose,
264
- runTaskkill,
265
- terminateProcessTree,
266
- terminateForegroundProcess,
267
- HARDENED_DIFF_ARGS,
268
- hardenedGitEnv,
269
- hardenedGitArgs,
270
- runGit,
271
- gitTools
272
- };
273
- //# sourceMappingURL=chunk-C7ADUNPR.js.map