@remnic/core 9.57.5 → 9.58.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 (476) hide show
  1. package/dist/access-admin-ops-surface.d.ts +12 -11
  2. package/dist/access-admin-ops-surface.js +25 -24
  3. package/dist/access-authorization-probe.d.ts +12 -11
  4. package/dist/access-authorization-probe.js +26 -25
  5. package/dist/access-boundary.d.ts +12 -11
  6. package/dist/access-boundary.js +25 -24
  7. package/dist/access-cli.js +77 -76
  8. package/dist/access-cli.js.map +1 -1
  9. package/dist/access-coding-context-resolution.d.ts +1 -1
  10. package/dist/access-extraction-force-flush.d.ts +12 -11
  11. package/dist/access-extraction-force-flush.js +25 -24
  12. package/dist/access-health-types.d.ts +1 -1
  13. package/dist/access-http-lcm-compaction.d.ts +12 -11
  14. package/dist/access-http-lifecycle-flush.d.ts +12 -11
  15. package/dist/access-http-offline-stream.d.ts +12 -11
  16. package/dist/access-http-query.js +25 -24
  17. package/dist/access-http.d.ts +13 -12
  18. package/dist/access-http.js +36 -35
  19. package/dist/access-identity-continuity-surface.d.ts +10 -9
  20. package/dist/access-identity-continuity-surface.js +25 -24
  21. package/dist/access-lcm-surface.d.ts +12 -11
  22. package/dist/access-lcm-surface.js +25 -24
  23. package/dist/access-mcp.d.ts +12 -11
  24. package/dist/access-mcp.js +32 -31
  25. package/dist/access-memory-search-fanout.d.ts +2 -2
  26. package/dist/{access-namespace-preflight-Bpt-SYP0.d.ts → access-namespace-preflight-TBXz-8s_.d.ts} +1 -1
  27. package/dist/access-namespace-preflight.d.ts +3 -3
  28. package/dist/access-namespace-preflight.js +25 -24
  29. package/dist/access-observe-write-surface.d.ts +12 -11
  30. package/dist/access-observe-write-surface.js +25 -24
  31. package/dist/access-offline-manifest.d.ts +10 -9
  32. package/dist/access-operations-batch.js +27 -26
  33. package/dist/access-operations.d.ts +15 -14
  34. package/dist/access-operations.js +31 -30
  35. package/dist/access-recall-concurrency.d.ts +12 -11
  36. package/dist/access-recall-concurrency.js +25 -24
  37. package/dist/access-recall-response.d.ts +12 -11
  38. package/dist/access-recall-response.js +25 -24
  39. package/dist/access-recall-surface.d.ts +12 -11
  40. package/dist/access-recall-surface.js +25 -24
  41. package/dist/access-schema.d.ts +88 -88
  42. package/dist/access-schema.js +3 -3
  43. package/dist/{access-service-D9xXmKvd.d.ts → access-service-BQkRul4d.d.ts} +71 -71
  44. package/dist/access-service-helpers.d.ts +12 -11
  45. package/dist/access-service.d.ts +12 -11
  46. package/dist/access-service.js +25 -24
  47. package/dist/access-surface-catalog.d.ts +12 -11
  48. package/dist/access-surface-catalog.js +27 -26
  49. package/dist/access-surface-catalog.js.map +1 -1
  50. package/dist/access-wearables-meetings-surface.d.ts +3 -3
  51. package/dist/action-confidence.d.ts +1 -1
  52. package/dist/active-memory-bridge.d.ts +1 -1
  53. package/dist/active-recall.d.ts +1 -1
  54. package/dist/active-recall.js +1 -1
  55. package/dist/ambient-provenance.d.ts +1 -1
  56. package/dist/artifact-search.d.ts +1 -1
  57. package/dist/behavior-learner.d.ts +1 -1
  58. package/dist/behavior-signals.d.ts +1 -1
  59. package/dist/bootstrap.d.ts +10 -9
  60. package/dist/briefing-window.d.ts +2 -2
  61. package/dist/briefing.d.ts +2 -2
  62. package/dist/briefing.js +10 -10
  63. package/dist/buffer-surprise-report.d.ts +1 -1
  64. package/dist/buffer-surprise.js +4 -3
  65. package/dist/buffer-turn-helpers.d.ts +1 -1
  66. package/dist/buffer.d.ts +2 -2
  67. package/dist/bulk-import/index.d.ts +3 -3
  68. package/dist/calibration.d.ts +1 -1
  69. package/dist/capabilities.d.ts +1 -1
  70. package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-GWVG7LGC.js} +2 -2
  71. package/dist/{catalog-BasnO3Bw.d.ts → catalog-z1UerMbh.d.ts} +1 -1
  72. package/dist/causal-behavior.d.ts +2 -1
  73. package/dist/causal-consolidation.d.ts +1 -1
  74. package/dist/causal-consolidation.js +14 -13
  75. package/dist/causal-consolidation.js.map +1 -1
  76. package/dist/causal-trajectory-graph.d.ts +1 -1
  77. package/dist/{chunk-P4BC54KI.js → chunk-2GBFKHBZ.js} +14 -38
  78. package/dist/chunk-2GBFKHBZ.js.map +1 -0
  79. package/dist/{chunk-YTZA7U6Q.js → chunk-2VHCLHZ5.js} +3 -3
  80. package/dist/{chunk-NEJQ65NV.js → chunk-4CLE2LXB.js} +2 -2
  81. package/dist/{chunk-3VFVQDZO.js → chunk-4IIKJOYM.js} +1 -1
  82. package/dist/{chunk-3VFVQDZO.js.map → chunk-4IIKJOYM.js.map} +1 -1
  83. package/dist/{chunk-JLNBQWZ2.js → chunk-5JMAGU26.js} +7 -6
  84. package/dist/{chunk-JLNBQWZ2.js.map → chunk-5JMAGU26.js.map} +1 -1
  85. package/dist/{chunk-VDX2J7OX.js → chunk-5W7CTZ2E.js} +4 -4
  86. package/dist/{chunk-CZUHNJHX.js → chunk-6C7Q6NG3.js} +13 -13
  87. package/dist/chunk-6TBIWIAG.js +201 -0
  88. package/dist/chunk-6TBIWIAG.js.map +1 -0
  89. package/dist/{chunk-4O6R3GPK.js → chunk-6W2ISYUV.js} +4 -4
  90. package/dist/{chunk-3AJOJOFP.js → chunk-77DAFTUU.js} +3 -3
  91. package/dist/{chunk-IKT6ZVO7.js → chunk-7DHFB5SG.js} +3 -3
  92. package/dist/{chunk-SFR7J2G6.js → chunk-7G34CBKS.js} +2 -2
  93. package/dist/{chunk-IT57AOMC.js → chunk-AH2NX6S7.js} +3 -3
  94. package/dist/{chunk-HYQJQGD3.js → chunk-AM4T6CHX.js} +49 -35
  95. package/dist/chunk-AM4T6CHX.js.map +1 -0
  96. package/dist/{chunk-Z54EM5YZ.js → chunk-B2QHVLJO.js} +60 -30
  97. package/dist/chunk-B2QHVLJO.js.map +1 -0
  98. package/dist/{chunk-FI3PIOOI.js → chunk-B4CSPFYN.js} +8 -8
  99. package/dist/{chunk-7PJARJS4.js → chunk-BPM6UHUL.js} +2 -2
  100. package/dist/{chunk-PPUTUOC3.js → chunk-BTHDQOF4.js} +5 -5
  101. package/dist/{chunk-AW4A4XOP.js → chunk-CZGOLAEW.js} +41 -41
  102. package/dist/{chunk-5PGYCWA7.js → chunk-DE6YCZEE.js} +2 -2
  103. package/dist/{chunk-JOA4Q765.js → chunk-EUYWQ7TS.js} +2 -2
  104. package/dist/{chunk-O5AFC5KM.js → chunk-EWL5DICB.js} +2 -2
  105. package/dist/{chunk-4YZCIK5E.js → chunk-EZXGJJXM.js} +2 -2
  106. package/dist/{chunk-GXIY65UH.js → chunk-FI5EAP4N.js} +2 -2
  107. package/dist/{chunk-YOKSRIFI.js → chunk-FUWS2MNJ.js} +4 -4
  108. package/dist/{chunk-6RK2X7BR.js → chunk-G3JFFUKR.js} +2 -2
  109. package/dist/{chunk-VANZKHTN.js → chunk-G5OB4XZX.js} +4 -4
  110. package/dist/{chunk-66SLUXKM.js → chunk-HK6KYIF6.js} +2 -2
  111. package/dist/{chunk-XBGLEOID.js → chunk-I4BLRM7X.js} +9 -9
  112. package/dist/{chunk-5ZIWRMZM.js → chunk-IVKIODHO.js} +7 -6
  113. package/dist/chunk-IVKIODHO.js.map +1 -0
  114. package/dist/{chunk-6SX4MY5Q.js → chunk-J65AKOD6.js} +2 -2
  115. package/dist/{chunk-BPWZDHFM.js → chunk-J6QIAYNR.js} +2 -2
  116. package/dist/{chunk-XL3TATMJ.js → chunk-JHMAYABJ.js} +4 -4
  117. package/dist/{chunk-F7M36OG2.js → chunk-JNWISMYG.js} +10 -10
  118. package/dist/{chunk-VYIXCMUP.js → chunk-KBIIE57I.js} +3 -3
  119. package/dist/{chunk-QKPDN325.js → chunk-KF75ANST.js} +6 -3
  120. package/dist/chunk-KF75ANST.js.map +1 -0
  121. package/dist/{chunk-24PNX2GF.js → chunk-KXYMU4IN.js} +3 -3
  122. package/dist/{chunk-FKTD7IVS.js → chunk-L3JVAM5Q.js} +2 -2
  123. package/dist/{chunk-5QHDXMXP.js → chunk-LLZKOSGM.js} +1 -1
  124. package/dist/{chunk-5QHDXMXP.js.map → chunk-LLZKOSGM.js.map} +1 -1
  125. package/dist/{chunk-LIF6DO6U.js → chunk-MG7GI4TT.js} +2 -2
  126. package/dist/{chunk-RUZM6Z52.js → chunk-MMZK2DWN.js} +1 -1
  127. package/dist/{chunk-NTJ6TY44.js → chunk-ODAE36PG.js} +3 -3
  128. package/dist/{chunk-QFN4U7AG.js → chunk-ODZKV2F7.js} +3 -3
  129. package/dist/{chunk-QFN4U7AG.js.map → chunk-ODZKV2F7.js.map} +1 -1
  130. package/dist/{chunk-YLHM5BQQ.js → chunk-OLX2HJFW.js} +4 -4
  131. package/dist/{chunk-DNC4A7RS.js → chunk-OU7RWWW6.js} +2 -2
  132. package/dist/{chunk-L3LRWDJW.js → chunk-OV36CROW.js} +3 -3
  133. package/dist/{chunk-BXLOS5AJ.js → chunk-OWHERGF2.js} +2 -2
  134. package/dist/{chunk-CSV34HIW.js → chunk-P5OYNV7B.js} +2 -2
  135. package/dist/{chunk-SNW3YRO6.js → chunk-P6GOYLHN.js} +2 -2
  136. package/dist/{chunk-C5B3CIMJ.js → chunk-PFPNUFKZ.js} +109 -59
  137. package/dist/chunk-PFPNUFKZ.js.map +1 -0
  138. package/dist/{chunk-USGKHF36.js → chunk-QUJC22CT.js} +2 -2
  139. package/dist/{chunk-BVRREV3N.js → chunk-R7IV3EUJ.js} +2 -2
  140. package/dist/{chunk-KSXJOC4F.js → chunk-RDFF2NAU.js} +18 -8
  141. package/dist/chunk-RDFF2NAU.js.map +1 -0
  142. package/dist/{chunk-D4LVHPOA.js → chunk-RXN7Z2V5.js} +3 -3
  143. package/dist/{chunk-7VSHDLJQ.js → chunk-T2VJ2EO5.js} +6 -6
  144. package/dist/chunk-TOTT6S2V.js +20 -0
  145. package/dist/chunk-TOTT6S2V.js.map +1 -0
  146. package/dist/{chunk-U7BMN33M.js → chunk-UBSM6TZP.js} +2 -2
  147. package/dist/{chunk-DQEMWVMT.js → chunk-UVYI6VIX.js} +1 -1
  148. package/dist/{chunk-U7XLOSG2.js → chunk-VIM6QH4D.js} +2 -2
  149. package/dist/{chunk-LJJWFV2Q.js → chunk-VJXSAJFW.js} +2 -2
  150. package/dist/{chunk-HZC3OECF.js → chunk-VOKK4WCG.js} +2 -2
  151. package/dist/{chunk-LTAVSOZW.js → chunk-WVP2I6C5.js} +5 -4
  152. package/dist/chunk-WVP2I6C5.js.map +1 -0
  153. package/dist/{chunk-LRDP2AC2.js → chunk-YBCZTBN2.js} +2 -2
  154. package/dist/{chunk-USAUBGBC.js → chunk-YKZFX2HQ.js} +2 -2
  155. package/dist/{chunk-I2GIYTRU.js → chunk-ZOGOOSB3.js} +2 -2
  156. package/dist/{chunk-PBGWYM3H.js → chunk-ZPUIRTIZ.js} +27 -15
  157. package/dist/chunk-ZPUIRTIZ.js.map +1 -0
  158. package/dist/chunking.d.ts +3 -1
  159. package/dist/chunking.js +8 -3
  160. package/dist/{cli-DTOkYhKI.d.ts → cli-DsyVvOFP.d.ts} +5 -5
  161. package/dist/cli.d.ts +14 -13
  162. package/dist/cli.js +57 -56
  163. package/dist/coding/pre-action-gate.d.ts +1 -1
  164. package/dist/compounding/engine.d.ts +2 -2
  165. package/dist/compounding/engine.js +10 -10
  166. package/dist/compounding/preference-consolidator.d.ts +2 -1
  167. package/dist/compression-optimizer.d.ts +1 -1
  168. package/dist/config.d.ts +1 -1
  169. package/dist/config.js +1 -1
  170. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  171. package/dist/connectors/codex-materialize-runner.js +12 -11
  172. package/dist/connectors/codex-materialize.d.ts +7 -12
  173. package/dist/connectors/codex-materialize.js +2 -1
  174. package/dist/connectors/index.d.ts +1 -1
  175. package/dist/connectors/index.js +12 -11
  176. package/dist/consolidation-provenance-check.d.ts +2 -2
  177. package/dist/consolidation-undo.d.ts +2 -2
  178. package/dist/contradiction/index.d.ts +2 -2
  179. package/dist/contradiction/index.js +4 -4
  180. package/dist/converge-config.d.ts +1 -1
  181. package/dist/convergence-refresh.d.ts +2 -2
  182. package/dist/convergence-refresh.js +12 -12
  183. package/dist/conversation-index/backend.d.ts +1 -1
  184. package/dist/conversation-index/chunker.d.ts +1 -1
  185. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  186. package/dist/conversation-index/indexer.d.ts +1 -1
  187. package/dist/conversation-index/search.d.ts +1 -1
  188. package/dist/corpus-watermark.d.ts +1 -1
  189. package/dist/corpus-watermark.js +12 -12
  190. package/dist/day-summary.d.ts +1 -1
  191. package/dist/day-summary.js +3 -2
  192. package/dist/delinearize.d.ts +1 -1
  193. package/dist/dependency-propagation-config.d.ts +1 -1
  194. package/dist/{dependency-propagation-delivery-C_K_AHcv.d.ts → dependency-propagation-delivery-DdwSc1qh.d.ts} +1 -1
  195. package/dist/direct-answer-wiring.d.ts +1 -1
  196. package/dist/direct-answer.d.ts +1 -1
  197. package/dist/embedding-fallback.d.ts +1 -1
  198. package/dist/enrichment/index.d.ts +1 -1
  199. package/dist/entity-retrieval.d.ts +2 -2
  200. package/dist/entity-retrieval.js +10 -10
  201. package/dist/entity-schema.d.ts +1 -1
  202. package/dist/explicit-capture.d.ts +10 -9
  203. package/dist/external-wiki-access.d.ts +12 -11
  204. package/dist/external-wiki-access.js +26 -25
  205. package/dist/external-wiki-collection-registration.d.ts +1 -1
  206. package/dist/external-wiki-collection.d.ts +1 -1
  207. package/dist/external-wiki-mcp-tools.d.ts +12 -11
  208. package/dist/extraction-error-classification.d.ts +1 -1
  209. package/dist/extraction-faithfulness.d.ts +1 -1
  210. package/dist/extraction-judge-telemetry.d.ts +1 -1
  211. package/dist/extraction-judge-training.d.ts +1 -1
  212. package/dist/extraction-judge.d.ts +1 -1
  213. package/dist/extraction-liveness.d.ts +1 -1
  214. package/dist/extraction-normalization.d.ts +1 -1
  215. package/dist/extraction-prompt.d.ts +1 -1
  216. package/dist/extraction-source-grounding-rules.d.ts +1 -1
  217. package/dist/extraction-source-grounding.d.ts +1 -1
  218. package/dist/extraction.d.ts +1 -1
  219. package/dist/extraction.js +16 -15
  220. package/dist/fallback-llm.d.ts +1 -1
  221. package/dist/graph-dashboard-diff.d.ts +1 -1
  222. package/dist/graph-dashboard-key.d.ts +1 -1
  223. package/dist/graph-dashboard-parser.d.ts +1 -1
  224. package/dist/graph-edge-reinforcement.d.ts +1 -1
  225. package/dist/graph-path-reconstruction.d.ts +1 -1
  226. package/dist/graph-path-scoring.d.ts +1 -1
  227. package/dist/graph-snapshot.d.ts +1 -1
  228. package/dist/graph.d.ts +1 -1
  229. package/dist/harmonic-retrieval.js +1 -1
  230. package/dist/importance.d.ts +1 -1
  231. package/dist/importers/index.d.ts +1 -1
  232. package/dist/in-flight-reads.d.ts +1 -1
  233. package/dist/index.d.ts +698 -697
  234. package/dist/index.js +81 -80
  235. package/dist/intent.d.ts +1 -1
  236. package/dist/lcm/archive.d.ts +3 -2
  237. package/dist/lcm/archive.js +2 -1
  238. package/dist/lcm/engine.d.ts +2 -1
  239. package/dist/lcm/engine.js +5 -4
  240. package/dist/lcm/index.d.ts +2 -1
  241. package/dist/lcm/index.js +7 -6
  242. package/dist/lcm/recall.d.ts +1 -0
  243. package/dist/lcm/summarizer.d.ts +1 -0
  244. package/dist/lcm/summarizer.js +3 -2
  245. package/dist/lcm/tools.d.ts +2 -1
  246. package/dist/lifecycle.d.ts +1 -1
  247. package/dist/live-connectors-runner.d.ts +1 -1
  248. package/dist/local-llm.d.ts +2 -3
  249. package/dist/local-llm.js +2 -1
  250. package/dist/local-model-endpoint.d.ts +1 -1
  251. package/dist/maintenance/memory-governance.d.ts +1 -1
  252. package/dist/maintenance/memory-governance.js +10 -10
  253. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
  254. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
  255. package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
  256. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  257. package/dist/{maintenance-DdP931i9.d.ts → maintenance-DvkiF7fm.d.ts} +3 -3
  258. package/dist/mcp-memory-inspector-app.d.ts +12 -11
  259. package/dist/memory-action-policy.d.ts +1 -1
  260. package/dist/memory-cache.d.ts +1 -1
  261. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  262. package/dist/memory-projection-store.d.ts +1 -1
  263. package/dist/memory-provenance.d.ts +1 -1
  264. package/dist/memory-snapshot.d.ts +1 -1
  265. package/dist/memory-worth-outcomes.d.ts +2 -2
  266. package/dist/models-json.d.ts +1 -1
  267. package/dist/namespaces/migrate.d.ts +3 -3
  268. package/dist/namespaces/migrate.js +11 -11
  269. package/dist/namespaces/principal.d.ts +1 -1
  270. package/dist/namespaces/search.d.ts +1 -1
  271. package/dist/namespaces/search.js +7 -7
  272. package/dist/namespaces/storage.d.ts +3 -3
  273. package/dist/namespaces/storage.js +10 -10
  274. package/dist/native-knowledge.d.ts +1 -1
  275. package/dist/offline-sync-impression-drain.d.ts +1 -1
  276. package/dist/operator-doctor-corpus.d.ts +1 -1
  277. package/dist/operator-doctor-corpus.js +13 -13
  278. package/dist/operator-doctor-replica.d.ts +1 -1
  279. package/dist/operator-toolkit.d.ts +3 -3
  280. package/dist/operator-toolkit.js +18 -18
  281. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  282. package/dist/orchestration/maintenance.d.ts +4 -4
  283. package/dist/orchestration/maintenance.js +15 -15
  284. package/dist/{orchestrator-DxUWX7gs.d.ts → orchestrator-CXPIzs3D.d.ts} +9 -9
  285. package/dist/orchestrator.d.ts +10 -9
  286. package/dist/orchestrator.js +77 -76
  287. package/dist/patterns-cli.d.ts +1 -1
  288. package/dist/{pipeline-BKUMU1vU.d.ts → pipeline-BuYWU9rt.d.ts} +1 -1
  289. package/dist/policy-runtime.d.ts +1 -1
  290. package/dist/proactive-contention.d.ts +1 -1
  291. package/dist/provenance.d.ts +1 -1
  292. package/dist/{public-http-C01DLhAp.d.ts → public-http-m9x1Qyzn.d.ts} +1 -1
  293. package/dist/{qmd-DJP-GLFe.d.ts → qmd-DYnrPw-m.d.ts} +1 -1
  294. package/dist/qmd-preflight.d.ts +2 -2
  295. package/dist/qmd-recall-cache.d.ts +1 -1
  296. package/dist/qmd.d.ts +2 -2
  297. package/dist/recall-concurrency-config.d.ts +1 -1
  298. package/dist/recall-disclosure-escalation.d.ts +1 -1
  299. package/dist/recall-explain-renderer.d.ts +1 -1
  300. package/dist/recall-explain-renderer.js +4 -3
  301. package/dist/recall-memory-map.d.ts +1 -1
  302. package/dist/recall-planner-llm.d.ts +1 -1
  303. package/dist/recall-state.d.ts +1 -1
  304. package/dist/recall-tag-filter.d.ts +1 -1
  305. package/dist/recall-timings.d.ts +1 -1
  306. package/dist/recall-xray-cli.d.ts +1 -1
  307. package/dist/recall-xray-cli.js +5 -4
  308. package/dist/recall-xray-renderer.d.ts +1 -1
  309. package/dist/recall-xray-renderer.js +4 -3
  310. package/dist/recall-xray.d.ts +2 -7
  311. package/dist/recall-xray.js +3 -2
  312. package/dist/reconcile/cursor.d.ts +1 -1
  313. package/dist/reconcile/manifest.d.ts +1 -1
  314. package/dist/reconcile/plan.d.ts +1 -1
  315. package/dist/replay/normalizers/chatgpt.d.ts +1 -1
  316. package/dist/replay/normalizers/claude.d.ts +1 -1
  317. package/dist/replay/normalizers/openclaw.d.ts +1 -1
  318. package/dist/replay/normalizers/shared.d.ts +1 -1
  319. package/dist/replay/runner.d.ts +1 -1
  320. package/dist/replay/types.d.ts +1 -1
  321. package/dist/replica-divergence.d.ts +1 -1
  322. package/dist/replica-peers-config.d.ts +1 -1
  323. package/dist/resolve-auth-token.d.ts +1 -1
  324. package/dist/resume-bundles.js +4 -3
  325. package/dist/retrieval-agents.d.ts +2 -2
  326. package/dist/retrieval-tiers.d.ts +1 -1
  327. package/dist/routing/engine.d.ts +1 -1
  328. package/dist/routing/store.d.ts +1 -1
  329. package/dist/salvage-envelope.d.ts +1 -1
  330. package/dist/schemas.d.ts +138 -138
  331. package/dist/{scope-profiles-CaKcnB-6.d.ts → scope-profiles-C1QG4Cmv.d.ts} +1 -1
  332. package/dist/search/embed-helper.d.ts +1 -1
  333. package/dist/search/factory.d.ts +1 -1
  334. package/dist/search/factory.js +6 -6
  335. package/dist/search/index.d.ts +1 -1
  336. package/dist/search/index.js +11 -11
  337. package/dist/search/lancedb-backend.d.ts +1 -1
  338. package/dist/search/lancedb-backend.js +2 -2
  339. package/dist/search/meilisearch-backend.d.ts +1 -1
  340. package/dist/search/meilisearch-backend.js +2 -2
  341. package/dist/search/noop-backend.d.ts +1 -1
  342. package/dist/search/orama-backend.d.ts +1 -1
  343. package/dist/search/orama-backend.js +2 -2
  344. package/dist/search/port.d.ts +1 -1
  345. package/dist/search/remote-backend.d.ts +1 -1
  346. package/dist/semantic-chunking.js +3 -2
  347. package/dist/{semantic-consolidation-P0eMWqh-.d.ts → semantic-consolidation-DMeX8XS0.d.ts} +1 -1
  348. package/dist/semantic-consolidation.d.ts +2 -2
  349. package/dist/semantic-consolidation.js +14 -13
  350. package/dist/semantic-rule-promotion.js +10 -10
  351. package/dist/semantic-rule-verifier.d.ts +1 -1
  352. package/dist/semantic-rule-verifier.js +10 -10
  353. package/dist/{service-D5GQLxq_.d.ts → service-CT3ENiTO.d.ts} +2 -2
  354. package/dist/session-observer-bands.d.ts +1 -1
  355. package/dist/session-observer-state.d.ts +1 -1
  356. package/dist/session-observer-state.js +1 -1
  357. package/dist/shared-context/manager.d.ts +9 -9
  358. package/dist/signal.d.ts +1 -1
  359. package/dist/source-agent-qualifier.d.ts +1 -1
  360. package/dist/source-agent-qualifier.js +12 -11
  361. package/dist/{storage-DIyil2ll.d.ts → storage-B0L76y7N.d.ts} +1 -1
  362. package/dist/storage.d.ts +2 -2
  363. package/dist/storage.js +9 -9
  364. package/dist/summarizer.d.ts +1 -1
  365. package/dist/summarizer.js +3 -2
  366. package/dist/summary-snapshot.d.ts +1 -1
  367. package/dist/support-passport/index.d.ts +15 -14
  368. package/dist/support-passport/index.js +28 -27
  369. package/dist/temporal-supersession.d.ts +2 -2
  370. package/dist/temporal-timeline-recall.d.ts +1 -1
  371. package/dist/temporal-validity.d.ts +1 -1
  372. package/dist/threading.d.ts +1 -1
  373. package/dist/tier-migration.d.ts +2 -2
  374. package/dist/tier-routing.d.ts +1 -1
  375. package/dist/token-estimate.d.ts +10 -0
  376. package/dist/token-estimate.js +8 -0
  377. package/dist/token-estimate.js.map +1 -0
  378. package/dist/topics.d.ts +1 -1
  379. package/dist/transcript.d.ts +1 -6
  380. package/dist/transcript.js +2 -1
  381. package/dist/transfer/backup.js +2 -2
  382. package/dist/transfer/capsule-export.js +2 -2
  383. package/dist/transfer/capsule-import.js +2 -2
  384. package/dist/transfer/types.d.ts +66 -66
  385. package/dist/trust-score-stage.d.ts +1 -1
  386. package/dist/trust-score.d.ts +1 -1
  387. package/dist/{types-CbXieHKr.d.ts → types-D7pB0QwO.d.ts} +1 -1
  388. package/dist/types.d.ts +1 -1
  389. package/dist/types.js +1 -1
  390. package/dist/utility-runtime.d.ts +1 -1
  391. package/dist/verified-recall.js +10 -10
  392. package/dist/write-envelope.d.ts +1 -1
  393. package/package.json +12 -2
  394. package/src/chunking.ts +124 -45
  395. package/src/config.ts +2 -0
  396. package/src/connectors/codex-materialize.ts +21 -26
  397. package/src/lcm/archive.ts +4 -5
  398. package/src/lcm/engine.ts +55 -34
  399. package/src/lcm/summarizer.ts +27 -16
  400. package/src/lcm-engine.test.ts +82 -2
  401. package/src/local-llm.ts +4 -6
  402. package/src/memory-extension-host/render-extensions-block.ts +3 -9
  403. package/src/orchestration/recall-section-coordinator.ts +93 -27
  404. package/src/recall-token-budget.test.ts +51 -0
  405. package/src/recall-xray.ts +4 -11
  406. package/src/semantic-chunking.ts +14 -59
  407. package/src/session-observer-state.ts +3 -3
  408. package/src/testing/subjects/recall-budget.test.ts +21 -5
  409. package/src/token-estimate.test.ts +38 -0
  410. package/src/token-estimate.ts +22 -0
  411. package/src/transcript.test.ts +38 -3
  412. package/src/transcript.ts +58 -59
  413. package/src/types.ts +1 -1
  414. package/dist/chunk-5ZIWRMZM.js.map +0 -1
  415. package/dist/chunk-C5B3CIMJ.js.map +0 -1
  416. package/dist/chunk-HYQJQGD3.js.map +0 -1
  417. package/dist/chunk-KSXJOC4F.js.map +0 -1
  418. package/dist/chunk-LTAVSOZW.js.map +0 -1
  419. package/dist/chunk-P4BC54KI.js.map +0 -1
  420. package/dist/chunk-PBGWYM3H.js.map +0 -1
  421. package/dist/chunk-QKPDN325.js.map +0 -1
  422. package/dist/chunk-UQ7RN5HK.js +0 -126
  423. package/dist/chunk-UQ7RN5HK.js.map +0 -1
  424. package/dist/chunk-Z54EM5YZ.js.map +0 -1
  425. /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-GWVG7LGC.js.map} +0 -0
  426. /package/dist/{chunk-YTZA7U6Q.js.map → chunk-2VHCLHZ5.js.map} +0 -0
  427. /package/dist/{chunk-NEJQ65NV.js.map → chunk-4CLE2LXB.js.map} +0 -0
  428. /package/dist/{chunk-VDX2J7OX.js.map → chunk-5W7CTZ2E.js.map} +0 -0
  429. /package/dist/{chunk-CZUHNJHX.js.map → chunk-6C7Q6NG3.js.map} +0 -0
  430. /package/dist/{chunk-4O6R3GPK.js.map → chunk-6W2ISYUV.js.map} +0 -0
  431. /package/dist/{chunk-3AJOJOFP.js.map → chunk-77DAFTUU.js.map} +0 -0
  432. /package/dist/{chunk-IKT6ZVO7.js.map → chunk-7DHFB5SG.js.map} +0 -0
  433. /package/dist/{chunk-SFR7J2G6.js.map → chunk-7G34CBKS.js.map} +0 -0
  434. /package/dist/{chunk-IT57AOMC.js.map → chunk-AH2NX6S7.js.map} +0 -0
  435. /package/dist/{chunk-FI3PIOOI.js.map → chunk-B4CSPFYN.js.map} +0 -0
  436. /package/dist/{chunk-7PJARJS4.js.map → chunk-BPM6UHUL.js.map} +0 -0
  437. /package/dist/{chunk-PPUTUOC3.js.map → chunk-BTHDQOF4.js.map} +0 -0
  438. /package/dist/{chunk-AW4A4XOP.js.map → chunk-CZGOLAEW.js.map} +0 -0
  439. /package/dist/{chunk-5PGYCWA7.js.map → chunk-DE6YCZEE.js.map} +0 -0
  440. /package/dist/{chunk-JOA4Q765.js.map → chunk-EUYWQ7TS.js.map} +0 -0
  441. /package/dist/{chunk-O5AFC5KM.js.map → chunk-EWL5DICB.js.map} +0 -0
  442. /package/dist/{chunk-4YZCIK5E.js.map → chunk-EZXGJJXM.js.map} +0 -0
  443. /package/dist/{chunk-GXIY65UH.js.map → chunk-FI5EAP4N.js.map} +0 -0
  444. /package/dist/{chunk-YOKSRIFI.js.map → chunk-FUWS2MNJ.js.map} +0 -0
  445. /package/dist/{chunk-6RK2X7BR.js.map → chunk-G3JFFUKR.js.map} +0 -0
  446. /package/dist/{chunk-VANZKHTN.js.map → chunk-G5OB4XZX.js.map} +0 -0
  447. /package/dist/{chunk-66SLUXKM.js.map → chunk-HK6KYIF6.js.map} +0 -0
  448. /package/dist/{chunk-XBGLEOID.js.map → chunk-I4BLRM7X.js.map} +0 -0
  449. /package/dist/{chunk-6SX4MY5Q.js.map → chunk-J65AKOD6.js.map} +0 -0
  450. /package/dist/{chunk-BPWZDHFM.js.map → chunk-J6QIAYNR.js.map} +0 -0
  451. /package/dist/{chunk-XL3TATMJ.js.map → chunk-JHMAYABJ.js.map} +0 -0
  452. /package/dist/{chunk-F7M36OG2.js.map → chunk-JNWISMYG.js.map} +0 -0
  453. /package/dist/{chunk-VYIXCMUP.js.map → chunk-KBIIE57I.js.map} +0 -0
  454. /package/dist/{chunk-24PNX2GF.js.map → chunk-KXYMU4IN.js.map} +0 -0
  455. /package/dist/{chunk-FKTD7IVS.js.map → chunk-L3JVAM5Q.js.map} +0 -0
  456. /package/dist/{chunk-LIF6DO6U.js.map → chunk-MG7GI4TT.js.map} +0 -0
  457. /package/dist/{chunk-RUZM6Z52.js.map → chunk-MMZK2DWN.js.map} +0 -0
  458. /package/dist/{chunk-NTJ6TY44.js.map → chunk-ODAE36PG.js.map} +0 -0
  459. /package/dist/{chunk-YLHM5BQQ.js.map → chunk-OLX2HJFW.js.map} +0 -0
  460. /package/dist/{chunk-DNC4A7RS.js.map → chunk-OU7RWWW6.js.map} +0 -0
  461. /package/dist/{chunk-L3LRWDJW.js.map → chunk-OV36CROW.js.map} +0 -0
  462. /package/dist/{chunk-BXLOS5AJ.js.map → chunk-OWHERGF2.js.map} +0 -0
  463. /package/dist/{chunk-CSV34HIW.js.map → chunk-P5OYNV7B.js.map} +0 -0
  464. /package/dist/{chunk-SNW3YRO6.js.map → chunk-P6GOYLHN.js.map} +0 -0
  465. /package/dist/{chunk-USGKHF36.js.map → chunk-QUJC22CT.js.map} +0 -0
  466. /package/dist/{chunk-BVRREV3N.js.map → chunk-R7IV3EUJ.js.map} +0 -0
  467. /package/dist/{chunk-D4LVHPOA.js.map → chunk-RXN7Z2V5.js.map} +0 -0
  468. /package/dist/{chunk-7VSHDLJQ.js.map → chunk-T2VJ2EO5.js.map} +0 -0
  469. /package/dist/{chunk-U7BMN33M.js.map → chunk-UBSM6TZP.js.map} +0 -0
  470. /package/dist/{chunk-DQEMWVMT.js.map → chunk-UVYI6VIX.js.map} +0 -0
  471. /package/dist/{chunk-U7XLOSG2.js.map → chunk-VIM6QH4D.js.map} +0 -0
  472. /package/dist/{chunk-LJJWFV2Q.js.map → chunk-VJXSAJFW.js.map} +0 -0
  473. /package/dist/{chunk-HZC3OECF.js.map → chunk-VOKK4WCG.js.map} +0 -0
  474. /package/dist/{chunk-LRDP2AC2.js.map → chunk-YBCZTBN2.js.map} +0 -0
  475. /package/dist/{chunk-USAUBGBC.js.map → chunk-YKZFX2HQ.js.map} +0 -0
  476. /package/dist/{chunk-I2GIYTRU.js.map → chunk-ZOGOOSB3.js.map} +0 -0
@@ -7,6 +7,7 @@ import test from "node:test";
7
7
  import { LcmArchive } from "./lcm/archive.js";
8
8
  import { LcmEngine, extractLcmConfig } from "./lcm/engine.js";
9
9
  import { openLcmDatabase } from "./lcm/schema.js";
10
+ import { estimateTokenCount } from "./token-estimate.js";
10
11
  import type { PluginConfig } from "./types.js";
11
12
 
12
13
  function createPluginConfig(memoryDir: string): PluginConfig {
@@ -661,20 +662,99 @@ test("expandContext preserves first, middle, and last row identity when truncate
661
662
  session_id: "session-truncated",
662
663
  turn_index: 2,
663
664
  role: "assistant",
664
- content: "BBBBBB",
665
+ content: "BBBBBBBB",
665
666
  },
666
667
  {
667
668
  id: last.id,
668
669
  session_id: "session-truncated",
669
670
  turn_index: 4,
670
671
  role: "assistant",
671
- content: "DDDDDD",
672
+ content: "DDDD",
672
673
  },
673
674
  ]);
674
675
  } finally {
675
676
  await rm(memoryDir, { recursive: true, force: true });
676
677
  }
677
678
  });
679
+ test("expandContext applies token budgets to wide-script messages", async () => {
680
+ const memoryDir = await mkdtemp(
681
+ path.join(os.tmpdir(), "engram-lcm-expand-wide-script-"),
682
+ );
683
+
684
+ try {
685
+ const engine = new LcmEngine(
686
+ createPluginConfig(memoryDir),
687
+ async () => "summary",
688
+ );
689
+ await engine.ensureInitialized();
690
+
691
+ const db = openLcmDatabase(memoryDir);
692
+ try {
693
+ const archive = new LcmArchive(db);
694
+ archive.appendMessages("session-wide-script", [
695
+ { turnIndex: 1, role: "user", content: "日本語".repeat(40) },
696
+ { turnIndex: 2, role: "assistant", content: "日本語".repeat(40) },
697
+ { turnIndex: 3, role: "user", content: "日本語".repeat(40) },
698
+ ]);
699
+ } finally {
700
+ db.close();
701
+ }
702
+
703
+ const expanded = await engine.expandContext(
704
+ "session-wide-script",
705
+ 1,
706
+ 3,
707
+ 10,
708
+ );
709
+
710
+ assert.ok(
711
+ expanded.reduce((sum, message) => sum + estimateTokenCount(message.content), 0) <= 10,
712
+ );
713
+ assert.ok(expanded.every((message) => message.content.length < 40));
714
+ } finally {
715
+ await rm(memoryDir, { recursive: true, force: true });
716
+ }
717
+ });
718
+ test("expandContext preserves the final message at the smallest positive budget", async () => {
719
+ const memoryDir = await mkdtemp(
720
+ path.join(os.tmpdir(), "engram-lcm-expand-minimum-budget-"),
721
+ );
722
+
723
+ let engine: LcmEngine | undefined;
724
+ try {
725
+ engine = new LcmEngine(
726
+ createPluginConfig(memoryDir),
727
+ async () => "summary",
728
+ );
729
+ await engine.ensureInitialized();
730
+
731
+ const db = openLcmDatabase(memoryDir);
732
+ try {
733
+ const archive = new LcmArchive(db);
734
+ archive.appendMessages("session-minimum-budget", [
735
+ { turnIndex: 1, role: "user", content: "first message" },
736
+ { turnIndex: 2, role: "assistant", content: "最後のメッセージ" },
737
+ ]);
738
+ } finally {
739
+ db.close();
740
+ }
741
+
742
+ const expanded = await engine.expandContext(
743
+ "session-minimum-budget",
744
+ 1,
745
+ 2,
746
+ 1,
747
+ );
748
+
749
+ assert.equal(expanded.at(-1)?.turn_index, 2);
750
+ assert.ok(
751
+ expanded.reduce((sum, message) => sum + estimateTokenCount(message.content), 0) <= 1,
752
+ );
753
+ } finally {
754
+ engine?.close();
755
+ await rm(memoryDir, { recursive: true, force: true });
756
+ }
757
+ });
678
758
 
679
759
  test("assembleRecallWithTrace preserves text and reports selected summary metadata", async () => {
680
760
  const memoryDir = await mkdtemp(
package/src/local-llm.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { log } from "./logger.js";
2
+ import { estimateTokenCount } from "./token-estimate.js";
2
3
  import type { PluginConfig } from "./types.js";
3
4
  import fs from "node:fs";
4
5
  import os from "node:os";
@@ -1431,18 +1432,15 @@ export class LocalLlmClient {
1431
1432
 
1432
1433
  return await this.runChatCompletionRequest(messages, options);
1433
1434
  }
1434
-
1435
1435
  /**
1436
- * Estimate tokens when local LLM doesn't return usage stats
1437
- * Rough estimate: 1 token ≈ 4 characters
1436
+ * Estimate tokens when local LLM does not return usage stats.
1438
1437
  */
1439
1438
  private estimateTokens(
1440
1439
  messages: Array<{ role: string; content: string }>,
1441
1440
  response: string
1442
1441
  ): { promptTokens: number; completionTokens: number; totalTokens: number } {
1443
- const promptChars = messages.reduce((sum, m) => sum + m.content.length, 0);
1444
- const promptTokens = Math.ceil(promptChars / 4);
1445
- const completionTokens = Math.ceil(response.length / 4);
1442
+ const promptTokens = messages.reduce((sum, message) => sum + estimateTokenCount(message.content), 0);
1443
+ const completionTokens = estimateTokenCount(response);
1446
1444
 
1447
1445
  return {
1448
1446
  promptTokens,
@@ -6,16 +6,10 @@
6
6
  * truncates with a footer listing omitted extensions when over budget.
7
7
  */
8
8
 
9
+ import { estimateTokenCount } from "../token-estimate.js";
9
10
  import { REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT } from "./host-discovery.js";
10
11
  import type { DiscoveredExtension } from "./types.js";
11
12
 
12
- /**
13
- * Approximate token count using the 4 chars per token heuristic.
14
- */
15
- function estimateTokens(text: string): number {
16
- return Math.ceil(text.length / 4);
17
- }
18
-
19
13
  /**
20
14
  * Render a markdown block containing extension instructions for injection
21
15
  * into consolidation prompts.
@@ -36,14 +30,14 @@ extension produces or curates.
36
30
  `;
37
31
 
38
32
  let budget = REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT;
39
- budget -= estimateTokens(header);
33
+ budget -= estimateTokenCount(header);
40
34
 
41
35
  const inlined: string[] = [];
42
36
  const omitted: string[] = [];
43
37
 
44
38
  for (const ext of extensions) {
45
39
  const block = `### remnic-extension/${ext.name}\n\`\`\`\n${ext.instructions}\n\`\`\`\n\n`;
46
- const cost = estimateTokens(block);
40
+ const cost = estimateTokenCount(block);
47
41
  if (cost <= budget) {
48
42
  inlined.push(block);
49
43
  budget -= cost;
@@ -17,8 +17,9 @@
17
17
  * TierMigrationCoordinator accessor pattern.
18
18
  */
19
19
 
20
- import type { PluginConfig, RecallSectionConfig } from "../types.js";
21
20
  import type { LastRecallBudgetSummary } from "../recall-state.js";
21
+ import { estimateTokenCount } from "../token-estimate.js";
22
+ import type { PluginConfig, RecallSectionConfig } from "../types.js";
22
23
 
23
24
  export interface RecallSectionAppendOptions {
24
25
  atomic?: boolean;
@@ -179,18 +180,54 @@ export class RecallSectionCoordinator {
179
180
  const end = boundary > 0 ? boundary : contentLimit;
180
181
  return `${content.slice(0, end).trimEnd()}${suffix}`;
181
182
  }
182
-
183
183
  truncateRecallSectionToBudget(
184
184
  content: string,
185
185
  maxChars: number,
186
+ maxTokens?: number,
186
187
  ): string {
187
- if (maxChars <= 0) return "";
188
- if (content.length <= maxChars) return content;
188
+ if (maxChars <= 0 || maxTokens === 0) return "";
189
189
  const suffix = "\n\n...(memory context trimmed)";
190
- if (maxChars <= suffix.length) {
191
- return content.slice(0, maxChars);
190
+ const charBounded =
191
+ content.length <= maxChars
192
+ ? content
193
+ : maxChars <= suffix.length
194
+ ? content.slice(0, maxChars)
195
+ : `${content.slice(0, maxChars - suffix.length)}${suffix}`;
196
+ if (maxTokens === undefined || estimateTokenCount(charBounded) <= maxTokens) {
197
+ return charBounded;
192
198
  }
193
- return `${content.slice(0, maxChars - suffix.length)}${suffix}`;
199
+ const markerFits =
200
+ maxChars > suffix.length && estimateTokenCount(suffix) <= maxTokens;
201
+ const tokenSource = content.length > maxChars ? content.slice(0, maxChars) : content;
202
+ const codePoints = [...tokenSource];
203
+ let low = 0;
204
+ let high = codePoints.length;
205
+ while (low < high) {
206
+ const mid = Math.ceil((low + high) / 2);
207
+ const prefix = codePoints.slice(0, mid).join("");
208
+ const candidate = markerFits ? `${prefix}${suffix}` : prefix;
209
+ if (candidate.length <= maxChars && estimateTokenCount(candidate) <= maxTokens) {
210
+ low = mid;
211
+ } else {
212
+ high = mid - 1;
213
+ }
214
+ }
215
+ if (markerFits && low === 0) {
216
+ low = 0;
217
+ high = codePoints.length;
218
+ while (low < high) {
219
+ const mid = Math.ceil((low + high) / 2);
220
+ const prefix = codePoints.slice(0, mid).join("");
221
+ if (prefix.length <= maxChars && estimateTokenCount(prefix) <= maxTokens) {
222
+ low = mid;
223
+ } else {
224
+ high = mid - 1;
225
+ }
226
+ }
227
+ return codePoints.slice(0, low).join("");
228
+ }
229
+ const prefix = codePoints.slice(0, low).join("");
230
+ return markerFits ? `${prefix}${suffix}` : prefix;
194
231
  }
195
232
 
196
233
 
@@ -280,6 +317,7 @@ export class RecallSectionCoordinator {
280
317
  }
281
318
 
282
319
  const budget = this.getRecallBudgetChars(budgetOverride);
320
+ const tokenBudget = Math.max(0, Math.floor(this.getConfig().maxMemoryTokens));
283
321
  const candidateMemoryChunks =
284
322
  orderedSections
285
323
  .find((section) => section.id === "memories")
@@ -312,10 +350,19 @@ export class RecallSectionCoordinator {
312
350
  const memoryIndex = allocationOrder.indexOf("memories");
313
351
  const memoryBudget = this.getRecallSectionMaxChars("memories") ?? budget;
314
352
  const memoryAllocationBudget = Math.min(budget, memoryBudget);
315
- let firstAtomicMemoryReserveChars = 0;
353
+ let firstAtomicMemoryReserve:
354
+ | { chars: number; tokens: number }
355
+ | undefined;
316
356
  for (const chunk of memorySection?.chunks ?? []) {
317
- if (chunk.atomic && chunk.content.length <= memoryAllocationBudget) {
318
- firstAtomicMemoryReserveChars = chunk.content.length;
357
+ if (!chunk.atomic || chunk.content.length > memoryAllocationBudget) {
358
+ continue;
359
+ }
360
+ const tokens = estimateTokenCount(chunk.content);
361
+ if (tokens <= tokenBudget) {
362
+ firstAtomicMemoryReserve = {
363
+ chars: chunk.content.length,
364
+ tokens,
365
+ };
319
366
  break;
320
367
  }
321
368
  }
@@ -323,6 +370,7 @@ export class RecallSectionCoordinator {
323
370
  const includedMemoryPaths: string[] = [];
324
371
  const includedMemoryNamespaces: Array<string | undefined> = [];
325
372
  let usedChars = 0;
373
+ let usedTokens = 0;
326
374
  let truncated = false;
327
375
  const selected = new Map<string, string>();
328
376
 
@@ -331,19 +379,30 @@ export class RecallSectionCoordinator {
331
379
  if (!section || selected.has(id)) continue;
332
380
  const separatorChars = selected.size > 0 ? separator.length : 0;
333
381
  const available = budget - usedChars - separatorChars;
382
+ const separatorTokens = selected.size > 0 ? estimateTokenCount(separator) : 0;
383
+ const availableTokens = tokenBudget - usedTokens - separatorTokens;
334
384
  const sectionMaxChars = this.getRecallSectionMaxChars(id);
335
- const reservesFirstMemory =
336
- memoryIndex > allocationOrder.indexOf(id) &&
337
- firstAtomicMemoryReserveChars > 0;
338
- const memoryReserve = reservesFirstMemory
339
- ? firstAtomicMemoryReserveChars + separator.length
385
+ const reservedMemory =
386
+ memoryIndex > allocationOrder.indexOf(id)
387
+ ? firstAtomicMemoryReserve
388
+ : undefined;
389
+ const memoryReserveSeparatorChars = separator.length;
390
+ const memoryReserveSeparatorTokens = estimateTokenCount(separator);
391
+ const memoryReserve = reservedMemory
392
+ ? reservedMemory.chars + memoryReserveSeparatorChars
340
393
  : 0;
341
394
  const availableAfterMemoryReserve = available - memoryReserve;
342
395
  const allocatedSectionAvailable =
343
396
  typeof sectionMaxChars === "number"
344
397
  ? Math.min(availableAfterMemoryReserve, sectionMaxChars)
345
398
  : availableAfterMemoryReserve;
346
- if (allocatedSectionAvailable <= 0) {
399
+ const availableAfterMemoryReserveTokens = reservedMemory
400
+ ? availableTokens -
401
+ reservedMemory.tokens -
402
+ memoryReserveSeparatorTokens
403
+ : availableTokens;
404
+ const allocatedTokenAvailable = availableAfterMemoryReserveTokens;
405
+ if (allocatedSectionAvailable <= 0 || allocatedTokenAvailable <= 0) {
347
406
  truncated = true;
348
407
  continue;
349
408
  }
@@ -372,13 +431,11 @@ export class RecallSectionCoordinator {
372
431
  Math.min(profileLimit, allocatedSectionAvailable),
373
432
  )
374
433
  : content;
375
- finalContent =
376
- id === "profile"
377
- ? boundedContent
378
- : this.truncateRecallSectionToBudget(
379
- boundedContent,
380
- allocatedSectionAvailable,
381
- );
434
+ finalContent = this.truncateRecallSectionToBudget(
435
+ boundedContent,
436
+ allocatedSectionAvailable,
437
+ allocatedTokenAvailable,
438
+ );
382
439
  if (!finalContent) {
383
440
  truncated = true;
384
441
  continue;
@@ -400,7 +457,10 @@ export class RecallSectionCoordinator {
400
457
  const candidate = rendered
401
458
  ? `${rendered}\n\n${chunk.content}`
402
459
  : chunk.content;
403
- if (candidate.length <= allocatedSectionAvailable) {
460
+ if (
461
+ candidate.length <= allocatedSectionAvailable &&
462
+ estimateTokenCount(candidate) <= allocatedTokenAvailable
463
+ ) {
404
464
  rendered = candidate;
405
465
  includedLeadingContent = true;
406
466
  } else {
@@ -412,12 +472,16 @@ export class RecallSectionCoordinator {
412
472
  const candidate = rendered
413
473
  ? `${rendered}\n\n${chunk.content}`
414
474
  : chunk.content;
415
- if (candidate.length > allocatedSectionAvailable) {
475
+ if (
476
+ candidate.length > allocatedSectionAvailable ||
477
+ estimateTokenCount(candidate) > allocatedTokenAvailable
478
+ ) {
416
479
  if (
417
480
  chunk.atomic &&
418
481
  includedAtomicCount === 0 &&
419
482
  includedLeadingContent &&
420
- chunk.content.length <= allocatedSectionAvailable
483
+ chunk.content.length <= allocatedSectionAvailable &&
484
+ estimateTokenCount(chunk.content) <= allocatedTokenAvailable
421
485
  ) {
422
486
  rendered = chunk.content;
423
487
  includedLeadingContent = false;
@@ -435,7 +499,8 @@ export class RecallSectionCoordinator {
435
499
  !chunk.atomic &&
436
500
  includedAtomicCount === 0 &&
437
501
  includedLeadingContent &&
438
- chunk.content.length <= allocatedSectionAvailable
502
+ chunk.content.length <= allocatedSectionAvailable &&
503
+ estimateTokenCount(chunk.content) <= allocatedTokenAvailable
439
504
  ) {
440
505
  rendered = chunk.content;
441
506
  includedLeadingContent = false;
@@ -479,6 +544,7 @@ export class RecallSectionCoordinator {
479
544
  }
480
545
  selected.set(id, finalContent);
481
546
  usedChars += separatorChars + finalContent.length;
547
+ usedTokens += separatorTokens + estimateTokenCount(finalContent);
482
548
  }
483
549
 
484
550
  const sections: string[] = [];
@@ -0,0 +1,51 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { parseConfig } from "./config.js";
5
+ import { RecallSectionCoordinator } from "./orchestration/recall-section-coordinator.js";
6
+ import { estimateTokenCount } from "./token-estimate.js";
7
+
8
+ test("recall derived budget keeps Japanese injection within maxMemoryTokens", () => {
9
+ const config = parseConfig({
10
+ maxMemoryTokens: 12,
11
+ recallPipeline: [{ id: "memories", enabled: true }],
12
+ });
13
+ const coordinator = new RecallSectionCoordinator({ getConfig: () => config });
14
+ const buckets = new Map<string, string[]>();
15
+
16
+ coordinator.appendRecallSection(buckets, "memories", "日本語の記憶。".repeat(20));
17
+ const assembled = coordinator.assembleRecallSections(buckets);
18
+ const injected = assembled.sections.join("\n\n---\n\n");
19
+
20
+ assert.equal(config.recallBudgetChars, config.maxMemoryTokens * 4);
21
+ assert.ok(estimateTokenCount(injected) <= config.maxMemoryTokens);
22
+ });
23
+ test("recall reserves one memory chunk that fits both character and token budgets", () => {
24
+ const config = parseConfig({
25
+ maxMemoryTokens: 50,
26
+ recallBudgetChars: 150,
27
+ recallPipeline: [
28
+ { id: "profile", enabled: true },
29
+ { id: "memories", enabled: true },
30
+ ],
31
+ });
32
+ const coordinator = new RecallSectionCoordinator({ getConfig: () => config });
33
+ const buckets = new Map<string, string[]>();
34
+
35
+ coordinator.appendRecallSection(buckets, "profile", "x".repeat(90));
36
+ coordinator.appendRecallSection(buckets, "memories", "日".repeat(51), {
37
+ atomic: true,
38
+ memoryId: "wide",
39
+ memoryPath: "memories/wide.md",
40
+ });
41
+ coordinator.appendRecallSection(buckets, "memories", "a".repeat(100), {
42
+ atomic: true,
43
+ memoryId: "latin",
44
+ memoryPath: "memories/latin.md",
45
+ });
46
+
47
+ const assembled = coordinator.assembleRecallSections(buckets);
48
+
49
+ assert.deepEqual(assembled.includedMemoryIds, ["latin"]);
50
+ assert.ok(estimateTokenCount(assembled.sections.join("\n\n---\n\n")) <= 50);
51
+ });
@@ -19,24 +19,17 @@
19
19
 
20
20
  import { randomUUID } from "node:crypto";
21
21
 
22
- import {
23
- normalizeRetrievedMemoryProvenance,
24
- type RetrievedMemoryProvenance,
25
- } from "./memory-provenance.js";
22
+ import { normalizeRetrievedMemoryProvenance, type RetrievedMemoryProvenance } from "./memory-provenance.js";
23
+ import { estimateTokenCount } from "./token-estimate.js";
26
24
  import type { RecallDisclosure, RecallTierExplain } from "./types.js";
27
25
  import { isRecallDisclosure } from "./types.js";
28
26
 
29
27
  /**
30
- * Estimate token cost of a payload at the rough ~4 chars/token English
31
- * heuristic. Non-negative integer; returns 0 for empty / null input.
32
- * Used by recall surfaces to attach `estimatedTokens` to X-ray results
33
- * (issue #677 PR 3/4). Identical to the private heuristic in
34
- * `chunking.ts`; kept self-contained here so X-ray callers don't pull
35
- * in chunking internals.
28
+ * Estimate token cost of a payload for X-ray disclosure accounting.
36
29
  */
37
30
  export function estimateRecallTokens(text: string | null | undefined): number {
38
31
  if (typeof text !== "string" || text.length === 0) return 0;
39
- return Math.ceil(text.length / 4);
32
+ return estimateTokenCount(text);
40
33
  }
41
34
 
42
35
  /**
@@ -6,7 +6,14 @@
6
6
  * natural topic boundaries, producing more coherent chunks.
7
7
  */
8
8
 
9
- import { chunkContent, type Chunk, type ChunkResult } from "./chunking.js";
9
+ import {
10
+ chunkContent,
11
+ joinSentences,
12
+ splitSentences,
13
+ type Chunk,
14
+ type ChunkResult,
15
+ } from "./chunking.js";
16
+ import { estimateTokenCount } from "./token-estimate.js";
10
17
 
11
18
  // ---------------------------------------------------------------------------
12
19
  // Configuration
@@ -177,59 +184,7 @@ export function findLocalMinima(
177
184
  }
178
185
 
179
186
  // ---------------------------------------------------------------------------
180
- // Sentence tokenizer
181
- // ---------------------------------------------------------------------------
182
-
183
- /**
184
- * Split text into sentences at punctuation boundaries.
185
- * Preserves punctuation with the preceding sentence.
186
- */
187
- function splitSentences(text: string): string[] {
188
- // Linear character scan instead of a regex. Every regex form of this split is
189
- // either polynomial (CodeQL js/polynomial-redos) or — once bounded/anchored to
190
- // satisfy CodeQL — mishandles long runs or interior punctuation (a global
191
- // match drops a skipped prefix; a sticky match stops at the first non-boundary
192
- // `.`, e.g. "v1.2.3" / "example.com", returning the whole document as one
193
- // sentence and bypassing chunking). The scan is O(n), drops nothing, and
194
- // handles interior punctuation correctly; normal prose splits identically to
195
- // the previous /[^.!?]*[.!?]+(?:\s+|$)/g form.
196
- const sentences: string[] = [];
197
- let start = 0;
198
- for (let i = 0; i < text.length; i++) {
199
- const ch = text[i];
200
- if (ch !== "." && ch !== "!" && ch !== "?") continue;
201
- let end = i;
202
- while (end + 1 < text.length) {
203
- const n = text[end + 1];
204
- if (n !== "." && n !== "!" && n !== "?") break;
205
- end++;
206
- }
207
- const after = text[end + 1];
208
- // A real boundary only if the terminator run ends the string or is followed
209
- // by whitespace. Interior punctuation (no following whitespace) is left in
210
- // place and the scan continues.
211
- if (after === undefined || /\s/.test(after)) {
212
- const sentence = text.slice(start, end + 1).trim();
213
- if (sentence.length > 0) sentences.push(sentence);
214
- start = end + 1;
215
- }
216
- i = end;
217
- }
218
- if (start < text.length) {
219
- const remaining = text.slice(start).trim();
220
- if (remaining.length > 0) sentences.push(remaining);
221
- }
222
- return sentences;
223
- }
224
187
 
225
- // ---------------------------------------------------------------------------
226
- // Token estimation
227
- // ---------------------------------------------------------------------------
228
-
229
- /** Rough token estimate: ~4 chars per token for English. */
230
- function estimateTokens(text: string): number {
231
- return Math.ceil(text.length / 4);
232
- }
233
188
 
234
189
  // ---------------------------------------------------------------------------
235
190
  // Core semantic chunking
@@ -316,7 +271,7 @@ function mergeShortSegments(
316
271
 
317
272
  for (let i = 0; i < segments.length; i++) {
318
273
  buffer = [...buffer, ...segments[i]];
319
- const tokenCount = estimateTokens(buffer.join(" "));
274
+ const tokenCount = estimateTokenCount(joinSentences(buffer));
320
275
 
321
276
  if (tokenCount >= minTokens || i === segments.length - 1) {
322
277
  merged.push(buffer);
@@ -344,7 +299,7 @@ function splitLongSegment(
344
299
  maxTokens: number,
345
300
  targetTokens: number,
346
301
  ): SemanticChunk[] {
347
- const text = segment.join(" ");
302
+ const text = joinSentences(segment);
348
303
  // Cap targetTokens to maxTokens so recursive splitting never produces
349
304
  // segments larger than the configured maximum (Finding 2, PR #420).
350
305
  const cappedTarget = Math.min(targetTokens, maxTokens);
@@ -396,7 +351,7 @@ export async function semanticChunkContent(
396
351
  const sentences = splitSentences(content);
397
352
 
398
353
  if (sentences.length <= 1) {
399
- const tokenCount = estimateTokens(content);
354
+ const tokenCount = estimateTokenCount(content);
400
355
  return {
401
356
  chunked: false,
402
357
  chunks: [
@@ -413,7 +368,7 @@ export async function semanticChunkContent(
413
368
  }
414
369
 
415
370
  // If total tokens is short enough, return as single chunk
416
- const totalTokens = estimateTokens(content);
371
+ const totalTokens = estimateTokenCount(content);
417
372
  if (totalTokens <= cfg.minTokens) {
418
373
  return {
419
374
  chunked: false,
@@ -511,8 +466,8 @@ export async function semanticChunkContent(
511
466
 
512
467
  for (let segIdx = 0; segIdx < segments.length; segIdx++) {
513
468
  const segment = segments[segIdx];
514
- const segText = segment.join(" ");
515
- const segTokens = estimateTokens(segText);
469
+ const segText = joinSentences(segment);
470
+ const segTokens = estimateTokenCount(segText);
516
471
 
517
472
  if (segTokens > cfg.maxTokens) {
518
473
  // Recursive split for oversized segment
@@ -14,7 +14,7 @@ interface SessionObserverCursor {
14
14
  }
15
15
 
16
16
  interface SessionObserverPersistedState {
17
- version: 1;
17
+ version: 2;
18
18
  sessions: Record<string, SessionObserverCursor>;
19
19
  }
20
20
 
@@ -134,7 +134,7 @@ export class SessionObserverState {
134
134
  try {
135
135
  const raw = await readFile(this.statePath, "utf-8");
136
136
  const parsed = JSON.parse(raw) as SessionObserverPersistedState;
137
- if (parsed?.version !== 1 || !parsed.sessions || typeof parsed.sessions !== "object") {
137
+ if (parsed?.version !== 2 || !parsed.sessions || typeof parsed.sessions !== "object") {
138
138
  return null;
139
139
  }
140
140
  return parsed;
@@ -246,7 +246,7 @@ export class SessionObserverState {
246
246
  for (const [key, value] of sessionsMap.entries()) {
247
247
  sessions[key] = value;
248
248
  }
249
- const payload: SessionObserverPersistedState = { version: 1, sessions };
249
+ const payload: SessionObserverPersistedState = { version: 2, sessions };
250
250
  await mkdir(path.dirname(this.statePath), { recursive: true });
251
251
  await writeFile(this.statePath, JSON.stringify(payload, null, 2), "utf-8");
252
252
  }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * The nine rows are realized honestly against the coordinator's REAL behavior —
11
11
  * no mocks. Config is built through the production `parseConfig` path so the
12
- * budget derivation (recallBudgetChars = maxMemoryTokens * 4), section
12
+ * budget derivation (recallBudgetChars = maxMemoryTokens), section
13
13
  * enablement filtering, per-section maxChars caps, and cap-after-filter
14
14
  * ordering are all exercised as they ship. Each row asserts one recall-budget
15
15
  * contract invariant:
@@ -28,6 +28,7 @@ import assert from "node:assert/strict";
28
28
 
29
29
  import { parseConfig } from "../../config.js";
30
30
  import { RecallSectionCoordinator } from "../../orchestration/recall-section-coordinator.js";
31
+ import { estimateTokenCount } from "../../token-estimate.js";
31
32
  import { createRecallSectionMetricRecorder, formatRecallSectionMetric } from "../../recall-qos.js";
32
33
  import { reorderRecallResultsWithMmr } from "../../recall-mmr.js";
33
34
  import { applyRuntimeRetrievalPolicy, expandQuery } from "../../retrieval.js";
@@ -297,10 +298,10 @@ const subject: LifecycleSubject<RecallBudgetState> = {
297
298
  return;
298
299
  }
299
300
  case "sparse-metadata-with-binding": {
300
- // Budget is derived from maxMemoryTokens (recallBudgetChars = tokens*4).
301
+ // Budget keeps Latin-script headroom and token-aware assembly caps wide scripts.
301
302
  assert.equal(config.recallBudgetChars, config.maxMemoryTokens * 4, "budget derives from maxMemoryTokens");
302
- assert.equal(budget, config.maxMemoryTokens * 4, "coordinator honors the token-derived budget");
303
- assert.ok(assembled.finalChars <= budget, "section sum stays within the token-derived budget");
303
+ assert.ok(budget <= config.maxMemoryTokens * 4, "coordinator honors the character budget");
304
+ assert.ok(assembled.finalChars <= budget, "section sum stays within the character budget");
304
305
  assert.ok(assembled.truncated, "oversized content forces truncation");
305
306
  return;
306
307
  }
@@ -372,7 +373,22 @@ const subject: LifecycleSubject<RecallBudgetState> = {
372
373
  // truncateRecallSectionToBudget respects a hard per-section budget too.
373
374
  const truncated = coordinator.truncateRecallSectionToBudget(body("m", 200), 50);
374
375
  assert.equal(truncated.length, 50, "budget-aware truncation lands exactly on the per-section limit");
375
- // A budget-skipped section reports as a debug-level "skip" metric — the
376
+ const tokenTrimmed = coordinator.truncateRecallSectionToBudget("x".repeat(200), 200, 20);
377
+ assert.match(tokenTrimmed, /\.\.\.\(memory context trimmed\)$/);
378
+ assert.ok(estimateTokenCount(tokenTrimmed) <= 20, "token-aware truncation keeps its marker inside the cap");
379
+ const markerBudget = estimateTokenCount("\n\n...(memory context trimmed)");
380
+ const markerOnlyBudget = coordinator.truncateRecallSectionToBudget("日本語".repeat(100), 200, markerBudget);
381
+ assert.ok(markerOnlyBudget.length > 0, "a marker-only budget keeps useful content");
382
+ assert.doesNotMatch(markerOnlyBudget, /\.\.\.\(memory context trimmed\)$/);
383
+ assert.ok(estimateTokenCount(markerOnlyBudget) <= markerBudget);
384
+ const wideScriptTrimmed = coordinator.truncateRecallSectionToBudget("日".repeat(100), 30, markerBudget);
385
+ assert.equal([...wideScriptTrimmed].length, 8, "wide-script fallback uses the available character room");
386
+ assert.ok(estimateTokenCount(wideScriptTrimmed) <= markerBudget);
387
+ assert.doesNotMatch(wideScriptTrimmed, /\.\.\.\(memory context trimmed\)$/);
388
+ const emojiTrimmed = coordinator.truncateRecallSectionToBudget("😀".repeat(30), 60, 29);
389
+ assert.ok(emojiTrimmed.length <= 60, "astral text stays within the UTF-16 character cap");
390
+ assert.ok(estimateTokenCount(emojiTrimmed) <= 29, "astral text stays within the token cap");
391
+ assert.match(emojiTrimmed, /\.\.\.\(memory context trimmed\)$/);
376
392
  // QoS accounting that flags a section dropped for the budget deadline.
377
393
  const skipMetric = formatRecallSectionMetric({
378
394
  section: "memories",