@theokit/sdk 5.13.0 → 5.15.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 (241) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/{agent-DSMuwADp.d.ts → agent-B6C9i_qu.d.ts} +31 -213
  3. package/dist/{agent-iW5kPpfT.d.cts → agent-B6FQad6P.d.cts} +31 -213
  4. package/dist/{agent-5DSDDH7K.js → agent-DRDGUU6T.js} +21 -20
  5. package/dist/{agent-5DSDDH7K.js.map → agent-DRDGUU6T.js.map} +1 -1
  6. package/dist/{agent-WOMRT3KX.cjs → agent-VI2HITPT.cjs} +22 -21
  7. package/dist/{agent-WOMRT3KX.cjs.map → agent-VI2HITPT.cjs.map} +1 -1
  8. package/dist/agent.d.ts +2 -3
  9. package/dist/auth/index.cjs +17 -16
  10. package/dist/auth/index.cjs.map +1 -1
  11. package/dist/auth/index.js +5 -4
  12. package/dist/auth/index.js.map +1 -1
  13. package/dist/{batch-EP3FCL6D.js → batch-N2XDYZED.js} +4 -4
  14. package/dist/{batch-EP3FCL6D.js.map → batch-N2XDYZED.js.map} +1 -1
  15. package/dist/{batch-IZ5EDKJ5.cjs → batch-VFR2MAUE.cjs} +7 -7
  16. package/dist/{batch-IZ5EDKJ5.cjs.map → batch-VFR2MAUE.cjs.map} +1 -1
  17. package/dist/{chunk-XOYNB3YN.js → chunk-23SS4ID4.js} +3 -3
  18. package/dist/{chunk-XOYNB3YN.js.map → chunk-23SS4ID4.js.map} +1 -1
  19. package/dist/{chunk-NHOEXFZT.js → chunk-2HXTNVLM.js} +5 -2
  20. package/dist/chunk-2HXTNVLM.js.map +1 -0
  21. package/dist/{chunk-2C24NSNV.js → chunk-3BBTHFGJ.js} +3 -3
  22. package/dist/{chunk-2C24NSNV.js.map → chunk-3BBTHFGJ.js.map} +1 -1
  23. package/dist/{chunk-NP362THY.cjs → chunk-3F53FLQ7.cjs} +5 -7
  24. package/dist/chunk-3F53FLQ7.cjs.map +1 -0
  25. package/dist/{chunk-3FSBE4PS.js → chunk-3GTN4HOO.js} +10 -6
  26. package/dist/chunk-3GTN4HOO.js.map +1 -0
  27. package/dist/{chunk-7WARNHWW.cjs → chunk-3UMPP553.cjs} +5 -2
  28. package/dist/chunk-3UMPP553.cjs.map +1 -0
  29. package/dist/{chunk-SXH37C7X.cjs → chunk-55LLQFHW.cjs} +6 -6
  30. package/dist/{chunk-SXH37C7X.cjs.map → chunk-55LLQFHW.cjs.map} +1 -1
  31. package/dist/{chunk-QISSETTG.cjs → chunk-5645PMVU.cjs} +6 -4
  32. package/dist/chunk-5645PMVU.cjs.map +1 -0
  33. package/dist/{chunk-JX4LB6XO.js → chunk-5M5GQYBM.js} +617 -250
  34. package/dist/chunk-5M5GQYBM.js.map +1 -0
  35. package/dist/{chunk-KL7ZUMK3.cjs → chunk-64ECM3XM.cjs} +704 -337
  36. package/dist/chunk-64ECM3XM.cjs.map +1 -0
  37. package/dist/{chunk-ZJAYFSVI.js → chunk-7ACS6P5Z.js} +5 -7
  38. package/dist/chunk-7ACS6P5Z.js.map +1 -0
  39. package/dist/{chunk-5A4UFBHW.cjs → chunk-A77UMVMD.cjs} +15 -11
  40. package/dist/chunk-A77UMVMD.cjs.map +1 -0
  41. package/dist/{chunk-ZTP6ZACR.js → chunk-ARC2V7NY.js} +3 -3
  42. package/dist/{chunk-ZTP6ZACR.js.map → chunk-ARC2V7NY.js.map} +1 -1
  43. package/dist/{chunk-WDVG5R2Q.cjs → chunk-AUH2EVWY.cjs} +4 -3
  44. package/dist/chunk-AUH2EVWY.cjs.map +1 -0
  45. package/dist/chunk-BNEUB52I.cjs +55 -0
  46. package/dist/chunk-BNEUB52I.cjs.map +1 -0
  47. package/dist/chunk-BPL5CVZ5.js +53 -0
  48. package/dist/chunk-BPL5CVZ5.js.map +1 -0
  49. package/dist/{chunk-2SFLLOMN.js → chunk-CBBT42VX.js} +3 -3
  50. package/dist/{chunk-2SFLLOMN.js.map → chunk-CBBT42VX.js.map} +1 -1
  51. package/dist/chunk-CHKV2L2Y.cjs +416 -0
  52. package/dist/chunk-CHKV2L2Y.cjs.map +1 -0
  53. package/dist/{chunk-4UYHP6NF.cjs → chunk-DJKGDLYZ.cjs} +93 -63
  54. package/dist/chunk-DJKGDLYZ.cjs.map +1 -0
  55. package/dist/{chunk-354F3OF5.js → chunk-FGGVSHDW.js} +3 -3
  56. package/dist/{chunk-354F3OF5.js.map → chunk-FGGVSHDW.js.map} +1 -1
  57. package/dist/{chunk-6OCJWGVD.cjs → chunk-FTXY4D44.cjs} +4 -4
  58. package/dist/{chunk-6OCJWGVD.cjs.map → chunk-FTXY4D44.cjs.map} +1 -1
  59. package/dist/chunk-INVEGVGO.js +405 -0
  60. package/dist/chunk-INVEGVGO.js.map +1 -0
  61. package/dist/{chunk-2PRGRYEQ.js → chunk-IOBHFP2E.js} +50 -20
  62. package/dist/chunk-IOBHFP2E.js.map +1 -0
  63. package/dist/{chunk-47EACDOC.cjs → chunk-KMFBNKCT.cjs} +4 -4
  64. package/dist/{chunk-47EACDOC.cjs.map → chunk-KMFBNKCT.cjs.map} +1 -1
  65. package/dist/{chunk-DF77RBF4.js → chunk-LCCT5ZXJ.js} +6 -6
  66. package/dist/{chunk-DF77RBF4.js.map → chunk-LCCT5ZXJ.js.map} +1 -1
  67. package/dist/{chunk-G7XBVM3T.cjs → chunk-MHUOVS5I.cjs} +5 -5
  68. package/dist/{chunk-G7XBVM3T.cjs.map → chunk-MHUOVS5I.cjs.map} +1 -1
  69. package/dist/{chunk-CWQPQTUZ.cjs → chunk-NEH3BPFD.cjs} +15 -15
  70. package/dist/{chunk-CWQPQTUZ.cjs.map → chunk-NEH3BPFD.cjs.map} +1 -1
  71. package/dist/{chunk-5AR6O7GW.js → chunk-NK5EOTDY.js} +3 -3
  72. package/dist/{chunk-5AR6O7GW.js.map → chunk-NK5EOTDY.js.map} +1 -1
  73. package/dist/{chunk-S46WJTDL.cjs → chunk-P7QYG4RZ.cjs} +27 -19
  74. package/dist/chunk-P7QYG4RZ.cjs.map +1 -0
  75. package/dist/chunk-PPU2D4FZ.js +19 -0
  76. package/dist/chunk-PPU2D4FZ.js.map +1 -0
  77. package/dist/{chunk-MS3TQSXU.js → chunk-QD6B6W6I.js} +6 -4
  78. package/dist/chunk-QD6B6W6I.js.map +1 -0
  79. package/dist/{chunk-AB4OPJLD.js → chunk-RGVMJU57.js} +3 -3
  80. package/dist/{chunk-AB4OPJLD.js.map → chunk-RGVMJU57.js.map} +1 -1
  81. package/dist/{chunk-NWWTYBD3.cjs → chunk-RTDVSHJR.cjs} +5 -5
  82. package/dist/{chunk-NWWTYBD3.cjs.map → chunk-RTDVSHJR.cjs.map} +1 -1
  83. package/dist/{chunk-5HSMH2ZZ.js → chunk-RZL2W4C3.js} +25 -17
  84. package/dist/chunk-RZL2W4C3.js.map +1 -0
  85. package/dist/chunk-SPEN7RJU.cjs +18 -0
  86. package/dist/{chunk-LO6LIUZ6.cjs.map → chunk-SPEN7RJU.cjs.map} +1 -1
  87. package/dist/{chunk-ZF2LDKQQ.cjs → chunk-U3DW3WDE.cjs} +9 -8
  88. package/dist/chunk-U3DW3WDE.cjs.map +1 -0
  89. package/dist/{chunk-DVP3YMDA.cjs → chunk-U7PRI3XM.cjs} +3 -3
  90. package/dist/{chunk-DVP3YMDA.cjs.map → chunk-U7PRI3XM.cjs.map} +1 -1
  91. package/dist/{chunk-H7G65PNI.js → chunk-VCQ37TC5.js} +3 -3
  92. package/dist/{chunk-H7G65PNI.js.map → chunk-VCQ37TC5.js.map} +1 -1
  93. package/dist/{chunk-SDZ4XDWS.cjs → chunk-VWYFGE27.cjs} +9 -9
  94. package/dist/{chunk-SDZ4XDWS.cjs.map → chunk-VWYFGE27.cjs.map} +1 -1
  95. package/dist/{chunk-RN3XDZGS.js → chunk-WM646HEN.js} +4 -3
  96. package/dist/chunk-WM646HEN.js.map +1 -0
  97. package/dist/chunk-X7CE7Y5W.js +305 -0
  98. package/dist/chunk-X7CE7Y5W.js.map +1 -0
  99. package/dist/{chunk-CGH4BCIY.cjs → chunk-XE3CAY6Z.cjs} +5 -5
  100. package/dist/{chunk-CGH4BCIY.cjs.map → chunk-XE3CAY6Z.cjs.map} +1 -1
  101. package/dist/chunk-XOU3CL7S.cjs +312 -0
  102. package/dist/chunk-XOU3CL7S.cjs.map +1 -0
  103. package/dist/{chunk-NKQVLWDF.js → chunk-YKNBTHMD.js} +3 -3
  104. package/dist/{chunk-NKQVLWDF.js.map → chunk-YKNBTHMD.js.map} +1 -1
  105. package/dist/{compact-session-B7P6EU3Z.cjs → compact-session-NX7HUNTB.cjs} +18 -18
  106. package/dist/{compact-session-B7P6EU3Z.cjs.map → compact-session-NX7HUNTB.cjs.map} +1 -1
  107. package/dist/{compact-session-5HMX6KYT.js → compact-session-Q5IGNFPW.js} +9 -9
  108. package/dist/{compact-session-5HMX6KYT.js.map → compact-session-Q5IGNFPW.js.map} +1 -1
  109. package/dist/{cron-BvxrLW5J.d.cts → cron-Bh3aWgQ2.d.cts} +2 -2
  110. package/dist/{cron-DdKzwJqK.d.ts → cron-CCDxeeFZ.d.ts} +2 -2
  111. package/dist/cron.cjs +21 -20
  112. package/dist/cron.d.cts +4 -3
  113. package/dist/cron.d.ts +4 -3
  114. package/dist/cron.js +20 -19
  115. package/dist/eval.cjs +20 -19
  116. package/dist/eval.cjs.map +1 -1
  117. package/dist/eval.js +19 -18
  118. package/dist/eval.js.map +1 -1
  119. package/dist/fs-session-store-CYNTEPJZ.cjs +20 -0
  120. package/dist/{fs-session-store-S2AXVJIX.cjs.map → fs-session-store-CYNTEPJZ.cjs.map} +1 -1
  121. package/dist/fs-session-store-MYQEVUFL.js +11 -0
  122. package/dist/{fs-session-store-6ZCCJX3K.js.map → fs-session-store-MYQEVUFL.js.map} +1 -1
  123. package/dist/{index-manager-HO456AVW.cjs → index-manager-2KH75BRW.cjs} +7 -7
  124. package/dist/{index-manager-HO456AVW.cjs.map → index-manager-2KH75BRW.cjs.map} +1 -1
  125. package/dist/{index-manager-QDH56FER.js → index-manager-NF2WRLOU.js} +6 -6
  126. package/dist/{index-manager-QDH56FER.js.map → index-manager-NF2WRLOU.js.map} +1 -1
  127. package/dist/index.cjs +93 -273
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +13 -46
  130. package/dist/index.d.ts +13 -46
  131. package/dist/index.js +52 -236
  132. package/dist/index.js.map +1 -1
  133. package/dist/{inject-session-75L3NCNL.js → inject-session-4ROYLZHY.js} +3 -3
  134. package/dist/{inject-session-75L3NCNL.js.map → inject-session-4ROYLZHY.js.map} +1 -1
  135. package/dist/{inject-session-WOH7TPDX.cjs → inject-session-AWKDCGHV.cjs} +4 -4
  136. package/dist/{inject-session-WOH7TPDX.cjs.map → inject-session-AWKDCGHV.cjs.map} +1 -1
  137. package/dist/internal/agent-loop/doom-loop-tracker.d.ts +3 -0
  138. package/dist/internal/agent-loop/prune-tool-results.d.ts +37 -0
  139. package/dist/internal/agent-loop/types.d.ts +6 -0
  140. package/dist/internal/auth/credential-store.d.ts +1 -1
  141. package/dist/internal/auth/types.d.ts +4 -0
  142. package/dist/internal/error-mappers/anthropic.d.ts +3 -0
  143. package/dist/internal/error-mappers/openai-compatible.d.ts +2 -3
  144. package/dist/internal/local-agent/local-agent-dispatch.d.ts +3 -1
  145. package/dist/internal/local-agent/send-queue.d.ts +31 -0
  146. package/dist/internal/mcp/client.d.ts +2 -0
  147. package/dist/internal/mcp/oauth-bearer.d.ts +2 -0
  148. package/dist/internal/memory/adapters/index.cjs +3 -3
  149. package/dist/internal/memory/adapters/index.js +2 -2
  150. package/dist/internal/memory/dreaming/phases.d.ts +15 -0
  151. package/dist/internal/memory/dreaming/run.d.ts +29 -0
  152. package/dist/internal/memory/sdk-memory-peer-loader.d.ts +2 -0
  153. package/dist/internal/memory/storage/index.cjs +51 -35
  154. package/dist/internal/memory/storage/index.d.cts +2 -0
  155. package/dist/internal/memory/storage/index.d.ts +2 -0
  156. package/dist/internal/memory/storage/index.js +4 -4
  157. package/dist/internal/persistence/cwd-mutex.d.cts +4 -2
  158. package/dist/internal/persistence/cwd-mutex.d.ts +4 -2
  159. package/dist/internal/persistence/index.cjs +4 -4
  160. package/dist/internal/persistence/index.js +2 -2
  161. package/dist/internal/persistence/sqlite-session-store.d.cts +25 -0
  162. package/dist/internal/persistence/sqlite-session-store.d.ts +25 -0
  163. package/dist/internal/providers/registry.d.ts +5 -1
  164. package/dist/internal/runtime/skills/binary-on-path.d.ts +1 -0
  165. package/dist/internal/runtime/skills/discover-skills.d.ts +2 -0
  166. package/dist/mcp-auth.cjs +25 -307
  167. package/dist/mcp-auth.cjs.map +1 -1
  168. package/dist/mcp-auth.js +5 -305
  169. package/dist/mcp-auth.js.map +1 -1
  170. package/dist/models.cjs +11 -11
  171. package/dist/models.js +4 -4
  172. package/dist/persistence.cjs +9 -4
  173. package/dist/persistence.cjs.map +1 -1
  174. package/dist/persistence.d.cts +744 -40
  175. package/dist/persistence.d.ts +744 -40
  176. package/dist/persistence.js +3 -2
  177. package/dist/persistence.js.map +1 -1
  178. package/dist/providers.cjs +6 -6
  179. package/dist/providers.js +4 -4
  180. package/dist/sandbox/docker-sandbox.d.cts +58 -0
  181. package/dist/sandbox/docker-sandbox.d.ts +58 -0
  182. package/dist/sandbox/index.cjs +129 -1
  183. package/dist/sandbox/index.cjs.map +1 -1
  184. package/dist/sandbox/index.d.cts +1 -0
  185. package/dist/sandbox/index.d.ts +1 -0
  186. package/dist/sandbox/index.js +132 -5
  187. package/dist/sandbox/index.js.map +1 -1
  188. package/dist/{sdk-agent-4-dXI2GS.d.cts → sdk-agent-BA_6_wtc.d.cts} +57 -4
  189. package/dist/{sdk-agent-CTOJlWA_.d.ts → sdk-agent-CnwPODYH.d.ts} +57 -4
  190. package/dist/session-store-Dh07jr9N.d.cts +209 -0
  191. package/dist/session-store-Dh07jr9N.d.ts +209 -0
  192. package/dist/skills.cjs +4 -4
  193. package/dist/skills.js +1 -1
  194. package/dist/sqlite-session-store-BbcpKY1L.d.ts +64 -0
  195. package/dist/sqlite-session-store-TKXtT4ON.d.cts +64 -0
  196. package/dist/subagents-loader-KLWF4QPD.cjs +16 -0
  197. package/dist/{subagents-loader-TXOA5EJJ.cjs.map → subagents-loader-KLWF4QPD.cjs.map} +1 -1
  198. package/dist/subagents-loader-SXETS2F2.js +7 -0
  199. package/dist/{subagents-loader-Z744TAKD.js.map → subagents-loader-SXETS2F2.js.map} +1 -1
  200. package/dist/subagents-loader.cjs +2 -2
  201. package/dist/subagents-loader.d.cts +3 -2
  202. package/dist/subagents-loader.d.ts +3 -2
  203. package/dist/subagents-loader.js +1 -1
  204. package/dist/types/agent.d.ts +20 -1
  205. package/dist/types/budget-tracker.d.ts +6 -2
  206. package/dist/types/mcp.d.ts +14 -0
  207. package/dist/types/run.d.ts +42 -3
  208. package/dist/workflow.d.cts +1 -1
  209. package/dist/workflow.d.ts +1 -1
  210. package/docs/adr/mutation-testing-scope.md +66 -0
  211. package/docs/error-codes.md +48 -45
  212. package/docs/harness-capability-map.md +60 -6
  213. package/package.json +1 -1
  214. package/dist/chunk-2PRGRYEQ.js.map +0 -1
  215. package/dist/chunk-3FSBE4PS.js.map +0 -1
  216. package/dist/chunk-4UYHP6NF.cjs.map +0 -1
  217. package/dist/chunk-5A4UFBHW.cjs.map +0 -1
  218. package/dist/chunk-5HSMH2ZZ.js.map +0 -1
  219. package/dist/chunk-6CXRYPG7.cjs +0 -211
  220. package/dist/chunk-6CXRYPG7.cjs.map +0 -1
  221. package/dist/chunk-7WARNHWW.cjs.map +0 -1
  222. package/dist/chunk-JX4LB6XO.js.map +0 -1
  223. package/dist/chunk-KL7ZUMK3.cjs.map +0 -1
  224. package/dist/chunk-LO6LIUZ6.cjs +0 -18
  225. package/dist/chunk-MS3TQSXU.js.map +0 -1
  226. package/dist/chunk-MSEL5E5P.js +0 -204
  227. package/dist/chunk-MSEL5E5P.js.map +0 -1
  228. package/dist/chunk-NHOEXFZT.js.map +0 -1
  229. package/dist/chunk-NP362THY.cjs.map +0 -1
  230. package/dist/chunk-Q5EWJPRY.js +0 -18
  231. package/dist/chunk-Q5EWJPRY.js.map +0 -1
  232. package/dist/chunk-QISSETTG.cjs.map +0 -1
  233. package/dist/chunk-RN3XDZGS.js.map +0 -1
  234. package/dist/chunk-S46WJTDL.cjs.map +0 -1
  235. package/dist/chunk-WDVG5R2Q.cjs.map +0 -1
  236. package/dist/chunk-ZF2LDKQQ.cjs.map +0 -1
  237. package/dist/chunk-ZJAYFSVI.js.map +0 -1
  238. package/dist/fs-session-store-6ZCCJX3K.js +0 -11
  239. package/dist/fs-session-store-S2AXVJIX.cjs +0 -20
  240. package/dist/subagents-loader-TXOA5EJJ.cjs +0 -16
  241. package/dist/subagents-loader-Z744TAKD.js +0 -7
@@ -1,41 +1,745 @@
1
+ export { S as SqliteSessionStore, a as SqliteSessionStoreOptions, w as withCwdMutex } from './sqlite-session-store-BbcpKY1L.js';
2
+ import { T as TheokitAgentError } from './errors-base-B5g01neU.js';
3
+ import { z } from 'zod';
4
+ export { T as TranscriptBlock, b as TranscriptMessage } from './session-store-Dh07jr9N.js';
5
+
1
6
  /**
2
- * Published as `@theokit/sdk/persistence`.
3
- *
4
- * Public persistence primitives (V2-3 — Theo Harness Capability Map, Theme G).
5
- *
6
- * Promotes the consumer-grade persistence helpers from `internal/persistence`
7
- * to a STABLE, semver-protected public sub-path so consumers (e.g. a code
8
- * assistant's eval harness or config store) adopt durable JSONL persist/resume,
9
- * audited atomic-write, and resilient-SQLite bootstrap WITHOUT coupling to the
10
- * semver-exempt `@theokit/sdk/internal/persistence` path.
11
- *
12
- * Several of these were extracted FROM a real consumer (theocode's SWE-bench
13
- * harness — see the `reference:` comments in `internal/persistence/jsonl.ts`);
14
- * this sub-path lets that consumer adopt its own contributed pattern back from a
15
- * stable home. DTS is generated via tsc (this barrel reaches `internal/`, like
16
- * `retry`/`compaction` — see `tsconfig.tools-dts.json`).
17
- */
18
- export type { AtomicWriteJsonOptions } from "./internal/persistence/atomic-write.js";
19
- export { atomicWriteJson, atomicWriteTempTarget, atomicWriteText, replaceFileAtomic, } from "./internal/persistence/atomic-write.js";
20
- export { withCwdMutex } from "./internal/persistence/cwd-mutex.js";
21
- export type { FileLockOptions } from "./internal/persistence/file-lock.js";
22
- export { withFileLock } from "./internal/persistence/file-lock.js";
23
- export { sanitizeFts5Query } from "./internal/persistence/fts5-sanitize.js";
24
- export { appendJsonl, JsonlParseError, loadJsonl, readJsonlIds, } from "./internal/persistence/jsonl.js";
25
- export { type ListSessionsOptions, listSessions, type SessionIdSource, type SessionListing, } from "./internal/persistence/list-sessions.js";
26
- export { PersistenceSchema } from "./internal/persistence/persistence-schema.js";
27
- export { classifySessionArtifact, type SessionArtifact, } from "./internal/persistence/session-artifacts.js";
28
- export { encodeProjectDir, legacyTranscriptPath, sessionUuidFor, transcriptPath, transcriptRoot, } from "./internal/persistence/session-transcript.js";
29
- export { acquireSessionWriter, SessionBusyError, type SessionWriterLease, sessionHasWriter, } from "./internal/persistence/session-writer.js";
30
- export type { OpenSqliteResilientOptions, ResilientSqliteDb, } from "./internal/persistence/sqlite-open.js";
31
- export { isCorruptionError, openSqliteResilient } from "./internal/persistence/sqlite-open.js";
32
- export type { WalApplyResult } from "./internal/persistence/sqlite-wal.js";
33
- export { applyWalWithFallback } from "./internal/persistence/sqlite-wal.js";
34
- /**
35
- * @deprecated Renamed to {@link LiveTranscriptError}. The root barrel exports a DIFFERENT class
36
- * under this name (`session-guard.ts`, about refusing to destroy a session), and a consumer holding
37
- * both got two classes `instanceof` could not tell apart while `err.name` matched both. This alias
38
- * keeps existing imports working; it will be removed in the next major.
39
- */
40
- export { type ForkTranscriptOptions, forkTranscript, LiveTranscriptError, LiveTranscriptError as LiveSessionError, type ReadJsonlTailOptions, readJsonlTail, } from "./internal/persistence/transcript-ops.js";
41
- export type { TranscriptBlock, TranscriptMessage } from "./types/session-record.js";
7
+ * M107 — temp-file creation control, shared by `replaceFileAtomic`,
8
+ * `atomicWriteJson` and `atomicWriteText`.
9
+ *
10
+ * Both fields are OPTIONAL and the default is byte-identical to the behavior
11
+ * before M107. See `replaceFileAtomic` for why the mode reassertion
12
+ * is conditional.
13
+ *
14
+ */
15
+ interface AtomicWriteFileOptions {
16
+ /**
17
+ * Permission bits of the created file. Default: `0o600`, the previous fixed
18
+ * literal — filtered by the `umask`, as it always was. When provided, the mode is
19
+ * reasserted on the descriptor, so the `umask` cannot silently
20
+ * clearing bits the caller asked for.
21
+ */
22
+ mode?: number;
23
+ /**
24
+ * Create the temp file with `wx` (exclusive creation) instead of `w`. Default:
25
+ * `false` — the previous flag. With `true`, a pre-existing temp file becomes
26
+ * `EEXIST` instead of being truncated.
27
+ */
28
+ exclusive?: boolean;
29
+ }
30
+ /**
31
+ * U-9 — the file a leftover temp was replacing, or `undefined` if this is not one of ours.
32
+ *
33
+ * A crash between the open and the rename leaves `<file>.<pid>.<hex>.tmp` behind, and nothing here
34
+ * collects it: this module creates temps and has no opinion about sweeping them. A consumer that
35
+ * wants to has to know the format — which lived only in the implementation, so one copied it out of
36
+ * a compiled chunk as a regex. That copy would have gone on reporting "nothing to collect" the day
37
+ * the format changed, which is the quietest possible way for a cleanup to stop working.
38
+ *
39
+ * Deliberately strict: the pid must be digits and the suffix exactly 16 hex characters. Matching any
40
+ * `.tmp` would claim editors' swap files and other tools' scratch, on a path whose purpose is
41
+ * deleting them.
42
+ */
43
+ declare function atomicWriteTempTarget(name: string): string | undefined;
44
+ /**
45
+ * Atomic file replacement: write content to a per-call unique tmp path,
46
+ * fsync, then rename over the target. Crash mid-write leaves either the old
47
+ * file intact or the new file complete — never a half-written file.
48
+ *
49
+ * The tmp suffix is `<pid>.<rand>.tmp` so parallel processes (and concurrent
50
+ * burst writes within one process) never collide on the same tmp path — a
51
+ * race that would manifest as `ENOENT` on `rename` after the rival process
52
+ * already moved its tmp into place.
53
+ *
54
+ * Mirrors peer-project's `replaceFileAtomic` from
55
+ * `reference/peer-project/packages/memory-host-sdk/src/host/fs-utils.ts` with
56
+ * the multi-writer robustness fix.
57
+ *
58
+ * ## M107 — `options` is optional, and the default is byte-identical
59
+ *
60
+ * The third parameter is additive: every earlier caller still compiles and
61
+ * writing exactly the same bytes, with the same mode, to the same path.
62
+ *
63
+ * The mode reassertion (`handle.chmod`) is **conditional on `mode !== undefined`**,
64
+ * and that is not cosmetic. `open`'s mode argument is filtered by the `umask`,
65
+ * which only CLEARS bits — measured in this codebase before the change:
66
+ *
67
+ * ```
68
+ * umask 0o002 -> 0o600 umask 0o022 -> 0o600 umask 0o200 -> 0o400
69
+ * ```
70
+ *
71
+ * An unconditional `chmod` would take the third case from `0o400` to `0o600` — a
72
+ * an on-disk change for every caller that asked for nothing, including external
73
+ * consumers. When the caller DOES ask for a mode, however, letting the `umask` decide
74
+ * silently is the defect this parameter exists to close; hence the reassertion.
75
+ *
76
+ * It goes on the DESCRIPTOR, before the `rename`, never after: chmod-ing the final
77
+ * final one would leave a window where it carries the `umask`'s mode — a known anti-pattern.
78
+ * The chosen shape passes the mode as an `open` argument.
79
+ *
80
+ */
81
+ declare function replaceFileAtomic(filePath: string, content: string, options?: AtomicWriteFileOptions): Promise<void>;
82
+ /**
83
+ * Options for `atomicWriteJson`.
84
+ *
85
+ */
86
+ interface AtomicWriteJsonOptions extends AtomicWriteFileOptions {
87
+ /** Indent passed to `JSON.stringify`. Default: 2. */
88
+ indent?: number;
89
+ /** Whether to append a trailing newline (POSIX convention). Default: true. */
90
+ trailingNewline?: boolean;
91
+ }
92
+ /**
93
+ * Typed JSON atomic write helper.
94
+ *
95
+ * Serializes `data` to JSON, then delegates to `replaceFileAtomic`. The
96
+ * parent directory is auto-created (recursive `mkdir`) to make this helper
97
+ * safe for callers who haven't ensured the directory exists (EC-4 in the
98
+ * persistence-state-hardening plan).
99
+ *
100
+ * Throws `TypeError` on circular refs or `undefined` data (propagates from
101
+ * `JSON.stringify`).
102
+ *
103
+ */
104
+ declare function atomicWriteJson<T>(filePath: string, data: T, options?: AtomicWriteJsonOptions): Promise<void>;
105
+ /**
106
+ * Atomic text write. Same crash-safety guarantees as `replaceFileAtomic` +
107
+ * auto-mkdir of the parent directory. Written for `theokit-migrate-config` (T4.1, EC-2 MUST FIX) so
108
+ * a crash mid-migration left previous MD files intact rather than corrupting them; that CLI is no
109
+ * longer published, and the guarantee is the reason every other caller uses this too.
110
+ *
111
+ */
112
+ declare function atomicWriteText(filePath: string, content: string): Promise<void>;
113
+
114
+ /**
115
+ * Cross-process file lock helper (ADR D61).
116
+ *
117
+ * Uses `proper-lockfile` (optional peer dep) for cross-process locks. When
118
+ * the peer dep is absent, falls back to `withCwdMutex` (in-process only)
119
+ * with a one-shot stderr warning.
120
+ *
121
+ * EC-1 fix: uses a companion `<path>.lock` file with `realpath: false` so
122
+ * `withFileLock` works even when the target `path` does not exist yet.
123
+ * Without this, fresh installs that lock-then-create would crash with ENOENT.
124
+ *
125
+ * @internal
126
+ */
127
+ /**
128
+ * Retry and staleness tuning for `withFileLock`.
129
+ *
130
+ * Every field is optional and the defaults are applied per call, so `undefined` means the default
131
+ * rather than "no limit". They are handed to `proper-lockfile` and have NO effect on the fallback
132
+ * path: when that package is absent, `withFileLock` degrades to the in-process mutex, which
133
+ * queues indefinitely and never goes stale.
134
+ *
135
+ * `stale` (default 30000 ms) is how old a lock file must be before it is treated as abandoned by a
136
+ * dead process and broken. Setting it below the longest a legitimate holder can run is what turns
137
+ * a slow operation into two concurrent ones.
138
+ *
139
+ * `retries` (default 5) is the number of acquisition attempts AFTER the first, and `retryFactor`
140
+ * (default 1.5) the backoff multiplier between them. The per-attempt wait is clamped to
141
+ * 100–5000 ms, which the caller cannot change. Exhausting the retries rejects — it does not fall
142
+ * through to running `fn` unlocked.
143
+ *
144
+ * @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for
145
+ * back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.
146
+ */
147
+ interface FileLockOptions {
148
+ /** Stale lock timeout in ms. Default 30_000 (30s). */
149
+ stale?: number;
150
+ /** Max retries on busy lock. Default 5. */
151
+ retries?: number;
152
+ /** Backoff factor between retries. Default 1.5. */
153
+ retryFactor?: number;
154
+ }
155
+ /**
156
+ * Run `fn` while holding an OS-level cross-process lock on `path`.
157
+ *
158
+ * If `proper-lockfile` is installed, uses it with a companion `<path>.lock`
159
+ * file (`realpath: false`, so target file does NOT need to exist yet).
160
+ * Otherwise falls back to in-process `withCwdMutex` and prints a one-shot
161
+ * stderr warning telling the user to install `proper-lockfile` for
162
+ * cross-process safety.
163
+ *
164
+ * The lock is released even when `fn` throws.
165
+ *
166
+ */
167
+ declare function withFileLock<T>(path: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
168
+
169
+ /**
170
+ * FTS5 query sanitization + CJK detection (ADR D64).
171
+ *
172
+ * Port of Hermes' 6-step sanitizer (`hermes_state.py:1797-1847`). Prevents
173
+ * crashes on user inputs with hyphens, dots, underscores, and unmatched
174
+ * specials. Auto-quotes identifier-shaped tokens so `error-code` finds
175
+ * literal `error-code` instead of `error AND code`.
176
+ *
177
+ * CJK detection returns true for inputs containing characters in the main
178
+ * CJK Unicode ranges (Chinese, Japanese, Korean). CJK trigram routing is
179
+ * deferred to v1.4 — callers receiving `containsCjk === true` should
180
+ * short-circuit to an empty result or LIKE fallback for v1.3.
181
+ *
182
+ * @internal
183
+ */
184
+ /**
185
+ * Six-step FTS5 query sanitizer. Returns a query string safe for passing
186
+ * to `WHERE <fts5_table> MATCH ?` as a parameter.
187
+ *
188
+ * Returns empty string when the input contains only specials (caller MUST
189
+ * short-circuit to avoid runtime SQL error — EC-3).
190
+ *
191
+ */
192
+ declare function sanitizeFts5Query(query: string): string;
193
+
194
+ /** Raised when a JSONL line is not valid JSON or is not a JSON object. Carries the 1-based line number. */
195
+ declare class JsonlParseError extends TheokitAgentError {
196
+ readonly line: number;
197
+ readonly name = "JsonlParseError";
198
+ constructor(message: string, line: number);
199
+ }
200
+ /**
201
+ * Parse a JSONL file into rows. Blank lines are skipped. A malformed or
202
+ * non-object line throws {@link JsonlParseError} naming the 1-based line. When
203
+ * `map` is provided, each raw object is mapped to the typed row (the SWE-bench
204
+ * schema lives in the caller's `map`, per M6 ADR D3).
205
+ */
206
+ declare function loadJsonl<T = Record<string, unknown>>(path: string, opts?: {
207
+ map?: (raw: Record<string, unknown>, lineNumber: number) => T;
208
+ /**
209
+ * M81 — tolerate a truncated LAST line (a crash artifact: the process died mid-write).
210
+ *
211
+ * Opt-in on purpose. As a default it would also swallow corruption in the MIDDLE of the file,
212
+ * turning loud data loss into silent data loss — the wrong trade for a session store.
213
+ */
214
+ tolerateTrailingPartialLine?: boolean;
215
+ }): T[];
216
+ /**
217
+ * Append one record as a whole `\n`-terminated JSON line. Creates the parent dir
218
+ * if missing. `appendFileSync` is synchronous, so within a single Node process
219
+ * the event loop serializes writes and each call writes its line atomically —
220
+ * interleave-safe for the bounded-concurrency batch runner.
221
+ *
222
+ * reference: swebench-batch.ts:192 (mkdir-before-append), :205 (per-line flush).
223
+ */
224
+ declare function appendJsonl(path: string, record: unknown): void;
225
+ /**
226
+ * Read the set of keys from an existing JSONL file for which `keyFn(parsed)`
227
+ * returns a non-empty string. Used to resume a crashed batch by skipping rows
228
+ * already persisted with a successful result. A trailing partial line from an
229
+ * interrupted append is tolerated (skipped, not thrown), and a missing file
230
+ * yields an empty set.
231
+ *
232
+ * reference: swebench-batch.ts:113 (readDoneIds), :129 (success-only),
233
+ * :131 (tolerate partial line).
234
+ */
235
+ declare function readJsonlIds(path: string, keyFn: (parsed: Record<string, unknown>) => string | undefined): Set<string>;
236
+
237
+ /**
238
+ * #598 — enumerate the sessions on disk, saying where each id came from.
239
+ *
240
+ * ## Why this exists
241
+ *
242
+ * Every transcript helper this package published mapped FORWARD — `sessionUuidFor`,
243
+ * `transcriptPath`, `legacyTranscriptPath`, `encodeProjectDir`, `transcriptRoot`. **None enumerated.**
244
+ * So a consumer that needed the list rebuilt it, and two independent ones did, in opposite
245
+ * directions, and both got it wrong on the first attempt:
246
+ *
247
+ * | consumer | direction | what broke |
248
+ * |---|---|---|
249
+ * | `@theokit/agents` | file → id | derived the id from the file STEM |
250
+ * | a downstream agent runtime | id → file | compared session ids against filenames |
251
+ *
252
+ * The second measured, against 5.0.1: the protected set never matched, **so neither the registered
253
+ * sessions nor the live one were protected and everything classified as an orphan** — a garbage
254
+ * collector that would delete the session in use.
255
+ *
256
+ * ## Why documentation was not the fix
257
+ *
258
+ * `b85dab4` documented the rename thoroughly. One of those two consumers had READ it and broke
259
+ * anyway; the other had not and broke identically. Two samples, one informed and one not, same
260
+ * defect. If reading were sufficient the informed sample would have survived — so the cause is the
261
+ * shape of the surface, not the reader.
262
+ *
263
+ * And it cannot be closed by publishing an inverse: the filename is a UUIDv8 over SHA-256, which has
264
+ * none. Whoever holds the file must read the id from INSIDE it, and that the id lives in the first
265
+ * record is this package's knowledge. Both consumers had to discover it by reading bytes.
266
+ *
267
+ * ## Why the id is not just a string
268
+ *
269
+ * An `id: string` that is sometimes read from the transcript and sometimes inferred from the
270
+ * filename is the same defect one layer up: a value that reads as authoritative and occasionally is
271
+ * not. That is precisely what produced the garbage-collector failure above.
272
+ *
273
+ * So every entry carries {@link SessionListing.idSource}, and an entry whose id could not be
274
+ * determined is `undefined` rather than guessed. A caller deciding what to DELETE needs to tell
275
+ * "this session is not registered" from "I could not read this file" — those look identical in a
276
+ * plain list and mean opposite things.
277
+ *
278
+ * @public
279
+ */
280
+ /** How the `id` on a {@link SessionListing} was obtained. */
281
+ type SessionIdSource =
282
+ /** Read from the `sessionId` of the transcript's first well-formed record. Authoritative. */
283
+ "transcript"
284
+ /**
285
+ * Not determined: the file was unreadable, empty, or its first records carried no `sessionId`.
286
+ * `id` is `undefined` — deliberately not the filename, which is a hash and not an id.
287
+ */
288
+ | "unavailable";
289
+ /** One session found on disk. */
290
+ interface SessionListing {
291
+ /**
292
+ * The session id, or `undefined` when {@link idSource} is `"unavailable"`.
293
+ *
294
+ * Never derived from the filename. The filename is a UUIDv8 over SHA-256 of the id, so treating
295
+ * it as the id is the exact defect this function exists to prevent.
296
+ */
297
+ readonly id: string | undefined;
298
+ /** Where {@link id} came from. Check this before acting on `id`. */
299
+ readonly idSource: SessionIdSource;
300
+ /** Absolute path to the `.jsonl` transcript. */
301
+ readonly transcript: string;
302
+ /** Last modification time of the transcript. */
303
+ readonly modifiedAt: Date;
304
+ }
305
+ /** Options for {@link listSessions}. */
306
+ interface ListSessionsOptions {
307
+ /**
308
+ * Root under which `projects/<encoded-cwd>/` lives. Defaults to {@link transcriptRoot}, which
309
+ * honours `THEOKIT_HOME`.
310
+ */
311
+ readonly baseDir?: string;
312
+ /**
313
+ * How many bytes of each transcript to read looking for the id. Default 65536.
314
+ *
315
+ * A cap rather than a full read because a transcript grows without bound and the id is in the
316
+ * first record. A session whose id is not in the first 64KB reports `"unavailable"` rather than
317
+ * being read to the end — bounded work with a declared outcome beats an unbounded read.
318
+ */
319
+ readonly idScanBytes?: number;
320
+ }
321
+ /**
322
+ * Every session transcript under `<baseDir>/projects/<encoded-cwd>/`, with the id read from inside
323
+ * each file.
324
+ *
325
+ * An absent directory yields `[]` — a cwd with no sessions is the common case, not an error.
326
+ *
327
+ * ```ts
328
+ * for (const s of await listSessions(process.cwd())) {
329
+ * if (s.idSource === "unavailable") continue; // do not guess, and do not delete
330
+ * …
331
+ * }
332
+ * ```
333
+ */
334
+ declare function listSessions(cwd: string, options?: ListSessionsOptions): Promise<readonly SessionListing[]>;
335
+
336
+ /**
337
+ * Shared Zod schema for the `{ backend: "memory" | "json", dir? }` options
338
+ * used by both `Workflow` snapshots and `Cache` semantic store persistence.
339
+ * Extracted to remove the cross-module clone flagged by jscpd.
340
+ *
341
+ * `PersistenceSchema` is re-exported from the semver-protected `@theokit/sdk/persistence` barrel
342
+ * and from the semver-exempt `@theokit/sdk/internal/persistence` alias.
343
+ *
344
+ * NOTE — no internal-visibility tag in this block. `tsconfig.base.json` sets `stripInternal: true`,
345
+ * and TypeScript scans EVERY leading comment range of the declaration that follows, including the
346
+ * import right below this one. The tag that used to sit here deleted that import from the emitted
347
+ * `.d.ts`, leaving the types it binds unresolvable for any consumer running type-aware lint
348
+ * (usetheodev/theokit-sdk#283 records the same trap on a declaration).
349
+ */
350
+
351
+ /**
352
+ * `persistence?` opt-in JSON disk backend with `dir` required when chosen.
353
+ *
354
+ * `.refine()` restored after Zod v4-only migration (plan zod-v4-migration
355
+ * T1.1, ADR D4). With single v4 instance across workspace, ZodEffects
356
+ * inside `z.object()` works correctly.
357
+ */
358
+ declare const PersistenceSchema: z.ZodOptional<z.ZodObject<{
359
+ backend: z.ZodEnum<{
360
+ memory: "memory";
361
+ json: "json";
362
+ }>;
363
+ dir: z.ZodOptional<z.ZodString>;
364
+ }, z.core.$strip>>;
365
+
366
+ /**
367
+ * The kinds of file this SDK leaves in a project's transcript directory.
368
+ *
369
+ * - `transcript` — the session itself (`transcriptPath`).
370
+ * - `writer-lock` — the cross-process writer lease (`session-writer.ts`, `<file>.writer.lock`).
371
+ * - `lock-directory` — `withFileLock`'s companion, taken by `mkdir`, so it is a DIRECTORY.
372
+ * - `temp` — what `replaceFileAtomic` leaves when a process dies between the open and the rename.
373
+ */
374
+ type SessionArtifact = "transcript" | "writer-lock" | "lock-directory" | "temp";
375
+ /**
376
+ * U-1 — what is this entry, if it is one of ours?
377
+ *
378
+ * The SDK writes four kinds of file into a project directory and reasons about none of them
379
+ * afterwards: there is no retention, no collector, and there was no way even to ask what an entry
380
+ * IS. A consumer wanting to reclaim disk had to re-derive the suffixes by reading this source — and
381
+ * one did, which means a suffix changing here would have left its classifier silently mislabelling
382
+ * files on a path that deletes them.
383
+ *
384
+ * This is deliberately NOT a garbage collector. Retention is policy — how many days, how many to
385
+ * keep, which session is live, whether to delete at all — and policy belongs to the application,
386
+ * which is the only one that can know. What belongs here is the half only the SDK can answer.
387
+ *
388
+ * `undefined` means "not written by this SDK", and it is the answer that matters most: a caller
389
+ * deleting what it does not recognise is how an editor's swap file gets collected. The `temp` case
390
+ * defers to {@link atomicWriteTempTarget} rather than matching `.tmp`, for exactly that reason.
391
+ */
392
+ declare function classifySessionArtifact(name: string, isDirectory: boolean): SessionArtifact | undefined;
393
+
394
+ /** Claude Code path convention: cwd with every non-alphanumeric replaced by `-`. */
395
+ declare function encodeProjectDir(cwd: string): string;
396
+ /**
397
+ * Root of the transcript state.
398
+ *
399
+ * `THEOKIT_HOME` wins; the fallback is `~/.theokit`. Home-anchored on purpose, and NOT
400
+ * `paths.ts`'s `getTheokitHome(cwd)`: that one falls back to `<cwd>/.theokit`, and switching
401
+ * by it would move the transcript of everyone who does **not** set the variable. The sibling with the
402
+ * right shape is `catalog-source-models-dev.ts` (M94 ADR-2).
403
+ *
404
+ * Before M94 this function ignored the env var, so whoever set it had their state split in
405
+ * two silently — sessions stayed in `~/.theokit` while the rest of the SDK migrated.
406
+ */
407
+ declare function transcriptRoot(): string;
408
+ /**
409
+ * The transcript filename for an agent id — always a UUID.
410
+ *
411
+ * #400: the Claude Code CLI resumes a transcript ONLY when its basename is a UUID. Measured against
412
+ * CLI 2.1.236: `<uuid>.jsonl` resumes; `agent-<uuid>.jsonl` and `billing-bot.jsonl` are both ignored,
413
+ * with no error — the session simply is not offered. So a session written under a human-readable
414
+ * agent id was invisible to `claude --continue`, which is the interoperability this project claims
415
+ * as its difference from a proprietary session store.
416
+ *
417
+ * An id that is ALREADY a UUID passes through verbatim, so a transcript Claude Code wrote keeps its
418
+ * own name and the two directions stay symmetric. Anything else is derived with a UUIDv8, which is
419
+ * deterministic: the same agent id always yields the same filename, so nothing has to be persisted
420
+ * to map one back to the other.
421
+ *
422
+ * A DERIVED name is version 8 — RFC 9562's slot for a UUID whose bits come from an
423
+ * implementation-defined scheme, which is exactly what this is. The obvious alternative was v5, but
424
+ * RFC 4122 fixes v5's hash to SHA-1, and reaching for SHA-1 in 2026 means either shipping a weak
425
+ * primitive or arguing with every scanner that flags it, forever. v8 lets the hash be SHA-256 and
426
+ * lets the version nibble say honestly that this is our scheme rather than a standard one.
427
+ *
428
+ * There is no security claim here either way — no secret, no signature, and nothing an attacker
429
+ * gains by colliding two agent ids inside one consumer's own directory. The hash is a naming
430
+ * function. The point of preferring SHA-256 is not that SHA-1 was exploitable here; it is that a
431
+ * weak primitive in the tree costs a permanent argument with every scanner that sees it.
432
+ *
433
+ * The version nibble was measured, not assumed: CLI 2.1.236 resumes a v8-named transcript exactly as
434
+ * it does a v4 one.
435
+ */
436
+ declare function sessionUuidFor(id: string): string;
437
+ /** The `.jsonl` path for a session: `<baseDir>/projects/<encoded-cwd>/<session-uuid>.jsonl`. */
438
+ declare function transcriptPath(baseDir: string, cwd: string, sessionId: string): string;
439
+ /**
440
+ * The path this session used BEFORE #400 made transcript filenames UUIDs.
441
+ *
442
+ * Kept so an existing transcript is never orphaned: {@link FsSessionStore} prefers this file when it
443
+ * exists, so history written under the old scheme keeps accumulating in the same file instead of
444
+ * being silently abandoned for a fresh one under the new name.
445
+ */
446
+ declare function legacyTranscriptPath(baseDir: string, cwd: string, sessionId: string): string;
447
+
448
+ /**
449
+ * M81 — single-writer lease for a session transcript.
450
+ *
451
+ * ## The problem
452
+ *
453
+ * Nothing stops two processes appending to the same JSONL transcript. The concrete case: `exec
454
+ * resume --last` can write into the TUI's live session. Two interleaved appends to an append-only
455
+ * file produce lines that are each individually valid and whose SEQUENCE is fiction — and nothing
456
+ * reports it, because every line parses.
457
+ *
458
+ * ## Why an exclusive lockfile rather than `withFileLock`
459
+ *
460
+ * The plan's ADR D2 said to compose `withFileLock`, and that was the right instinct — do not build a
461
+ * second lock mechanism. It turned out not to fit the SHAPE: `withFileLock(path, fn)` is
462
+ * scope-based — it holds the lock for the duration of a callback. A session lease is **held across
463
+ * turns**, for as long as the process owns the session, with an explicit `release()`. Wrapping the
464
+ * whole session lifetime in a callback would invert control of the entire agent loop.
465
+ *
466
+ * So this uses the same underlying primitive `withFileLock` uses (an exclusive-create lockfile,
467
+ * `wx`) with lease semantics on top. That keeps the mechanism single — the file-existence lock —
468
+ * while giving it the lifetime the caller needs. The deviation from D2 is recorded here because the
469
+ * plan's rationale (no second mechanism) still holds; only its shape assumption did not.
470
+ *
471
+ * ## Fail fast, never wait
472
+ *
473
+ * A second writer that WAITED would block `exec` behind a TUI session that can last hours. The typed
474
+ * error lets the caller choose: fork to a new id, or give up with a real diagnosis.
475
+ *
476
+ * @internal
477
+ */
478
+
479
+ /**
480
+ * M81 — another process already holds the writer lease for this session.
481
+ *
482
+ * Carries `sessionPath` because knowing WHICH session is busy is what lets the caller decide between
483
+ * forking and waiting for the user to close the TUI (`rules/error-handling.md § 2` — context enough
484
+ * to act on).
485
+ */
486
+ declare class SessionBusyError extends TheokitAgentError {
487
+ readonly sessionPath: string;
488
+ readonly name = "SessionBusyError";
489
+ constructor(sessionPath: string);
490
+ }
491
+ /** A held writer lease. `release()` is idempotent. */
492
+ interface SessionWriterLease {
493
+ readonly sessionPath: string;
494
+ release(): Promise<void>;
495
+ /**
496
+ * Re-stamp the ownership record, so a **live** owner never crosses the staleness window.
497
+ *
498
+ * Idempotent and cheap: one `write` of a ~80-byte JSON. Call it on the path that already writes to
499
+ * the session — appending a turn — and the cross-host window stops being a lie about liveness.
500
+ *
501
+ * A no-op after `release()`: renewing a lease you no longer hold would re-create the lock file and
502
+ * hand this process ownership it gave up. That is the one direction of this API that could
503
+ * *create* the double-writer it exists to prevent.
504
+ */
505
+ renew(): void;
506
+ }
507
+ /**
508
+ * Does the session have a writer **right now**? A query that does NOT take the lease.
509
+ *
510
+ * M95 — it exists because asking by taking creates the very contention it meant to detect: two
511
+ * processes querying a **free** session at the same time made one of them lose, and the consumer
512
+ * forked for no reason. Measured in adversarial review: `RACE: spurious forks = 1`.
513
+ *
514
+ * It is a snapshot, not a guarantee: between the query and the real acquisition someone may take
515
+ * the session. Callers needing the guarantee use {@link acquireSessionWriter}; callers needing to
516
+ * **decide an id before opening anything** use this, and handle the race where it shows up.
517
+ *
518
+ */
519
+ declare function sessionHasWriter(sessionPath: string): boolean;
520
+ /**
521
+ * Take the exclusive writer lease for `sessionPath`, or reject with {@link SessionBusyError}.
522
+ *
523
+ * The lock is a sibling `.lock` file created with `wx` — the same file-existence primitive the
524
+ * SDK's `withFileLock` builds on. Exclusivity comes from the filesystem, so it holds across
525
+ * processes, not just across async tasks in one process.
526
+ */
527
+ declare function acquireSessionWriter(sessionPath: string): Promise<SessionWriterLease>;
528
+
529
+ /**
530
+ * Resilient SQLite open (plan m0-foundation-expose-primitives, M0-5).
531
+ *
532
+ * Generalizes the driver-load + WAL-apply + corruption-recovery logic that was
533
+ * duplicated (byte-identical) across `sdk/internal/memory/index-db.ts` and
534
+ * `sdk-memory/internal/index/index-db.ts`. Schema-agnostic: the caller applies
535
+ * its own PRAGMA/SCHEMA via the `onOpen` callback.
536
+ *
537
+ * Corruption recovery (EC-7): when opening fails with a "malformed" / "not a
538
+ * database" / "encrypted" error and `recoverCorrupt` is not false, the file is
539
+ * renamed aside to `<path>.corrupt-<ts>` (plus its WAL/SHM siblings) and a fresh
540
+ * DB is opened. The corrupt file is renamed, NOT backed up — the timestamped
541
+ * `.corrupt-*` file is kept for manual recovery.
542
+ *
543
+ * @internal — public via `@theokit/sdk/internal/persistence` (semver-exempt)
544
+ */
545
+ /** Minimal SQLite handle surface every driver (`better-sqlite3`) exposes. */
546
+ interface ResilientSqliteDb {
547
+ /** SQLite `pragma()` access (used by `applyWalWithFallback`). */
548
+ pragma(statement: string, options?: {
549
+ simple?: boolean;
550
+ }): unknown;
551
+ exec(sql: string): void;
552
+ close(): void;
553
+ }
554
+ /**
555
+ * Input to {@link openSqliteResilient}.
556
+ *
557
+ * The trap is `recoverCorrupt`, whose default is ON. When the driver reports a corrupt or encrypted
558
+ * database, the file is renamed aside and a FRESH one is opened — so the call SUCCEEDS and hands
559
+ * back an EMPTY database. The old bytes survive on disk under the renamed path, but a caller that
560
+ * treats a resolved promise as "my data is here" is wrong exactly when it matters. Pass
561
+ * `recoverCorrupt: false` to get the corruption error thrown instead.
562
+ *
563
+ * Semver-exempt: reachable via the '@theokit/sdk/internal/persistence' sub-path, which the package
564
+ * declares in 'exports' but does NOT cover with its semver contract.
565
+ *
566
+ * @typeParam T - the concrete DB handle the driver returns; defaults to {@link ResilientSqliteDb}
567
+ */
568
+ interface OpenSqliteResilientOptions<T extends ResilientSqliteDb> {
569
+ /** Absolute path to the SQLite file. Parent directories are created. */
570
+ filePath: string;
571
+ /**
572
+ * Called after the driver is open and WAL is applied, before the handle is
573
+ * returned. Apply PRAGMA/SCHEMA statements here. Errors propagate.
574
+ */
575
+ onOpen?: (db: T) => void | Promise<void>;
576
+ /** Label used in the WAL-fallback warning and corruption-recovery log. Default "sqlite". */
577
+ label?: string;
578
+ /** When true (default) a corruption error renames the file aside and rebuilds. */
579
+ recoverCorrupt?: boolean;
580
+ }
581
+ /**
582
+ * Open a SQLite file with WAL (+ DELETE fallback) and corruption recovery.
583
+ *
584
+ * @typeParam T - the concrete DB handle type the driver returns (defaults to the
585
+ * minimal {@link ResilientSqliteDb} surface)
586
+ */
587
+ declare function openSqliteResilient<T extends ResilientSqliteDb = ResilientSqliteDb>(options: OpenSqliteResilientOptions<T>): Promise<T>;
588
+ /** True when an open error indicates an unreadable / corrupt database file. */
589
+ declare function isCorruptionError(cause: unknown): boolean;
590
+
591
+ /**
592
+ * SQLite WAL mode helper with NFS/SMB/FUSE fallback to DELETE (ADR D63).
593
+ *
594
+ * WAL is faster (concurrent readers + one writer) but unsupported on some
595
+ * network/FUSE filesystems. Try WAL; if the pragma returns something else
596
+ * or throws, fall back to DELETE journal mode. Warn one time per label.
597
+ *
598
+ * This block is the MODULE header and must stay at offset 0. Sitting below the import it became a
599
+ * leading comment of the first declaration instead, which is how its tag deleted `PragmaCapable`
600
+ * from the emitted `.d.ts` while `applyWalWithFallback` — which names it — survived.
601
+ */
602
+ /**
603
+ * The narrow slice of a SQLite handle this module needs: anything with a `pragma()` method, which
604
+ * `better-sqlite3` satisfies without being named here.
605
+ *
606
+ * Not exported, but it appears in the signature of `applyWalWithFallback`, so it is emitted into
607
+ * the published declarations and a consumer can structurally satisfy it.
608
+ */
609
+ interface PragmaCapable {
610
+ pragma: (statement: string, options?: {
611
+ simple?: boolean;
612
+ }) => unknown;
613
+ }
614
+ /**
615
+ * What journal mode a connection ended up in after `applyWalWithFallback`.
616
+ *
617
+ * `mode` is the mode actually in effect, never the one that was requested. `fellBack` is `true`
618
+ * only when WAL was attempted and refused — either the pragma threw or it reported a mode other
619
+ * than `wal` — and the connection was put into DELETE instead.
620
+ *
621
+ * `fellBack: true` is normal on NFS, SMB and FUSE, where WAL needs shared memory the filesystem
622
+ * does not provide. It is not an error and nothing further is required of the caller; the
623
+ * consequence is slower concurrent access, since DELETE mode does not allow readers alongside a
624
+ * writer. Treat it as a signal about the storage, not about the database.
625
+ *
626
+ * @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for
627
+ * back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.
628
+ */
629
+ interface WalApplyResult {
630
+ /** Final journal_mode actually in effect. */
631
+ mode: "wal" | "delete";
632
+ /** True if we wanted WAL but the filesystem refused. */
633
+ fellBack: boolean;
634
+ }
635
+ /**
636
+ * Apply WAL mode with DELETE fallback. Idempotent — safe to call multiple
637
+ * times on the same connection.
638
+ *
639
+ * @param db any `pragma()`-capable SQLite handle (e.g., `better-sqlite3`)
640
+ * @param label short identifier used in the warning (e.g., "memory-index")
641
+ *
642
+ */
643
+ declare function applyWalWithFallback(db: PragmaCapable, label: string): WalApplyResult;
644
+
645
+ /**
646
+ * M81 — transcript operations the consumer was doing by hand INSIDE the framework's own store.
647
+ *
648
+ * ## What this replaces
649
+ *
650
+ * `agents/lib/session/backtrack.ts:188` (agent-builder) wrote straight into the session store with a
651
+ * bare `writeFileSync` — no atomicity, no lock, no API. 243 lines re-implementing parse, cut and
652
+ * write for a format the framework owns. The consumer is not at fault: nothing here was reachable.
653
+ *
654
+ * ## The rule that travels WITH the operation
655
+ *
656
+ * `rules/audit-trail-rotation.md § Session transcripts (M60)` defines a NEVER-delete list — the live
657
+ * pointer, the most recent transcript, and any active registry entry. That rule lived in the
658
+ * CONSUMER. Moving the operation here without moving the rule would ship an API able to destroy
659
+ * exactly what the rule protects — the same shape of defect as M80's `reconcileUpdateGoalStatus`:
660
+ * critical knowledge outside the module that needs it, applied by convention.
661
+ *
662
+ * So `forkTranscript` takes `liveSessionPaths` and refuses, with a TYPED error, to write over any of
663
+ * them. The caller supplies the list because only the caller knows which session is live; the
664
+ * enforcement lives here because that is where the write happens.
665
+ *
666
+ * @internal
667
+ */
668
+
669
+ /**
670
+ * M81 — the target is a protected session (live pointer / most-recent transcript / active entry).
671
+ *
672
+ * Typed rather than a bare `Error` because the caller must distinguish "this session is protected"
673
+ * from "the disk is full": the first is a correct refusal, the second is an incident.
674
+ *
675
+ * RENAMED from `LiveSessionError` (2026-09-01). `session-guard.ts` exports a DIFFERENT class of that
676
+ * name from the root barrel, with an incompatible shape — `(sessionId, reason)` and a `reason`
677
+ * field, against this one's `(path)` and `code: "live_session_protected"` — and `.` and
678
+ * `./persistence` are both declared subpaths, so one consumer can hold both. `instanceof` never
679
+ * crossed the pair, so a `catch` checking the root import silently missed this one and ran its
680
+ * fallback for a condition it believed it handled; `err.name` matched BOTH, so a name check looked
681
+ * right and then read `err.reason`, which only the other one has. The names now say what each
682
+ * refusal is about: destroying a SESSION, versus overwriting a TRANSCRIPT file.
683
+ */
684
+ declare class LiveTranscriptError extends TheokitAgentError {
685
+ readonly path: string;
686
+ readonly name = "LiveTranscriptError";
687
+ constructor(path: string);
688
+ }
689
+ /** Options for {@link forkTranscript}. */
690
+ interface ForkTranscriptOptions {
691
+ /** Keep records `[0, beforeRecordIndex)`. Omit to copy the whole transcript. */
692
+ readonly beforeRecordIndex?: number;
693
+ /**
694
+ * Paths that must never be written over — the live pointer, the most recent transcript, any active
695
+ * registry entry. The caller supplies them because only the caller knows which session is live.
696
+ */
697
+ readonly liveSessionPaths?: readonly string[];
698
+ /**
699
+ * M107 — permission bits for the created destination. Default: `0o600`.
700
+ *
701
+ * A transcript carries the conversation. Before M107 no mode was passed at all, so the file was
702
+ * born `0o666 & ~umask` — measured `0o664` (group-WRITABLE) on a `umask 002` machine, `0o644` on
703
+ * `umask 022`, `0o466` on `umask 0200`. This is a DEFAULT and not a required knob on purpose: a
704
+ * knob would reach zero consumers by omission, which is the failure mode that matters.
705
+ *
706
+ * As with any `open` mode, the `umask` may still CLEAR bits — under `umask 0200` the result is
707
+ * `0o400`. That is accepted: the invariant bought here is "neither group nor others", and `0o400`
708
+ * satisfies it more strictly. The SDK deliberately does not `fchmod` the default back, because
709
+ * that would hand back a bit the operator asked to remove.
710
+ */
711
+ readonly mode?: number;
712
+ }
713
+ /**
714
+ * Copy `src` into `dst`, keeping the first `beforeRecordIndex` records. The SOURCE is never touched.
715
+ *
716
+ * Atomicity comes from `wx` (exclusive create): two concurrent forks to the same destination cannot
717
+ * both succeed — the loser gets `EEXIST` rather than writing over a half-written file. That is also
718
+ * why an existing destination is a refusal, not a silent overwrite: losing a transcript without an
719
+ * error is the worst failure mode for an operation that touches user sessions.
720
+ */
721
+ declare function forkTranscript(src: string, dst: string, options?: ForkTranscriptOptions): void;
722
+ /** Options for {@link readJsonlTail}. */
723
+ interface ReadJsonlTailOptions {
724
+ /** Maximum records to return, counted from the END. */
725
+ readonly maxRecords?: number;
726
+ /**
727
+ * Start the window AFTER the last record whose `subtype` (or `type`) equals this.
728
+ *
729
+ * Matched STRUCTURALLY since T2.5. It used to be `line.includes(marker)`, so any message
730
+ * mentioning the marker in its text truncated the read — silently, with a successful return.
731
+ */
732
+ readonly sinceMarker?: string;
733
+ /** Test-only: also report how many bytes were read, to prove the read is not whole-file. */
734
+ readonly _stats?: boolean;
735
+ }
736
+ /**
737
+ * Read the LAST records of a JSONL file without loading the whole thing.
738
+ *
739
+ * Reads fixed-size chunks backwards from EOF until enough newlines have been seen. A session
740
+ * transcript grows without bound; loading megabytes to show the last three turns is the cost this
741
+ * exists to avoid — and a `slice` over a full read would be that same cost with a better name.
742
+ */
743
+ declare function readJsonlTail<T = Record<string, unknown>>(path: string, options?: ReadJsonlTailOptions): T[];
744
+
745
+ export { type AtomicWriteJsonOptions, type FileLockOptions, type ForkTranscriptOptions, JsonlParseError, type ListSessionsOptions, LiveTranscriptError as LiveSessionError, LiveTranscriptError, type OpenSqliteResilientOptions, PersistenceSchema, type ReadJsonlTailOptions, type ResilientSqliteDb, type SessionArtifact, SessionBusyError, type SessionIdSource, type SessionListing, type SessionWriterLease, type WalApplyResult, acquireSessionWriter, appendJsonl, applyWalWithFallback, atomicWriteJson, atomicWriteTempTarget, atomicWriteText, classifySessionArtifact, encodeProjectDir, forkTranscript, isCorruptionError, legacyTranscriptPath, listSessions, loadJsonl, openSqliteResilient, readJsonlIds, readJsonlTail, replaceFileAtomic, sanitizeFts5Query, sessionHasWriter, sessionUuidFor, transcriptPath, transcriptRoot, withFileLock };