@theokit/sdk 4.62.0 → 4.63.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 (114) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/{agent-NVBFTY5H.js → agent-536OOESZ.js} +8 -6
  3. package/dist/{agent-NVBFTY5H.js.map → agent-536OOESZ.js.map} +1 -1
  4. package/dist/{agent-VYPUO4UB.cjs → agent-5KYMG63I.cjs} +9 -7
  5. package/dist/{agent-VYPUO4UB.cjs.map → agent-5KYMG63I.cjs.map} +1 -1
  6. package/dist/{agent-DIu6FooJ.d.ts → agent-BFno7Sfn.d.ts} +36 -21
  7. package/dist/{agent-D3Xr_-6Z.d.cts → agent-X0DKA943.d.cts} +36 -21
  8. package/dist/chunk-A4RAL2ER.cjs +51 -0
  9. package/dist/chunk-A4RAL2ER.cjs.map +1 -0
  10. package/dist/chunk-A4VHSE56.cjs +21 -0
  11. package/dist/chunk-A4VHSE56.cjs.map +1 -0
  12. package/dist/{chunk-CQ2TQ32Y.js → chunk-AMFXSENK.js} +3 -48
  13. package/dist/chunk-AMFXSENK.js.map +1 -0
  14. package/dist/chunk-C7QPML3L.js +831 -0
  15. package/dist/chunk-C7QPML3L.js.map +1 -0
  16. package/dist/{chunk-CWHTMNNK.cjs → chunk-CASUEY42.cjs} +4 -4
  17. package/dist/{chunk-CWHTMNNK.cjs.map → chunk-CASUEY42.cjs.map} +1 -1
  18. package/dist/{chunk-SKOCYA55.js → chunk-CHKH5452.js} +37 -50
  19. package/dist/chunk-CHKH5452.js.map +1 -0
  20. package/dist/chunk-EHJEZAOO.cjs +864 -0
  21. package/dist/chunk-EHJEZAOO.cjs.map +1 -0
  22. package/dist/chunk-IRCJ7EHV.cjs +201 -0
  23. package/dist/chunk-IRCJ7EHV.cjs.map +1 -0
  24. package/dist/chunk-LW7G5DYW.js +19 -0
  25. package/dist/chunk-LW7G5DYW.js.map +1 -0
  26. package/dist/{chunk-IBYRA5PM.js → chunk-MG3SOM3M.js} +3 -3
  27. package/dist/{chunk-IBYRA5PM.js.map → chunk-MG3SOM3M.js.map} +1 -1
  28. package/dist/{chunk-AH6WD7JR.cjs → chunk-NLXOGBRJ.cjs} +45 -58
  29. package/dist/chunk-NLXOGBRJ.cjs.map +1 -0
  30. package/dist/{chunk-SKXBJ2NU.cjs → chunk-NXH4GPAQ.cjs} +2 -49
  31. package/dist/chunk-NXH4GPAQ.cjs.map +1 -0
  32. package/dist/chunk-R6TA2DRJ.js +194 -0
  33. package/dist/chunk-R6TA2DRJ.js.map +1 -0
  34. package/dist/{chunk-6OBIWHDR.cjs → chunk-UUP3MUZ6.cjs} +20 -361
  35. package/dist/chunk-UUP3MUZ6.cjs.map +1 -0
  36. package/dist/chunk-WE22OXQA.js +48 -0
  37. package/dist/chunk-WE22OXQA.js.map +1 -0
  38. package/dist/{chunk-QEKI3YKI.js → chunk-YTR3RBUZ.js} +13 -348
  39. package/dist/chunk-YTR3RBUZ.js.map +1 -0
  40. package/dist/{cron-BcWmzWzT.d.cts → cron-BuiRPrvt.d.cts} +1 -1
  41. package/dist/{cron-De6hzWCF.d.ts → cron-C2SIi31n.d.ts} +1 -1
  42. package/dist/cron.cjs +8 -6
  43. package/dist/cron.d.cts +2 -2
  44. package/dist/cron.d.ts +2 -2
  45. package/dist/cron.js +7 -5
  46. package/dist/eval.cjs +7 -5
  47. package/dist/eval.cjs.map +1 -1
  48. package/dist/eval.js +6 -4
  49. package/dist/eval.js.map +1 -1
  50. package/dist/{index-manager-RHPFVFSC.cjs → index-manager-6TEEIWM7.cjs} +7 -6
  51. package/dist/{index-manager-RHPFVFSC.cjs.map → index-manager-6TEEIWM7.cjs.map} +1 -1
  52. package/dist/{index-manager-AHAYJ33H.js → index-manager-ACEQG3IJ.js} +6 -5
  53. package/dist/{index-manager-AHAYJ33H.js.map → index-manager-ACEQG3IJ.js.map} +1 -1
  54. package/dist/index.cjs +35 -208
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +8 -3
  57. package/dist/index.d.ts +8 -3
  58. package/dist/index.js +22 -195
  59. package/dist/index.js.map +1 -1
  60. package/dist/internal/memory/active-memory.d.ts +8 -1
  61. package/dist/internal/memory/dreaming/diary.d.ts +39 -2
  62. package/dist/internal/memory/index-db.d.ts +44 -1
  63. package/dist/internal/memory/index-manager-contract.d.ts +10 -0
  64. package/dist/internal/memory/index-manager-helpers.d.ts +20 -1
  65. package/dist/internal/memory/index-manager.d.ts +1 -1
  66. package/dist/internal/memory/lance-index.d.ts +15 -1
  67. package/dist/internal/memory/migrate-sqlite-to-lance.d.ts +43 -0
  68. package/dist/internal/memory/storage/index.cjs +95 -12
  69. package/dist/internal/memory/storage/index.d.cts +11 -1
  70. package/dist/internal/memory/storage/index.d.ts +11 -1
  71. package/dist/internal/memory/storage/index.js +4 -1
  72. package/dist/internal/memory/storage/markdown-store.d.cts +13 -42
  73. package/dist/internal/memory/storage/markdown-store.d.ts +13 -42
  74. package/dist/internal/memory/storage/memory-root.d.cts +93 -0
  75. package/dist/internal/memory/storage/memory-root.d.ts +93 -0
  76. package/dist/internal/memory/storage/session-loader.d.cts +35 -1
  77. package/dist/internal/memory/storage/session-loader.d.ts +35 -1
  78. package/dist/internal/memory/storage/session-summary-writer.d.cts +50 -2
  79. package/dist/internal/memory/storage/session-summary-writer.d.ts +50 -2
  80. package/dist/internal/memory/storage/transcript-store.d.cts +38 -1
  81. package/dist/internal/memory/storage/transcript-store.d.ts +38 -1
  82. package/dist/internal/memory/storage/wiki-loader.d.cts +27 -2
  83. package/dist/internal/memory/storage/wiki-loader.d.ts +27 -2
  84. package/dist/internal/memory/tools.d.ts +8 -1
  85. package/dist/internal/memory/types.d.ts +5 -0
  86. package/dist/internal/persistence/index.cjs +12 -11
  87. package/dist/internal/persistence/index.cjs.map +1 -1
  88. package/dist/internal/persistence/index.js +2 -1
  89. package/dist/internal/persistence/index.js.map +1 -1
  90. package/dist/internal/persistence/session-dir.d.cts +0 -15
  91. package/dist/internal/persistence/session-dir.d.ts +0 -15
  92. package/dist/internal/runtime/memory/memory-store.d.ts +2 -2
  93. package/dist/persistence.cjs +9 -8
  94. package/dist/persistence.cjs.map +1 -1
  95. package/dist/persistence.js +2 -1
  96. package/dist/persistence.js.map +1 -1
  97. package/dist/subagents-loader.d.cts +1 -1
  98. package/dist/subagents-loader.d.ts +1 -1
  99. package/dist/types/agent.d.ts +11 -0
  100. package/dist/types/memory-provider.d.ts +8 -0
  101. package/docs/error-codes.md +10 -9
  102. package/docs/harness-capability-map.md +57 -17
  103. package/docs/memory-decisions.md +44 -7
  104. package/package.json +1 -1
  105. package/dist/chunk-2UCFUSPW.cjs +0 -468
  106. package/dist/chunk-2UCFUSPW.cjs.map +0 -1
  107. package/dist/chunk-6OBIWHDR.cjs.map +0 -1
  108. package/dist/chunk-AH6WD7JR.cjs.map +0 -1
  109. package/dist/chunk-CQ2TQ32Y.js.map +0 -1
  110. package/dist/chunk-QEKI3YKI.js.map +0 -1
  111. package/dist/chunk-SKOCYA55.js.map +0 -1
  112. package/dist/chunk-SKXBJ2NU.cjs.map +0 -1
  113. package/dist/chunk-WCLDJSMY.js +0 -456
  114. package/dist/chunk-WCLDJSMY.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.63.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6410e27: **BREAKING (narrow):** `local.sessionDir` no longer decides where memory is written. Use the new
8
+ `memory.directory` option. Facts already recorded are not moved and stay readable — the Claude Code
9
+ store is a read root unconditionally — so a consumer who relied on the old coupling gets their new
10
+ facts in the project store until they set `memory.directory`.
11
+
12
+ Memory now has ONE answer to "where does this agent's memory live?", and every path derives from it.
13
+
14
+ Fourteen places computed a memory path from `cwd`, and one of them computed a different one.
15
+ `appendFact` relocated when `local.sessionDir` was set; the indexer, the `memory_get` path guard,
16
+ `MEMORY.md`, `sessions/`, `notes/`, `wiki/`, the dream diary and the index database did not — the
17
+ last of those spelled the default layout out again as a string literal, so no search for the shared
18
+ helper would have found it. A relocated fact was therefore written, never indexed, unreadable by the
19
+ tool whose job is reading memory, and shadowed by a second `MEMORY.md` in the store it had left.
20
+
21
+ - **New `memory.directory`.** Absolute or `~/`-prefixed. Point it at
22
+ `~/.claude/projects/<encoded-cwd>/memory` to write where the Claude Code CLI reads. A relative
23
+ value is refused with `invalid_memory_directory` rather than resolved: the workspace and the
24
+ process cwd are both plausible bases, and picking one silently is how a store ends up split
25
+ across both.
26
+ - **One resolver.** `resolveMemoryRoot` is the only producer of a root, and it returns a branded
27
+ `MemoryRoot` that every path helper now requires. The brand is STRUCTURAL rather than a
28
+ `unique symbol`: the d.ts bundler inlines a `unique symbol` declaration into every package that
29
+ re-exports it, so `@theokit/sdk-memory` ended up with a `MemoryRoot` its own compiler rejected
30
+ against the SDK's, on values that were the same string. A structural tag refuses a bare `string`
31
+ exactly as well and survives the package boundary. A cwd and a root are both strings, so the brand
32
+ is what makes "every path derives from one resolution" a compiler rule instead of a convention —
33
+ and it is what surfaced the three call sites that were silently reading the wrong directory.
34
+ - **`local.sessionDir` means one thing again:** where session transcripts go.
35
+ - **Unchanged:** WRITE ONE, READ ALL. Recall still covers the configured root, the project store and
36
+ the CLI's store, so relocating the write orphans nothing.
37
+
38
+ Everything under the root follows it: `MEMORY.md`, the per-memory files, `notes/`, `sessions/`,
39
+ `wiki/`, `transcripts/`, `dream-diary.md`, `.index/memory.sqlite` and the Lance store. Two of those
40
+ were found by the brand rather than by reading — `index-db.ts` and `lance-index.ts` each spelled
41
+ `.theokit/memory` out again as a string literal, so no search for the shared helper would have
42
+ reached them.
43
+
44
+ `Memory.runDreamingSweep` and the SQLite→Lance migration take a `directory` for the same reason: a
45
+ sweep that consolidated notes into the default store while the facts lived elsewhere would be the
46
+ same defect one function over.
47
+
48
+ `internal/memory/storage` (semver-exempt sub-path) drops `memoryDir` and `memoryWriteDir` and gains
49
+ `resolveMemoryRoot`, `projectMemoryDir`, `memoryReadRoots`, `asMemoryRoot` and `MemoryRoot`.
50
+ `RecordSessionSummaryArgs` gains a required `memoryRoot`, supplied by the kernel — an implementor
51
+ consumes those args and never constructs them.
52
+
53
+ **The `MEMORY.md` budget is a statement about the interop partner, and only that.** The Claude Code
54
+ CLI loads the first 200 lines / 25 KB of an index into every session and drops the rest in silence.
55
+ This SDK never loads the index at all — the `<memory>` block is built from the per-memory FILES,
56
+ ranked and capped — so our recall does not degrade as the index grows. `indexBudgetWarning` therefore
57
+ speaks ONLY when `memory.directory` points at the store the CLI reads, says what is true (the CLI
58
+ drops entries) rather than what is not (memory stops working), and never throws: the fact file and
59
+ the index rewrite are one atomic operation, so refusing the second would lose the first.
60
+
61
+ **Two things deliberately do NOT follow the option.** The index DATABASE stays in the project store
62
+ even when the facts move: `memory.directory` may name the directory the Claude Code CLI manages, and
63
+ that CLI has no index format — a binary artefact it does not understand does not belong in a
64
+ directory it owns. What gets INDEXED is still the configured root; only the file's location is held
65
+ back (`docs/memory-decisions.md` § 1). And one path: `legacyMemoryJsonPath`, which locates the
66
+ pre-#389 JSON store. That store was written before the option existed, so pointing it at a
67
+ configured root would look for a legacy file where a legacy file cannot be.
68
+
3
69
  ## 4.62.0
4
70
 
5
71
  ### Minor Changes
@@ -1,13 +1,14 @@
1
- export { Agent } from './chunk-SKOCYA55.js';
1
+ export { Agent } from './chunk-CHKH5452.js';
2
2
  import './chunk-SUKXXLWD.js';
3
3
  import './chunk-YEXA3PGR.js';
4
4
  import './chunk-K2BQQ445.js';
5
- import './chunk-QEKI3YKI.js';
5
+ import './chunk-YTR3RBUZ.js';
6
6
  import './chunk-MZ2FGBLZ.js';
7
7
  import './chunk-2SFBB54R.js';
8
8
  import './chunk-XN7NOENA.js';
9
9
  import './chunk-H73MEMQB.js';
10
- import './chunk-WCLDJSMY.js';
10
+ import './chunk-LW7G5DYW.js';
11
+ import './chunk-C7QPML3L.js';
11
12
  import './chunk-YJDH3CKG.js';
12
13
  import './chunk-YXGAW7BB.js';
13
14
  import './chunk-3E77SX4H.js';
@@ -33,7 +34,8 @@ import './chunk-UC3HT2S4.js';
33
34
  import './chunk-OC4NTGMN.js';
34
35
  import './chunk-44JAAH4X.js';
35
36
  import './chunk-FKMUFNQE.js';
36
- import './chunk-CQ2TQ32Y.js';
37
+ import './chunk-WE22OXQA.js';
38
+ import './chunk-AMFXSENK.js';
37
39
  import './chunk-CV7XMBHP.js';
38
40
  import './chunk-Q5EWJPRY.js';
39
41
  import './chunk-RUDY2GTT.js';
@@ -51,5 +53,5 @@ import './chunk-3OR54XG4.js';
51
53
  import './chunk-IDCKSLYH.js';
52
54
  import './chunk-T7XEKOVW.js';
53
55
  import './chunk-T7O6K6PX.js';
54
- //# sourceMappingURL=agent-NVBFTY5H.js.map
55
- //# sourceMappingURL=agent-NVBFTY5H.js.map
56
+ //# sourceMappingURL=agent-536OOESZ.js.map
57
+ //# sourceMappingURL=agent-536OOESZ.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-NVBFTY5H.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-536OOESZ.js"}
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
- var chunkAH6WD7JR_cjs = require('./chunk-AH6WD7JR.cjs');
3
+ var chunkNLXOGBRJ_cjs = require('./chunk-NLXOGBRJ.cjs');
4
4
  require('./chunk-NGESVVJN.cjs');
5
5
  require('./chunk-2DG7KW4L.cjs');
6
6
  require('./chunk-BUUUWQMB.cjs');
7
- require('./chunk-6OBIWHDR.cjs');
7
+ require('./chunk-UUP3MUZ6.cjs');
8
8
  require('./chunk-P6H23T43.cjs');
9
9
  require('./chunk-BV2MWEMV.cjs');
10
10
  require('./chunk-EI2Q7SJ5.cjs');
11
11
  require('./chunk-GHX4P3V2.cjs');
12
- require('./chunk-2UCFUSPW.cjs');
12
+ require('./chunk-A4VHSE56.cjs');
13
+ require('./chunk-EHJEZAOO.cjs');
13
14
  require('./chunk-QI7WE46I.cjs');
14
15
  require('./chunk-P5LCASTC.cjs');
15
16
  require('./chunk-F3YZMOAU.cjs');
@@ -35,7 +36,8 @@ require('./chunk-ILCGLTSA.cjs');
35
36
  require('./chunk-CQGYNZ3K.cjs');
36
37
  require('./chunk-4I55V454.cjs');
37
38
  require('./chunk-BUIK7GUA.cjs');
38
- require('./chunk-SKXBJ2NU.cjs');
39
+ require('./chunk-A4RAL2ER.cjs');
40
+ require('./chunk-NXH4GPAQ.cjs');
39
41
  require('./chunk-2ADR2GSO.cjs');
40
42
  require('./chunk-ZF2LDKQQ.cjs');
41
43
  require('./chunk-2C72DXQF.cjs');
@@ -58,7 +60,7 @@ require('./chunk-NUKRL3I6.cjs');
58
60
 
59
61
  Object.defineProperty(exports, "Agent", {
60
62
  enumerable: true,
61
- get: function () { return chunkAH6WD7JR_cjs.Agent; }
63
+ get: function () { return chunkNLXOGBRJ_cjs.Agent; }
62
64
  });
63
- //# sourceMappingURL=agent-VYPUO4UB.cjs.map
64
- //# sourceMappingURL=agent-VYPUO4UB.cjs.map
65
+ //# sourceMappingURL=agent-5KYMG63I.cjs.map
66
+ //# sourceMappingURL=agent-5KYMG63I.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-VYPUO4UB.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"agent-5KYMG63I.cjs"}
@@ -295,28 +295,24 @@ interface BudgetTracker {
295
295
  }
296
296
 
297
297
  /**
298
- * `MemoryProvider`kernel-facing port for the memory subsystem
299
- * (SDK 2.0 Phase 1 / T1.1 — Hexagonal Architecture / Ports & Adapters,
300
- * SOLID Dependency Inversion).
301
- *
302
- * The agent loop kernel depends on THIS CONTRACT not on the concrete
303
- * `internal/memory/*` modules. Default adapter ships with `@theokit/sdk`
304
- * (no-op for back-compat); rich impl ships in `@theokit/sdk-memory`.
305
- * Consumers opt-in via `Agent.create({ memoryProvider: ... })`.
306
- *
307
- * DIP-correct home (SE46): the port + companion contract types live in the
308
- * domain `types/` layer; the application-layer module
309
- * (`internal/runtime/memory/memory-provider.ts`) re-exports them for
310
- * back-compat while the concrete providers live under `internal/`.
311
- *
312
- * Layered model (mirrors Budget):
313
- * - `MemoryAdapter` (in `types/memory-adapter.ts`) — LOW-LEVEL data port:
314
- * write / recall / delete primitives.
315
- * - `MemoryProvider` (THIS FILE) — HIGH-LEVEL lifecycle port: init,
316
- * tool factories, active memory pass, embedding runtime selection.
317
- *
318
- * @public — surface-level interface; impls are internal-but-replaceable.
298
+ * A path that {@link resolveMemoryRoot} produced the only thing the subsystem's path helpers
299
+ * accept.
300
+ *
301
+ * The brand is what makes "every path derives from one resolution" a compiler rule rather than a
302
+ * convention. Both a `cwd` and a root are strings, so without it the helpers would go on accepting
303
+ * either, and the next one added would have the same even chance of taking the wrong one that
304
+ * produced #463 in the first place. It costs one cast, at the one place a caller legitimately has a
305
+ * directory that did not come from here.
306
+ *
307
+ * STRUCTURAL, not a `unique symbol`. A `unique symbol` brand is identity-based, and the d.ts
308
+ * bundler inlines the declaration into each package that re-exports it — so `@theokit/sdk-memory`
309
+ * ended up with a `MemoryRoot` its own compiler considered incompatible with the SDK's, on values
310
+ * that were the same string. A structural tag refuses a bare `string` exactly as well and survives
311
+ * the package boundary, which is where this type has to work.
319
312
  */
313
+ type MemoryRoot = string & {
314
+ readonly __memoryRoot: "resolved";
315
+ };
320
316
 
321
317
  /** Result of `MemoryProvider.runActivePass(...)` — what the kernel injects into the LLM call. */
322
318
  interface ActiveMemoryPassResult {
@@ -360,6 +356,14 @@ interface RecordSessionSummaryArgs {
360
356
  * compute the markdown file path.
361
357
  */
362
358
  readonly cwd: string;
359
+ /**
360
+ * The memory root the kernel resolved for this agent, from `memory.directory` or the default.
361
+ *
362
+ * Supplied by the kernel, never constructed by an implementor — which is why it is required
363
+ * rather than optional. An implementation that recomputed it from `cwd` would write the summary
364
+ * into a different directory than the one the rest of the subsystem uses (#463).
365
+ */
366
+ readonly memoryRoot: MemoryRoot;
363
367
  /** Run id used as the filename key. */
364
368
  readonly runId: string;
365
369
  /** Agent identity for scope (foldering). */
@@ -1067,6 +1071,17 @@ interface MemorySettings {
1067
1071
  userId?: string;
1068
1072
  scope?: "agent" | "user" | "team";
1069
1073
  storePath?: string;
1074
+ /**
1075
+ * Where this agent's memory lives. Default `<cwd>/.theokit/memory`.
1076
+ *
1077
+ * Must be an absolute path or start with `~/` — a relative value is refused rather than
1078
+ * resolved, because the two plausible bases put the store in two different places.
1079
+ *
1080
+ * Point it at `~/.claude/projects/<encoded-cwd>/memory` to WRITE where the Claude Code CLI
1081
+ * reads. That store is READ unconditionally either way, so setting this is only needed to
1082
+ * share the writes.
1083
+ */
1084
+ directory?: string;
1070
1085
  /**
1071
1086
  * Whether the SDK auto-injects recalled facts as a `<memory>` block in the
1072
1087
  * LLM system prompt. Default `true`.
@@ -295,28 +295,24 @@ interface BudgetTracker {
295
295
  }
296
296
 
297
297
  /**
298
- * `MemoryProvider`kernel-facing port for the memory subsystem
299
- * (SDK 2.0 Phase 1 / T1.1 — Hexagonal Architecture / Ports & Adapters,
300
- * SOLID Dependency Inversion).
301
- *
302
- * The agent loop kernel depends on THIS CONTRACT not on the concrete
303
- * `internal/memory/*` modules. Default adapter ships with `@theokit/sdk`
304
- * (no-op for back-compat); rich impl ships in `@theokit/sdk-memory`.
305
- * Consumers opt-in via `Agent.create({ memoryProvider: ... })`.
306
- *
307
- * DIP-correct home (SE46): the port + companion contract types live in the
308
- * domain `types/` layer; the application-layer module
309
- * (`internal/runtime/memory/memory-provider.ts`) re-exports them for
310
- * back-compat while the concrete providers live under `internal/`.
311
- *
312
- * Layered model (mirrors Budget):
313
- * - `MemoryAdapter` (in `types/memory-adapter.ts`) — LOW-LEVEL data port:
314
- * write / recall / delete primitives.
315
- * - `MemoryProvider` (THIS FILE) — HIGH-LEVEL lifecycle port: init,
316
- * tool factories, active memory pass, embedding runtime selection.
317
- *
318
- * @public — surface-level interface; impls are internal-but-replaceable.
298
+ * A path that {@link resolveMemoryRoot} produced the only thing the subsystem's path helpers
299
+ * accept.
300
+ *
301
+ * The brand is what makes "every path derives from one resolution" a compiler rule rather than a
302
+ * convention. Both a `cwd` and a root are strings, so without it the helpers would go on accepting
303
+ * either, and the next one added would have the same even chance of taking the wrong one that
304
+ * produced #463 in the first place. It costs one cast, at the one place a caller legitimately has a
305
+ * directory that did not come from here.
306
+ *
307
+ * STRUCTURAL, not a `unique symbol`. A `unique symbol` brand is identity-based, and the d.ts
308
+ * bundler inlines the declaration into each package that re-exports it — so `@theokit/sdk-memory`
309
+ * ended up with a `MemoryRoot` its own compiler considered incompatible with the SDK's, on values
310
+ * that were the same string. A structural tag refuses a bare `string` exactly as well and survives
311
+ * the package boundary, which is where this type has to work.
319
312
  */
313
+ type MemoryRoot = string & {
314
+ readonly __memoryRoot: "resolved";
315
+ };
320
316
 
321
317
  /** Result of `MemoryProvider.runActivePass(...)` — what the kernel injects into the LLM call. */
322
318
  interface ActiveMemoryPassResult {
@@ -360,6 +356,14 @@ interface RecordSessionSummaryArgs {
360
356
  * compute the markdown file path.
361
357
  */
362
358
  readonly cwd: string;
359
+ /**
360
+ * The memory root the kernel resolved for this agent, from `memory.directory` or the default.
361
+ *
362
+ * Supplied by the kernel, never constructed by an implementor — which is why it is required
363
+ * rather than optional. An implementation that recomputed it from `cwd` would write the summary
364
+ * into a different directory than the one the rest of the subsystem uses (#463).
365
+ */
366
+ readonly memoryRoot: MemoryRoot;
363
367
  /** Run id used as the filename key. */
364
368
  readonly runId: string;
365
369
  /** Agent identity for scope (foldering). */
@@ -1067,6 +1071,17 @@ interface MemorySettings {
1067
1071
  userId?: string;
1068
1072
  scope?: "agent" | "user" | "team";
1069
1073
  storePath?: string;
1074
+ /**
1075
+ * Where this agent's memory lives. Default `<cwd>/.theokit/memory`.
1076
+ *
1077
+ * Must be an absolute path or start with `~/` — a relative value is refused rather than
1078
+ * resolved, because the two plausible bases put the store in two different places.
1079
+ *
1080
+ * Point it at `~/.claude/projects/<encoded-cwd>/memory` to WRITE where the Claude Code CLI
1081
+ * reads. That store is READ unconditionally either way, so setting this is only needed to
1082
+ * share the writes.
1083
+ */
1084
+ directory?: string;
1070
1085
  /**
1071
1086
  * Whether the SDK auto-injects recalled facts as a `<memory>` block in the
1072
1087
  * LLM system prompt. Default `true`.
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ // src/internal/persistence/fts5-sanitize.ts
4
+ var PHRASE_OPEN = "";
5
+ var PHRASE_CLOSE = "";
6
+ function sanitizeFts5Query(query) {
7
+ if (query.length === 0) return query;
8
+ const phrases = [];
9
+ let text = query.replace(/"[^"]+"/g, (match) => {
10
+ phrases.push(match);
11
+ return `${PHRASE_OPEN}${phrases.length - 1}${PHRASE_CLOSE}`;
12
+ });
13
+ text = text.replace(/[[\]{}()"^]/g, " ");
14
+ text = text.replace(/\*+/g, "*");
15
+ text = text.replace(/^\s*(AND|OR|NOT)\s+/i, "");
16
+ text = text.replace(/\s+(AND|OR|NOT)\s*$/i, "");
17
+ text = text.replace(/\b\w+[-._]\w[\w\-._]*\b/g, (match) => `"${match}"`);
18
+ for (let i = 0; i < phrases.length; i += 1) {
19
+ text = text.replace(`${PHRASE_OPEN}${i}${PHRASE_CLOSE}`, phrases[i] ?? "");
20
+ }
21
+ return text.trim();
22
+ }
23
+ var CJK_RANGES = [
24
+ [12288, 12351],
25
+ // CJK Symbols and Punctuation
26
+ [12352, 12447],
27
+ // Hiragana
28
+ [12448, 12543],
29
+ // Katakana
30
+ [13312, 19903],
31
+ // CJK Unified Ideographs Extension A
32
+ [19968, 40959],
33
+ // CJK Unified Ideographs
34
+ [44032, 55215]
35
+ // Hangul Syllables
36
+ ];
37
+ function containsCjk(text) {
38
+ for (const char of text) {
39
+ const cp = char.codePointAt(0);
40
+ if (cp === void 0) continue;
41
+ for (const [lo, hi] of CJK_RANGES) {
42
+ if (cp >= lo && cp <= hi) return true;
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+
48
+ exports.containsCjk = containsCjk;
49
+ exports.sanitizeFts5Query = sanitizeFts5Query;
50
+ //# sourceMappingURL=chunk-A4RAL2ER.cjs.map
51
+ //# sourceMappingURL=chunk-A4RAL2ER.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/fts5-sanitize.ts"],"names":[],"mappings":";;;AAsBA,IAAM,WAAA,GAAc,GAAA;AACpB,IAAM,YAAA,GAAe,GAAA;AAUd,SAAS,kBAAkB,KAAA,EAAuB;AACvD,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,KAAA;AAG/B,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,IAAI,IAAA,GAAO,KAAA,CAAM,OAAA,CAAQ,UAAA,EAAY,CAAC,KAAA,KAAU;AAC9C,IAAA,OAAA,CAAQ,KAAK,KAAK,CAAA;AAClB,IAAA,OAAO,GAAG,WAAW,CAAA,EAAG,QAAQ,MAAA,GAAS,CAAC,GAAG,YAAY,CAAA,CAAA;AAAA,EAC3D,CAAC,CAAA;AAGD,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,cAAA,EAAgB,GAAG,CAAA;AAGvC,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA;AAG/B,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,sBAAA,EAAwB,EAAE,CAAA;AAC9C,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,sBAAA,EAAwB,EAAE,CAAA;AAI9C,EAAA,IAAA,GAAO,KAAK,OAAA,CAAQ,0BAAA,EAA4B,CAAC,KAAA,KAAU,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAG,CAAA;AAGvE,EAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA,EAAG;AAC1C,IAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,CAAA,EAAG,WAAW,CAAA,EAAG,CAAC,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,OAAA,CAAQ,CAAC,CAAA,IAAK,EAAE,CAAA;AAAA,EAC3E;AAEA,EAAA,OAAO,KAAK,IAAA,EAAK;AACnB;AAIA,IAAM,UAAA,GAAuD;AAAA,EAC3D,CAAC,OAAQ,KAAM,CAAA;AAAA;AAAA,EACf,CAAC,OAAQ,KAAM,CAAA;AAAA;AAAA,EACf,CAAC,OAAQ,KAAM,CAAA;AAAA;AAAA,EACf,CAAC,OAAQ,KAAM,CAAA;AAAA;AAAA,EACf,CAAC,OAAQ,KAAM,CAAA;AAAA;AAAA,EACf,CAAC,OAAQ,KAAM;AAAA;AACjB,CAAA;AAqBO,SAAS,YAAY,IAAA,EAAuB;AACjD,EAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,WAAA,CAAY,CAAC,CAAA;AAC7B,IAAA,IAAI,OAAO,MAAA,EAAW;AACtB,IAAA,KAAA,MAAW,CAAC,EAAA,EAAI,EAAE,CAAA,IAAK,UAAA,EAAY;AACjC,MAAA,IAAI,EAAA,IAAM,EAAA,IAAM,EAAA,IAAM,EAAA,EAAI,OAAO,IAAA;AAAA,IACnC;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT","file":"chunk-A4RAL2ER.cjs","sourcesContent":["/**\n * FTS5 query sanitization + CJK detection (ADR D64).\n *\n * Port of Hermes' 6-step sanitizer (`hermes_state.py:1797-1847`). Prevents\n * crashes on user inputs with hyphens, dots, underscores, and unmatched\n * specials. Auto-quotes identifier-shaped tokens so `error-code` finds\n * literal `error-code` instead of `error AND code`.\n *\n * CJK detection returns true for inputs containing characters in the main\n * CJK Unicode ranges (Chinese, Japanese, Korean). CJK trigram routing is\n * deferred to v1.4 — callers receiving `containsCjk === true` should\n * short-circuit to an empty result or LIKE fallback for v1.3.\n *\n * @internal\n */\n\n// Control-char sentinels for the phrase-preservation placeholder. U+0001\n// and U+0002 are essentially never present in real user queries, and\n// crucially do NOT form word boundaries / `\\w` characters, so Step 5's\n// auto-quote regex (`\\b\\w+[-._]\\w[\\w\\-._]*\\b`) cannot match them.\n// Choosing `__PHRASE_N__` instead would re-trigger Step 5 on the second\n// sanitize pass and break idempotence.\nconst PHRASE_OPEN = \"\u0001\";\nconst PHRASE_CLOSE = \"\u0002\";\n\n/**\n * Six-step FTS5 query sanitizer. Returns a query string safe for passing\n * to `WHERE <fts5_table> MATCH ?` as a parameter.\n *\n * Returns empty string when the input contains only specials (caller MUST\n * short-circuit to avoid runtime SQL error — EC-3).\n *\n */\nexport function sanitizeFts5Query(query: string): string {\n if (query.length === 0) return query;\n\n // Step 1: preserve \"quoted phrases\" via control-char placeholders.\n const phrases: string[] = [];\n let text = query.replace(/\"[^\"]+\"/g, (match) => {\n phrases.push(match);\n return `${PHRASE_OPEN}${phrases.length - 1}${PHRASE_CLOSE}`;\n });\n\n // Step 2: strip unmatched specials (brackets, braces, parens, double quotes, caret).\n text = text.replace(/[[\\]{}()\"^]/g, \" \");\n\n // Step 3: collapse repeated asterisks (FTS5 prefix operator is single `*`).\n text = text.replace(/\\*+/g, \"*\");\n\n // Step 4: strip dangling boolean operators (AND/OR/NOT) at start/end.\n text = text.replace(/^\\s*(AND|OR|NOT)\\s+/i, \"\");\n text = text.replace(/\\s+(AND|OR|NOT)\\s*$/i, \"\");\n\n // Step 5: auto-quote identifier-shaped tokens that contain `-`, `.`, or `_`.\n // FTS5 tokenizer would otherwise split them as boolean conjunctions.\n text = text.replace(/\\b\\w+[-._]\\w[\\w\\-._]*\\b/g, (match) => `\"${match}\"`);\n\n // Step 6: restore preserved phrases.\n for (let i = 0; i < phrases.length; i += 1) {\n text = text.replace(`${PHRASE_OPEN}${i}${PHRASE_CLOSE}`, phrases[i] ?? \"\");\n }\n\n return text.trim();\n}\n\n// CJK code-point ranges. Coverage matches Hermes' coarse detection for v1.3;\n// fine-grained trigram routing comes in v1.4.\nconst CJK_RANGES: ReadonlyArray<readonly [number, number]> = [\n [0x3000, 0x303f], // CJK Symbols and Punctuation\n [0x3040, 0x309f], // Hiragana\n [0x30a0, 0x30ff], // Katakana\n [0x3400, 0x4dbf], // CJK Unified Ideographs Extension A\n [0x4e00, 0x9fff], // CJK Unified Ideographs\n [0xac00, 0xd7af], // Hangul Syllables\n];\n\n/**\n * Report whether `text` holds at least one Chinese, Japanese or Korean character.\n *\n * Iterates by code point, so surrogate pairs are handled correctly, and returns on the first hit.\n * An empty string is `false`.\n *\n * Coverage is deliberately coarse: CJK Symbols and Punctuation, Hiragana, Katakana, Hangul\n * Syllables, and CJK Unified Ideographs including Extension A. Everything beyond that — the\n * higher ideograph extensions, Halfwidth and Fullwidth Forms, Hangul Jamo — reads as `false`, so\n * a `false` is not proof that the text is CJK-free.\n *\n * The reason to ask is FTS5: the default tokenizer splits on whitespace, which CJK text does not\n * use, so a matching query against it returns nothing rather than failing. A caller seeing `true`\n * should short-circuit to an empty result or a LIKE fallback instead of running the search and\n * reporting no matches.\n *\n * Semver-exempt: reachable via the `@theokit/sdk/internal/persistence` sub-path, which the package\n * declares in `exports` but does NOT cover with its semver contract.\n */\nexport function containsCjk(text: string): boolean {\n for (const char of text) {\n const cp = char.codePointAt(0);\n if (cp === undefined) continue;\n for (const [lo, hi] of CJK_RANGES) {\n if (cp >= lo && cp <= hi) return true;\n }\n }\n return false;\n}\n"]}
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ var chunkI6TGFUCO_cjs = require('./chunk-I6TGFUCO.cjs');
4
+ var chunkNUKRL3I6_cjs = require('./chunk-NUKRL3I6.cjs');
5
+ var path = require('path');
6
+
7
+ async function persistActiveMemoryTranscript(root, transcript) {
8
+ try {
9
+ const dir = path.join(root, "transcripts", "active-memory");
10
+ const file = path.join(dir, `${transcript.runId}.json`);
11
+ await chunkI6TGFUCO_cjs.atomicWriteJson(file, transcript);
12
+ } catch (cause) {
13
+ const message = cause instanceof Error ? cause.message : String(cause);
14
+ chunkNUKRL3I6_cjs.diag(`[theokit-sdk] active-memory transcript persist failed: ${message}
15
+ `);
16
+ }
17
+ }
18
+
19
+ exports.persistActiveMemoryTranscript = persistActiveMemoryTranscript;
20
+ //# sourceMappingURL=chunk-A4VHSE56.cjs.map
21
+ //# sourceMappingURL=chunk-A4VHSE56.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/memory/storage/transcript-store.ts"],"names":["join","atomicWriteJson","diag"],"mappings":";;;;;;AA2CA,eAAsB,6BAAA,CACpB,MACA,UAAA,EACe;AACf,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAMA,SAAA,CAAK,IAAA,EAAM,aAAA,EAAe,eAAe,CAAA;AACrD,IAAA,MAAM,OAAOA,SAAA,CAAK,GAAA,EAAK,CAAA,EAAG,UAAA,CAAW,KAAK,CAAA,KAAA,CAAO,CAAA;AAGjD,IAAA,MAAMC,iCAAA,CAAgB,MAAM,UAAU,CAAA;AAAA,EACxC,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,UAAU,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AACrE,IAAAC,sBAAA,CAAK,0DAA0D,OAAO;AAAA,CAAI,CAAA;AAAA,EAC5E;AACF","file":"chunk-A4VHSE56.cjs","sourcesContent":["import { join } from \"node:path\";\nimport { diag } from \"../../diagnostics.js\";\nimport { atomicWriteJson } from \"../../persistence/atomic-write.js\";\nimport type { MemoryRoot } from \"./memory-root.js\";\n\n/**\n * Optional on-disk persistence for Active Memory recall transcripts (ADR D6).\n *\n * Writes one JSON file per run under\n * `.theokit/memory/transcripts/active-memory/<runId>.json` when the agent\n * passes `persistTranscripts: true`. Failures are swallowed with a stderr\n * warning so transcript IO never crashes the agent run.\n *\n * Shared with `@theokit/sdk-memory` through the semver-exempt `internal/memory-store`\n * sub-path, so it carries no internal-visibility tag. `stripInternal` matches that tag as TEXT\n * anywhere in the block, so naming it here — even in backticks, even to say it is absent — deletes\n * this symbol from the published declarations and forces the satellite back onto a copy. Measured:\n * the first draft of this very note did exactly that. See #430 and #463.\n */\n\nexport interface ActiveMemoryTranscript {\n runId: string;\n startedAtMs: number;\n userText: string;\n queryMode: string;\n status: string;\n durationMs: number;\n summary: string | undefined;\n hits: ReadonlyArray<{\n path: string;\n startLine: number;\n endLine: number;\n score: number;\n snippet: string;\n }>;\n}\n\n/**\n * Write one active-memory recall transcript under `<memory root>/transcripts/active-memory`.\n *\n * Never throws. Transcript IO is observability, and observability must not break the run it merely\n * observes — a failure is reported through the diagnostics sink and swallowed.\n */\nexport async function persistActiveMemoryTranscript(\n root: MemoryRoot,\n transcript: ActiveMemoryTranscript,\n): Promise<void> {\n try {\n const dir = join(root, \"transcripts\", \"active-memory\");\n const file = join(dir, `${transcript.runId}.json`);\n // atomicWriteJson auto-creates the parent directory + writes atomically\n // (no torn writes during crash).\n await atomicWriteJson(file, transcript);\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n diag(`[theokit-sdk] active-memory transcript persist failed: ${message}\\n`);\n }\n}\n"]}
@@ -3,51 +3,6 @@ import { diag } from './chunk-T7O6K6PX.js';
3
3
  import { mkdir, rename } from 'fs/promises';
4
4
  import { dirname } from 'path';
5
5
 
6
- // src/internal/persistence/fts5-sanitize.ts
7
- var PHRASE_OPEN = "";
8
- var PHRASE_CLOSE = "";
9
- function sanitizeFts5Query(query) {
10
- if (query.length === 0) return query;
11
- const phrases = [];
12
- let text = query.replace(/"[^"]+"/g, (match) => {
13
- phrases.push(match);
14
- return `${PHRASE_OPEN}${phrases.length - 1}${PHRASE_CLOSE}`;
15
- });
16
- text = text.replace(/[[\]{}()"^]/g, " ");
17
- text = text.replace(/\*+/g, "*");
18
- text = text.replace(/^\s*(AND|OR|NOT)\s+/i, "");
19
- text = text.replace(/\s+(AND|OR|NOT)\s*$/i, "");
20
- text = text.replace(/\b\w+[-._]\w[\w\-._]*\b/g, (match) => `"${match}"`);
21
- for (let i = 0; i < phrases.length; i += 1) {
22
- text = text.replace(`${PHRASE_OPEN}${i}${PHRASE_CLOSE}`, phrases[i] ?? "");
23
- }
24
- return text.trim();
25
- }
26
- var CJK_RANGES = [
27
- [12288, 12351],
28
- // CJK Symbols and Punctuation
29
- [12352, 12447],
30
- // Hiragana
31
- [12448, 12543],
32
- // Katakana
33
- [13312, 19903],
34
- // CJK Unified Ideographs Extension A
35
- [19968, 40959],
36
- // CJK Unified Ideographs
37
- [44032, 55215]
38
- // Hangul Syllables
39
- ];
40
- function containsCjk(text) {
41
- for (const char of text) {
42
- const cp = char.codePointAt(0);
43
- if (cp === void 0) continue;
44
- for (const [lo, hi] of CJK_RANGES) {
45
- if (cp >= lo && cp <= hi) return true;
46
- }
47
- }
48
- return false;
49
- }
50
-
51
6
  // src/internal/persistence/sqlite-wal.ts
52
7
  var warnedLabels = /* @__PURE__ */ new Set();
53
8
  function applyWalWithFallback(db, label) {
@@ -161,6 +116,6 @@ async function renameAside(filePath, label) {
161
116
  );
162
117
  }
163
118
 
164
- export { applyWalWithFallback, containsCjk, isCorruptionError, openSqliteResilient, sanitizeFts5Query };
165
- //# sourceMappingURL=chunk-CQ2TQ32Y.js.map
166
- //# sourceMappingURL=chunk-CQ2TQ32Y.js.map
119
+ export { applyWalWithFallback, isCorruptionError, openSqliteResilient };
120
+ //# sourceMappingURL=chunk-AMFXSENK.js.map
121
+ //# sourceMappingURL=chunk-AMFXSENK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/internal/persistence/sqlite-wal.ts","../src/internal/persistence/sqlite-open.ts"],"names":[],"mappings":"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","file":"chunk-AMFXSENK.js","sourcesContent":["/**\n * SQLite WAL mode helper with NFS/SMB/FUSE fallback to DELETE (ADR D63).\n *\n * WAL is faster (concurrent readers + one writer) but unsupported on some\n * network/FUSE filesystems. Try WAL; if the pragma returns something else\n * or throws, fall back to DELETE journal mode. Warn one time per label.\n *\n * This block is the MODULE header and must stay at offset 0. Sitting below the import it became a\n * leading comment of the first declaration instead, which is how its tag deleted `PragmaCapable`\n * from the emitted `.d.ts` while `applyWalWithFallback` — which names it — survived.\n */\n\nimport { diag } from \"../diagnostics.js\";\n\n/**\n * The narrow slice of a SQLite handle this module needs: anything with a `pragma()` method, which\n * `better-sqlite3` satisfies without being named here.\n *\n * Not exported, but it appears in the signature of `applyWalWithFallback`, so it is emitted into\n * the published declarations and a consumer can structurally satisfy it.\n */\ninterface PragmaCapable {\n pragma: (statement: string, options?: { simple?: boolean }) => unknown;\n}\n\n/**\n * What journal mode a connection ended up in after `applyWalWithFallback`.\n *\n * `mode` is the mode actually in effect, never the one that was requested. `fellBack` is `true`\n * only when WAL was attempted and refused — either the pragma threw or it reported a mode other\n * than `wal` — and the connection was put into DELETE instead.\n *\n * `fellBack: true` is normal on NFS, SMB and FUSE, where WAL needs shared memory the filesystem\n * does not provide. It is not an error and nothing further is required of the caller; the\n * consequence is slower concurrent access, since DELETE mode does not allow readers alongside a\n * writer. Treat it as a signal about the storage, not about the database.\n *\n * @public — re-exported from the semver-protected `@theokit/sdk/persistence` barrel, and (for\n * back-compat) from the semver-exempt `@theokit/sdk/internal/persistence` alias.\n */\nexport interface WalApplyResult {\n /** Final journal_mode actually in effect. */\n mode: \"wal\" | \"delete\";\n /** True if we wanted WAL but the filesystem refused. */\n fellBack: boolean;\n}\n\nconst warnedLabels = new Set<string>();\n\n/**\n * Apply WAL mode with DELETE fallback. Idempotent — safe to call multiple\n * times on the same connection.\n *\n * @param db any `pragma()`-capable SQLite handle (e.g., `better-sqlite3`)\n * @param label short identifier used in the warning (e.g., \"memory-index\")\n *\n */\nexport function applyWalWithFallback(db: PragmaCapable, label: string): WalApplyResult {\n try {\n const result = db.pragma(\"journal_mode = WAL\", { simple: true });\n if (typeof result === \"string\" && result.toLowerCase() === \"wal\") {\n return { mode: \"wal\", fellBack: false };\n }\n logFallback(label, `got \"${String(result)}\" instead of \"wal\"`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n logFallback(label, msg);\n }\n\n db.pragma(\"journal_mode = DELETE\");\n return { mode: \"delete\", fellBack: true };\n}\n\nfunction logFallback(label: string, reason: string): void {\n if (warnedLabels.has(label)) return;\n warnedLabels.add(label);\n diag(\n `[theokit-sdk] ${label}: WAL unavailable (${reason}); using DELETE journal mode. ` +\n \"This is normal on NFS/SMB/FUSE; expect slightly slower concurrent access.\\n\",\n );\n}\n\n/**\n * Test helper — clears the warn-once registry.\n *\n * @internal\n */\nexport function _resetWalWarnings(): void {\n warnedLabels.clear();\n}\n","/**\n * Resilient SQLite open (plan m0-foundation-expose-primitives, M0-5).\n *\n * Generalizes the driver-load + WAL-apply + corruption-recovery logic that was\n * duplicated (byte-identical) across `sdk/internal/memory/index-db.ts` and\n * `sdk-memory/internal/index/index-db.ts`. Schema-agnostic: the caller applies\n * its own PRAGMA/SCHEMA via the `onOpen` callback.\n *\n * Corruption recovery (EC-7): when opening fails with a \"malformed\" / \"not a\n * database\" / \"encrypted\" error and `recoverCorrupt` is not false, the file is\n * renamed aside to `<path>.corrupt-<ts>` (plus its WAL/SHM siblings) and a fresh\n * DB is opened. The corrupt file is renamed, NOT backed up — the timestamped\n * `.corrupt-*` file is kept for manual recovery.\n *\n * @internal — public via `@theokit/sdk/internal/persistence` (semver-exempt)\n */\n\nimport { mkdir, rename } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { applyWalWithFallback } from \"./sqlite-wal.js\";\n\n/** Minimal SQLite handle surface every driver (`better-sqlite3`) exposes. */\nexport interface ResilientSqliteDb {\n /** SQLite `pragma()` access (used by `applyWalWithFallback`). */\n pragma(statement: string, options?: { simple?: boolean }): unknown;\n exec(sql: string): void;\n close(): void;\n}\n\n/**\n * Input to {@link openSqliteResilient}.\n *\n * The trap is `recoverCorrupt`, whose default is ON. When the driver reports a corrupt or encrypted\n * database, the file is renamed aside and a FRESH one is opened — so the call SUCCEEDS and hands\n * back an EMPTY database. The old bytes survive on disk under the renamed path, but a caller that\n * treats a resolved promise as \"my data is here\" is wrong exactly when it matters. Pass\n * `recoverCorrupt: false` to get the corruption error thrown instead.\n *\n * Semver-exempt: reachable via the '@theokit/sdk/internal/persistence' sub-path, which the package\n * declares in 'exports' but does NOT cover with its semver contract.\n *\n * @typeParam T - the concrete DB handle the driver returns; defaults to {@link ResilientSqliteDb}\n */\nexport interface OpenSqliteResilientOptions<T extends ResilientSqliteDb> {\n /** Absolute path to the SQLite file. Parent directories are created. */\n filePath: string;\n /**\n * Called after the driver is open and WAL is applied, before the handle is\n * returned. Apply PRAGMA/SCHEMA statements here. Errors propagate.\n */\n onOpen?: (db: T) => void | Promise<void>;\n /** Label used in the WAL-fallback warning and corruption-recovery log. Default \"sqlite\". */\n label?: string;\n /** When true (default) a corruption error renames the file aside and rebuilds. */\n recoverCorrupt?: boolean;\n}\n\n/**\n * Open a SQLite file with WAL (+ DELETE fallback) and corruption recovery.\n *\n * @typeParam T - the concrete DB handle type the driver returns (defaults to the\n * minimal {@link ResilientSqliteDb} surface)\n */\nexport async function openSqliteResilient<T extends ResilientSqliteDb = ResilientSqliteDb>(\n options: OpenSqliteResilientOptions<T>,\n): Promise<T> {\n await mkdir(dirname(options.filePath), { recursive: true });\n try {\n return await openConcrete(options);\n } catch (cause) {\n if (options.recoverCorrupt !== false && isCorruptionError(cause)) {\n await renameAside(options.filePath, options.label ?? \"sqlite\");\n return await openConcrete(options);\n }\n throw cause;\n }\n}\n\nasync function openConcrete<T extends ResilientSqliteDb>(\n options: OpenSqliteResilientOptions<T>,\n): Promise<T> {\n const db = await loadDriver<T>(options.filePath);\n // Apply WAL with NFS/SMB/FUSE fallback BEFORE schema so the journal mode is\n // set for the whole session.\n applyWalWithFallback(db, options.label ?? \"sqlite\");\n await options.onOpen?.(db);\n return db;\n}\n\n/** Injectable driver loaders (tests simulate a consumer env without better-sqlite3). */\ninterface DriverLoaders {\n betterSqlite3?: () => Promise<unknown>;\n nodeSqlite?: () => Promise<unknown>;\n}\nlet driverLoaderOverrides: DriverLoaders | undefined;\n\n/** Test-only. @internal */\nexport function _setDriverLoadersForTests(overrides: DriverLoaders | undefined): void {\n driverLoaderOverrides = overrides;\n}\n\n/**\n * Adapt `node:sqlite`'s `DatabaseSync` to the better-sqlite3 surface this module's callers use\n * (`prepare/get/all/run`, `exec`, `close`, `pragma`, `loadExtension`). The error message below has\n * ALWAYS promised this fallback (\"or run on Node 22.5+ for built-in node:sqlite\") — before the\n * flicker-bug fix the promise was fabricated: only better-sqlite3 was ever tried, so every consumer\n * without the optional native dep lost memory tools AND got a per-turn stderr WARN.\n */\nfunction adaptNodeSqlite(db: {\n prepare(sql: string): unknown;\n exec(sql: string): void;\n close(): void;\n loadExtension?: (path: string) => void;\n}): ResilientSqliteDb {\n const pragma = (statement: string, options?: { simple?: boolean }): unknown => {\n const stmt = db.prepare(`PRAGMA ${statement}`) as {\n get(): Record<string, unknown> | undefined;\n };\n const row = stmt.get();\n if (options?.simple === true) {\n return row === undefined ? undefined : Object.values(row)[0];\n }\n return row === undefined ? [] : [row];\n };\n return new Proxy(db as unknown as ResilientSqliteDb, {\n get(target, prop, receiver) {\n if (prop === \"pragma\") return pragma;\n if (prop === \"loadExtension\" && typeof db.loadExtension !== \"function\") {\n return () => {\n throw new Error(\n \"SQLite extension loading is unavailable on the node:sqlite fallback — install better-sqlite3 for sqlite-vec\",\n );\n };\n }\n const value = Reflect.get(target, prop, receiver) as unknown;\n return typeof value === \"function\"\n ? (value as (...a: unknown[]) => unknown).bind(target)\n : value;\n },\n });\n}\n\n// PRE-EXISTING debt, exposed when M75 fixed the Biome config that used to abort before\n// sweeping these files (a nested root under refactor/). It is not new code and was not touched\n// by M75; refactoring SDK internals without review would trade a visible problem for a diff\n// risky. Tracked in usetheodev/theokit-sdk#151.\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: see the reason just above\nasync function loadDriver<T extends ResilientSqliteDb>(filePath: string): Promise<T> {\n let betterSqliteCause: unknown;\n try {\n const mod = (await (driverLoaderOverrides?.betterSqlite3?.() ?? import(\"better-sqlite3\"))) as {\n default?: unknown;\n };\n const Ctor = mod.default ?? mod;\n if (typeof Ctor !== \"function\") {\n throw new Error(`better-sqlite3 export is not a constructor (got ${typeof Ctor})`);\n }\n return new (Ctor as new (path: string) => unknown)(filePath) as T;\n } catch (cause) {\n betterSqliteCause = cause;\n }\n // Fallback: the Node 22.5+ built-in driver (the path the error message documents).\n // `process.getBuiltinModule` (Node 22.3+) instead of `import(\"node:sqlite\")` — bundlers that\n // predate the sqlite builtin rewrite the import specifier to a bare \"sqlite\" package (proven in\n // the published dist: \"Cannot find package 'sqlite'\"), while getBuiltinModule is opaque to them.\n try {\n const mod = (await (driverLoaderOverrides?.nodeSqlite?.() ??\n Promise.resolve(\n (process as { getBuiltinModule?: (id: string) => unknown }).getBuiltinModule?.(\n \"node:sqlite\",\n ) ??\n (() => {\n throw new Error(\"node:sqlite built-in unavailable (Node < 22.3)\");\n })(),\n ))) as {\n DatabaseSync: new (\n path: string,\n ) => {\n prepare(sql: string): unknown;\n exec(sql: string): void;\n close(): void;\n };\n };\n return adaptNodeSqlite(new mod.DatabaseSync(filePath)) as T;\n } catch (nodeSqliteCause) {\n const b =\n betterSqliteCause instanceof Error ? betterSqliteCause.message : String(betterSqliteCause);\n const n = nodeSqliteCause instanceof Error ? nodeSqliteCause.message : String(nodeSqliteCause);\n throw new ConfigurationError(\n `Failed to load SQLite driver. Install \\`better-sqlite3\\` or run on Node 22.5+ for built-in \\`node:sqlite\\`. better-sqlite3: ${b}; node:sqlite: ${n}`,\n { code: \"sqlite_driver_unavailable\", cause: nodeSqliteCause },\n );\n }\n}\n\n/** True when an open error indicates an unreadable / corrupt database file. */\nexport function isCorruptionError(cause: unknown): boolean {\n if (!(cause instanceof Error)) return false;\n const msg = cause.message.toLowerCase();\n return (\n msg.includes(\"malformed\") ||\n msg.includes(\"not a database\") ||\n msg.includes(\"encrypted\") ||\n msg.includes(\"disk image is malformed\")\n );\n}\n\nasync function renameAside(filePath: string, label: string): Promise<void> {\n const asidePath = `${filePath}.corrupt-${Date.now()}`;\n await rename(filePath, asidePath).catch(() => undefined);\n await rename(`${filePath}-wal`, `${asidePath}-wal`).catch(() => undefined);\n await rename(`${filePath}-shm`, `${asidePath}-shm`).catch(() => undefined);\n diag(\n `[theokit-sdk] ${label} database corrupt; renamed aside to ${asidePath} and rebuilt schema\\n`,\n );\n}\n"]}