@remnic/core 9.3.751 → 9.3.752

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 (319) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +29 -29
  3. package/dist/access-cli.js +88 -88
  4. package/dist/access-http.d.ts +5 -5
  5. package/dist/access-http.js +35 -35
  6. package/dist/access-mcp.d.ts +5 -5
  7. package/dist/access-mcp.js +34 -34
  8. package/dist/access-operations-batch.js +31 -31
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +33 -33
  11. package/dist/access-recall-surface.d.ts +363 -0
  12. package/dist/access-recall-surface.js +128 -0
  13. package/dist/access-schema.js +7 -7
  14. package/dist/{access-service-DmAEOJdJ.d.ts → access-service-C2cOHQvg.d.ts} +7 -26
  15. package/dist/access-service.d.ts +5 -5
  16. package/dist/access-service.js +28 -28
  17. package/dist/access-surface-catalog.d.ts +5 -5
  18. package/dist/action-confidence.d.ts +1 -1
  19. package/dist/active-memory-bridge.d.ts +1 -1
  20. package/dist/active-recall.d.ts +1 -1
  21. package/dist/active-recall.js +1 -1
  22. package/dist/behavior-learner.d.ts +1 -1
  23. package/dist/behavior-signals.d.ts +1 -1
  24. package/dist/bootstrap.d.ts +4 -4
  25. package/dist/briefing.d.ts +1 -1
  26. package/dist/briefing.js +4 -4
  27. package/dist/buffer-surprise-report.d.ts +1 -1
  28. package/dist/buffer.d.ts +1 -1
  29. package/dist/buffer.js +2 -2
  30. package/dist/calibration.d.ts +1 -1
  31. package/dist/capabilities.d.ts +1 -1
  32. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-CZJSLEFG.js} +3 -3
  33. package/dist/{catalog-BpOcwHdE.d.ts → catalog-D_FvzTM0.d.ts} +1 -1
  34. package/dist/causal-behavior.d.ts +1 -1
  35. package/dist/causal-behavior.js +2 -2
  36. package/dist/causal-chain.js +2 -2
  37. package/dist/causal-consolidation.d.ts +1 -1
  38. package/dist/causal-consolidation.js +9 -9
  39. package/dist/causal-retrieval.js +2 -2
  40. package/dist/causal-trajectory-graph.d.ts +1 -1
  41. package/dist/causal-trajectory.js +1 -1
  42. package/dist/{chunk-2NLLXCJG.js → chunk-2BFLDH5F.js} +2 -2
  43. package/dist/{chunk-UU32EAIY.js → chunk-2DPCAW2T.js} +5 -5
  44. package/dist/{chunk-5PZCDUJ6.js → chunk-2U4E34Y3.js} +4 -4
  45. package/dist/{chunk-JRZV3767.js → chunk-33NX5BLR.js} +4 -4
  46. package/dist/{chunk-X7Y7WX73.js → chunk-437S3G37.js} +2 -2
  47. package/dist/{chunk-HAZVVOYT.js → chunk-5DXUE7GB.js} +2 -2
  48. package/dist/{chunk-4DLFJJOQ.js → chunk-5JAT4ZJD.js} +4 -4
  49. package/dist/{chunk-RS25QOKZ.js → chunk-64SE4X5G.js} +2 -2
  50. package/dist/{chunk-QRRBXD24.js → chunk-67JRXV6Z.js} +7 -7
  51. package/dist/{chunk-WA7FECSG.js → chunk-6ROCH5JV.js} +7 -7
  52. package/dist/{chunk-5QANTTY2.js → chunk-BIAMJUUJ.js} +4 -4
  53. package/dist/{chunk-IKHKHADN.js → chunk-C4LNKQ3M.js} +5 -5
  54. package/dist/{chunk-B35L45HZ.js → chunk-DC5OBZS3.js} +2 -2
  55. package/dist/{chunk-ORAK3LNZ.js → chunk-ER3CCWK2.js} +2 -2
  56. package/dist/{chunk-SD34EK4Z.js → chunk-FEELYTRT.js} +5 -5
  57. package/dist/{chunk-KQAFEZQX.js → chunk-FLVL4L3L.js} +2 -2
  58. package/dist/{chunk-WIWPSQYU.js → chunk-FXRFFYWD.js} +4 -4
  59. package/dist/{chunk-W3M34XT4.js → chunk-GECEETQ3.js} +4 -4
  60. package/dist/{chunk-WWSXVOGY.js → chunk-GFGYHLON.js} +2 -2
  61. package/dist/{chunk-BJMBJZ2Y.js → chunk-GWTFLF2P.js} +59 -59
  62. package/dist/chunk-GWTFLF2P.js.map +1 -0
  63. package/dist/{chunk-WOX2UW4K.js → chunk-IBPEVGP5.js} +2 -2
  64. package/dist/{chunk-JZ7P26GF.js → chunk-IQWFWSCP.js} +4 -4
  65. package/dist/{chunk-6SXNQFHB.js → chunk-J2JB6BHT.js} +8 -8
  66. package/dist/{chunk-K2JM4DZZ.js → chunk-JHZ3LEM7.js} +2 -2
  67. package/dist/{chunk-YGGXUNS4.js → chunk-JNKSFIAY.js} +45 -45
  68. package/dist/{chunk-CGZWGVQF.js → chunk-K5CWCJKJ.js} +6 -6
  69. package/dist/{chunk-TPLE6SNY.js → chunk-KFNG72W7.js} +7 -7
  70. package/dist/{chunk-4NWIGAIC.js → chunk-KT2VQLP7.js} +8 -8
  71. package/dist/{chunk-6FNZ3NMS.js → chunk-L3VSTJ2V.js} +2 -2
  72. package/dist/{chunk-ARV3AUOM.js → chunk-M5QKGHCR.js} +2 -2
  73. package/dist/{chunk-E4PFDD3N.js → chunk-MEPWWSTD.js} +4 -4
  74. package/dist/{chunk-V5RVMULT.js → chunk-MLDAD27I.js} +7 -7
  75. package/dist/{chunk-K2R3DEEH.js → chunk-MTN2G4DT.js} +2 -2
  76. package/dist/{chunk-DBSMLRTO.js → chunk-MW7UUXTA.js} +5 -5
  77. package/dist/{chunk-OSMBC3JU.js → chunk-OKOF7ITI.js} +6 -6
  78. package/dist/{chunk-DBRVU5PR.js → chunk-OVMLS7TN.js} +3 -3
  79. package/dist/{chunk-NNL4LJAD.js → chunk-QMMCMV2L.js} +5 -5
  80. package/dist/{chunk-RNWWAS5V.js → chunk-QRM3K3XT.js} +5 -5
  81. package/dist/{chunk-HM6UJPPO.js → chunk-R7TGDLV3.js} +4 -4
  82. package/dist/{chunk-EEKDRAWX.js → chunk-RBYV3WSB.js} +4 -4
  83. package/dist/{chunk-LOPZ2DWR.js → chunk-RRRZ3CLN.js} +4 -4
  84. package/dist/{chunk-7TWA7DKP.js → chunk-S5UYWK3O.js} +6 -6
  85. package/dist/{chunk-QOGXUNWL.js → chunk-SC5AEZLU.js} +9 -9
  86. package/dist/{chunk-SNEXVX3U.js → chunk-UFNQKZV7.js} +15 -15
  87. package/dist/{chunk-W3BFT5OJ.js → chunk-V4ZPLQLC.js} +2 -2
  88. package/dist/{chunk-SQNED75R.js → chunk-V5UV7BOA.js} +1 -1
  89. package/dist/{chunk-RWZB6MAY.js → chunk-VQL22SHO.js} +891 -1002
  90. package/dist/chunk-VQL22SHO.js.map +1 -0
  91. package/dist/{chunk-UETBEHBA.js → chunk-VX2RYM2K.js} +2232 -1942
  92. package/dist/chunk-VX2RYM2K.js.map +1 -0
  93. package/dist/{chunk-K5LLP4CY.js → chunk-XKAOT4ZB.js} +4 -4
  94. package/dist/{chunk-ZBVQ4ZM4.js → chunk-YGNQG4Q6.js} +8 -8
  95. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  96. package/dist/{chunk-HYI2GP3F.js → chunk-ZGGI3COG.js} +9 -9
  97. package/dist/{cli-lmf5RGyQ.d.ts → cli-DFUU9WYu.d.ts} +3 -3
  98. package/dist/cli.d.ts +6 -6
  99. package/dist/cli.js +60 -60
  100. package/dist/compounding/engine.d.ts +1 -1
  101. package/dist/compounding/engine.js +6 -6
  102. package/dist/compounding/preference-consolidator.d.ts +1 -1
  103. package/dist/compression-optimizer.d.ts +1 -1
  104. package/dist/config.d.ts +1 -1
  105. package/dist/config.js +1 -1
  106. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  107. package/dist/connectors/codex-materialize-runner.js +5 -5
  108. package/dist/connectors/codex-materialize.d.ts +1 -1
  109. package/dist/connectors/index.d.ts +1 -1
  110. package/dist/connectors/index.js +7 -7
  111. package/dist/consolidation-provenance-check.d.ts +1 -1
  112. package/dist/consolidation-undo.d.ts +1 -1
  113. package/dist/contradiction/index.d.ts +1 -1
  114. package/dist/contradiction/index.js +2 -2
  115. package/dist/{contradiction-scan-3FJYWS3G.js → contradiction-scan-KO4WWCS4.js} +3 -3
  116. package/dist/contradiction-scan-KO4WWCS4.js.map +1 -0
  117. package/dist/conversation-index/backend.d.ts +1 -1
  118. package/dist/conversation-index/chunker.d.ts +1 -1
  119. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  120. package/dist/conversation-index/indexer.d.ts +1 -1
  121. package/dist/conversation-index/search.d.ts +1 -1
  122. package/dist/dashboard-runtime.js +2 -2
  123. package/dist/day-summary.d.ts +1 -1
  124. package/dist/day-summary.js +2 -2
  125. package/dist/delinearize.d.ts +1 -1
  126. package/dist/direct-answer-wiring.d.ts +1 -1
  127. package/dist/direct-answer.d.ts +1 -1
  128. package/dist/embedding-fallback.d.ts +1 -1
  129. package/dist/embedding-fallback.js +2 -2
  130. package/dist/enrichment/index.d.ts +1 -1
  131. package/dist/entity-retrieval.d.ts +1 -1
  132. package/dist/entity-retrieval.js +5 -5
  133. package/dist/entity-schema.d.ts +1 -1
  134. package/dist/explicit-capture.d.ts +4 -4
  135. package/dist/explicit-capture.js +2 -2
  136. package/dist/extraction-faithfulness.d.ts +1 -1
  137. package/dist/extraction-judge-telemetry.d.ts +1 -1
  138. package/dist/extraction-judge-training.d.ts +1 -1
  139. package/dist/extraction-judge.d.ts +1 -1
  140. package/dist/extraction.d.ts +1 -1
  141. package/dist/extraction.js +4 -4
  142. package/dist/fallback-llm.d.ts +1 -1
  143. package/dist/graph-dashboard-diff.d.ts +1 -1
  144. package/dist/graph-dashboard-key.d.ts +1 -1
  145. package/dist/graph-dashboard-parser.d.ts +1 -1
  146. package/dist/graph-edge-reinforcement.d.ts +1 -1
  147. package/dist/graph-snapshot.d.ts +1 -1
  148. package/dist/graph.d.ts +1 -1
  149. package/dist/identity-continuity.d.ts +1 -1
  150. package/dist/importance.d.ts +1 -1
  151. package/dist/index.d.ts +9 -9
  152. package/dist/index.js +142 -142
  153. package/dist/intent.d.ts +1 -1
  154. package/dist/lcm/engine.d.ts +1 -1
  155. package/dist/lcm/engine.js +3 -3
  156. package/dist/lcm/index.d.ts +1 -1
  157. package/dist/lcm/index.js +8 -8
  158. package/dist/lcm/tools.d.ts +1 -1
  159. package/dist/lifecycle.d.ts +1 -1
  160. package/dist/live-connectors-runner.d.ts +1 -1
  161. package/dist/local-llm.d.ts +1 -1
  162. package/dist/local-llm.js +2 -2
  163. package/dist/local-model-endpoint.d.ts +1 -1
  164. package/dist/maintenance/memory-governance.d.ts +1 -1
  165. package/dist/maintenance/memory-governance.js +4 -4
  166. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
  167. package/dist/maintenance/rebuild-memory-projection.js +5 -5
  168. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  169. package/dist/memory-action-policy.d.ts +1 -1
  170. package/dist/memory-cache.d.ts +1 -1
  171. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  172. package/dist/memory-projection-store.d.ts +1 -1
  173. package/dist/memory-provenance.d.ts +1 -1
  174. package/dist/memory-worth-outcomes.d.ts +1 -1
  175. package/dist/models-json.d.ts +1 -1
  176. package/dist/namespaces/migrate.d.ts +2 -2
  177. package/dist/namespaces/migrate.js +10 -10
  178. package/dist/namespaces/principal.d.ts +1 -1
  179. package/dist/namespaces/search.d.ts +1 -1
  180. package/dist/namespaces/search.js +4 -4
  181. package/dist/namespaces/storage.d.ts +2 -2
  182. package/dist/namespaces/storage.js +6 -6
  183. package/dist/native-knowledge.d.ts +1 -1
  184. package/dist/operator-toolkit.d.ts +1 -1
  185. package/dist/operator-toolkit.js +17 -17
  186. package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
  187. package/dist/orchestration/compression-guideline-coordinator.js +2 -2
  188. package/dist/orchestration/maintenance.d.ts +2 -2
  189. package/dist/orchestration/maintenance.js +10 -10
  190. package/dist/{orchestrator-D7bb8ZGp.d.ts → orchestrator-BhxMxtQJ.d.ts} +27 -106
  191. package/dist/orchestrator.d.ts +4 -4
  192. package/dist/orchestrator.js +86 -86
  193. package/dist/patterns-cli.d.ts +1 -1
  194. package/dist/policy-runtime.d.ts +1 -1
  195. package/dist/provenance.d.ts +1 -1
  196. package/dist/qmd-recall-cache.d.ts +1 -1
  197. package/dist/qmd.d.ts +1 -1
  198. package/dist/recall-disclosure-escalation.d.ts +1 -1
  199. package/dist/recall-explain-renderer.d.ts +1 -1
  200. package/dist/recall-planner-llm.d.ts +1 -1
  201. package/dist/recall-state.d.ts +1 -1
  202. package/dist/recall-tag-filter.d.ts +1 -1
  203. package/dist/recall-xray-cli.d.ts +1 -1
  204. package/dist/recall-xray-renderer.d.ts +1 -1
  205. package/dist/recall-xray.d.ts +1 -1
  206. package/dist/resolve-auth-token.d.ts +1 -1
  207. package/dist/resume-bundles.js +1 -1
  208. package/dist/retrieval-agents.d.ts +1 -1
  209. package/dist/retrieval-tiers.d.ts +1 -1
  210. package/dist/routing/engine.d.ts +1 -1
  211. package/dist/routing/store.d.ts +1 -1
  212. package/dist/routing/store.js +2 -2
  213. package/dist/schemas.d.ts +22 -22
  214. package/dist/search/embed-helper.d.ts +1 -1
  215. package/dist/search/embed-helper.js +2 -2
  216. package/dist/search/factory.d.ts +1 -1
  217. package/dist/search/factory.js +3 -3
  218. package/dist/search/index.d.ts +1 -1
  219. package/dist/search/index.js +3 -3
  220. package/dist/search/lancedb-backend.d.ts +1 -1
  221. package/dist/search/meilisearch-backend.d.ts +1 -1
  222. package/dist/search/noop-backend.d.ts +1 -1
  223. package/dist/search/orama-backend.d.ts +1 -1
  224. package/dist/search/port.d.ts +1 -1
  225. package/dist/search/remote-backend.d.ts +1 -1
  226. package/dist/secure-store/index.js +2 -2
  227. package/dist/{semantic-consolidation-CTfbVwCA.d.ts → semantic-consolidation-BrsoFeMP.d.ts} +1 -1
  228. package/dist/semantic-consolidation.d.ts +2 -2
  229. package/dist/semantic-consolidation.js +7 -7
  230. package/dist/semantic-rule-promotion.js +4 -4
  231. package/dist/semantic-rule-verifier.d.ts +1 -1
  232. package/dist/semantic-rule-verifier.js +4 -4
  233. package/dist/session-observer-bands.d.ts +1 -1
  234. package/dist/session-observer-state.d.ts +1 -1
  235. package/dist/shared-context/manager.d.ts +1 -1
  236. package/dist/shared-context/manager.js +2 -2
  237. package/dist/signal.d.ts +1 -1
  238. package/dist/storage.d.ts +1 -1
  239. package/dist/storage.js +3 -3
  240. package/dist/summarizer.d.ts +1 -1
  241. package/dist/summarizer.js +3 -3
  242. package/dist/summary-snapshot.d.ts +1 -1
  243. package/dist/temporal-supersession.d.ts +1 -1
  244. package/dist/temporal-validity.d.ts +1 -1
  245. package/dist/threading.d.ts +1 -1
  246. package/dist/tier-migration.d.ts +1 -1
  247. package/dist/tier-routing.d.ts +1 -1
  248. package/dist/topics.d.ts +1 -1
  249. package/dist/transcript.d.ts +1 -1
  250. package/dist/transfer/backup.js +3 -3
  251. package/dist/transfer/capsule-export.js +3 -3
  252. package/dist/transfer/capsule-import.js +3 -3
  253. package/dist/transfer/types.d.ts +12 -12
  254. package/dist/trust-score-stage.d.ts +1 -1
  255. package/dist/trust-score.d.ts +1 -1
  256. package/dist/{types-D4NYDtXI.d.ts → types-BfI4uJeb.d.ts} +1 -1
  257. package/dist/types.d.ts +1 -1
  258. package/dist/utility-runtime.d.ts +1 -1
  259. package/dist/verified-recall.js +4 -4
  260. package/package.json +2 -2
  261. package/src/access-recall-surface.ts +1552 -0
  262. package/src/access-service.ts +29 -1219
  263. package/src/orchestration/recall-entry.ts +290 -0
  264. package/src/orchestration/session-context.ts +407 -0
  265. package/src/orchestrator.ts +125 -462
  266. package/dist/chunk-BJMBJZ2Y.js.map +0 -1
  267. package/dist/chunk-RWZB6MAY.js.map +0 -1
  268. package/dist/chunk-UETBEHBA.js.map +0 -1
  269. /package/dist/{capsule-crypto-7FJQINUR.js.map → access-recall-surface.js.map} +0 -0
  270. /package/dist/{contradiction-scan-3FJYWS3G.js.map → capsule-crypto-CZJSLEFG.js.map} +0 -0
  271. /package/dist/{chunk-2NLLXCJG.js.map → chunk-2BFLDH5F.js.map} +0 -0
  272. /package/dist/{chunk-UU32EAIY.js.map → chunk-2DPCAW2T.js.map} +0 -0
  273. /package/dist/{chunk-5PZCDUJ6.js.map → chunk-2U4E34Y3.js.map} +0 -0
  274. /package/dist/{chunk-JRZV3767.js.map → chunk-33NX5BLR.js.map} +0 -0
  275. /package/dist/{chunk-X7Y7WX73.js.map → chunk-437S3G37.js.map} +0 -0
  276. /package/dist/{chunk-HAZVVOYT.js.map → chunk-5DXUE7GB.js.map} +0 -0
  277. /package/dist/{chunk-4DLFJJOQ.js.map → chunk-5JAT4ZJD.js.map} +0 -0
  278. /package/dist/{chunk-RS25QOKZ.js.map → chunk-64SE4X5G.js.map} +0 -0
  279. /package/dist/{chunk-QRRBXD24.js.map → chunk-67JRXV6Z.js.map} +0 -0
  280. /package/dist/{chunk-WA7FECSG.js.map → chunk-6ROCH5JV.js.map} +0 -0
  281. /package/dist/{chunk-5QANTTY2.js.map → chunk-BIAMJUUJ.js.map} +0 -0
  282. /package/dist/{chunk-IKHKHADN.js.map → chunk-C4LNKQ3M.js.map} +0 -0
  283. /package/dist/{chunk-B35L45HZ.js.map → chunk-DC5OBZS3.js.map} +0 -0
  284. /package/dist/{chunk-ORAK3LNZ.js.map → chunk-ER3CCWK2.js.map} +0 -0
  285. /package/dist/{chunk-SD34EK4Z.js.map → chunk-FEELYTRT.js.map} +0 -0
  286. /package/dist/{chunk-KQAFEZQX.js.map → chunk-FLVL4L3L.js.map} +0 -0
  287. /package/dist/{chunk-WIWPSQYU.js.map → chunk-FXRFFYWD.js.map} +0 -0
  288. /package/dist/{chunk-W3M34XT4.js.map → chunk-GECEETQ3.js.map} +0 -0
  289. /package/dist/{chunk-WWSXVOGY.js.map → chunk-GFGYHLON.js.map} +0 -0
  290. /package/dist/{chunk-WOX2UW4K.js.map → chunk-IBPEVGP5.js.map} +0 -0
  291. /package/dist/{chunk-JZ7P26GF.js.map → chunk-IQWFWSCP.js.map} +0 -0
  292. /package/dist/{chunk-6SXNQFHB.js.map → chunk-J2JB6BHT.js.map} +0 -0
  293. /package/dist/{chunk-K2JM4DZZ.js.map → chunk-JHZ3LEM7.js.map} +0 -0
  294. /package/dist/{chunk-YGGXUNS4.js.map → chunk-JNKSFIAY.js.map} +0 -0
  295. /package/dist/{chunk-CGZWGVQF.js.map → chunk-K5CWCJKJ.js.map} +0 -0
  296. /package/dist/{chunk-TPLE6SNY.js.map → chunk-KFNG72W7.js.map} +0 -0
  297. /package/dist/{chunk-4NWIGAIC.js.map → chunk-KT2VQLP7.js.map} +0 -0
  298. /package/dist/{chunk-6FNZ3NMS.js.map → chunk-L3VSTJ2V.js.map} +0 -0
  299. /package/dist/{chunk-ARV3AUOM.js.map → chunk-M5QKGHCR.js.map} +0 -0
  300. /package/dist/{chunk-E4PFDD3N.js.map → chunk-MEPWWSTD.js.map} +0 -0
  301. /package/dist/{chunk-V5RVMULT.js.map → chunk-MLDAD27I.js.map} +0 -0
  302. /package/dist/{chunk-K2R3DEEH.js.map → chunk-MTN2G4DT.js.map} +0 -0
  303. /package/dist/{chunk-DBSMLRTO.js.map → chunk-MW7UUXTA.js.map} +0 -0
  304. /package/dist/{chunk-OSMBC3JU.js.map → chunk-OKOF7ITI.js.map} +0 -0
  305. /package/dist/{chunk-DBRVU5PR.js.map → chunk-OVMLS7TN.js.map} +0 -0
  306. /package/dist/{chunk-NNL4LJAD.js.map → chunk-QMMCMV2L.js.map} +0 -0
  307. /package/dist/{chunk-RNWWAS5V.js.map → chunk-QRM3K3XT.js.map} +0 -0
  308. /package/dist/{chunk-HM6UJPPO.js.map → chunk-R7TGDLV3.js.map} +0 -0
  309. /package/dist/{chunk-EEKDRAWX.js.map → chunk-RBYV3WSB.js.map} +0 -0
  310. /package/dist/{chunk-LOPZ2DWR.js.map → chunk-RRRZ3CLN.js.map} +0 -0
  311. /package/dist/{chunk-7TWA7DKP.js.map → chunk-S5UYWK3O.js.map} +0 -0
  312. /package/dist/{chunk-QOGXUNWL.js.map → chunk-SC5AEZLU.js.map} +0 -0
  313. /package/dist/{chunk-SNEXVX3U.js.map → chunk-UFNQKZV7.js.map} +0 -0
  314. /package/dist/{chunk-W3BFT5OJ.js.map → chunk-V4ZPLQLC.js.map} +0 -0
  315. /package/dist/{chunk-SQNED75R.js.map → chunk-V5UV7BOA.js.map} +0 -0
  316. /package/dist/{chunk-K5LLP4CY.js.map → chunk-XKAOT4ZB.js.map} +0 -0
  317. /package/dist/{chunk-ZBVQ4ZM4.js.map → chunk-YGNQG4Q6.js.map} +0 -0
  318. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  319. /package/dist/{chunk-HYI2GP3F.js.map → chunk-ZGGI3COG.js.map} +0 -0
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-GDB4J2H3.js";
9
9
  import {
10
10
  importCapsule
11
- } from "./chunk-ARV3AUOM.js";
11
+ } from "./chunk-M5QKGHCR.js";
12
12
  import {
13
13
  getTrustZoneStoreStatus,
14
14
  isTrustZoneName,
@@ -56,10 +56,10 @@ import {
56
56
  resolveScopeProfilePlan,
57
57
  resolveWritableNamespaceValue,
58
58
  sanitizeFragment
59
- } from "./chunk-W3M34XT4.js";
59
+ } from "./chunk-GECEETQ3.js";
60
60
  import {
61
61
  namespaceCollectionName
62
- } from "./chunk-HAZVVOYT.js";
62
+ } from "./chunk-5DXUE7GB.js";
63
63
  import {
64
64
  namespaceIdentityFromToken
65
65
  } from "./chunk-ZFXCQPNO.js";
@@ -70,7 +70,7 @@ import {
70
70
  listMemoryGovernanceRuns,
71
71
  readMemoryGovernanceRunArtifact,
72
72
  runMemoryGovernance
73
- } from "./chunk-K2JM4DZZ.js";
73
+ } from "./chunk-JHZ3LEM7.js";
74
74
  import {
75
75
  recordMemoryOutcome
76
76
  } from "./chunk-EIR5VLIH.js";
@@ -91,7 +91,7 @@ import {
91
91
  persistExplicitCapture,
92
92
  queueExplicitCaptureForReview,
93
93
  validateExplicitCaptureInput
94
- } from "./chunk-K5LLP4CY.js";
94
+ } from "./chunk-XKAOT4ZB.js";
95
95
  import {
96
96
  CrossNamespaceBudget
97
97
  } from "./chunk-ODPLEWB6.js";
@@ -101,17 +101,17 @@ import {
101
101
  import {
102
102
  filterTrajectoriesByLookbackDays,
103
103
  readCausalTrajectoryRecords
104
- } from "./chunk-SQNED75R.js";
104
+ } from "./chunk-V5UV7BOA.js";
105
105
  import {
106
106
  FileCalendarSource,
107
107
  buildBriefing,
108
108
  enqueuePassiveCorrectionNotification,
109
109
  parseBriefingFocus,
110
110
  parseBriefingWindow
111
- } from "./chunk-K2R3DEEH.js";
111
+ } from "./chunk-MTN2G4DT.js";
112
112
  import {
113
113
  parseEntityFile
114
- } from "./chunk-ZBVQ4ZM4.js";
114
+ } from "./chunk-YGNQG4Q6.js";
115
115
  import {
116
116
  stripAttributesSuffix
117
117
  } from "./chunk-SVOZFLIQ.js";
@@ -145,9 +145,32 @@ import {
145
145
  import {
146
146
  computeContentHash
147
147
  } from "./chunk-W5H6ZRPT.js";
148
+ import {
149
+ sanitizeMemoryContent
150
+ } from "./chunk-5GKPFL3S.js";
151
+ import {
152
+ expandTildePath
153
+ } from "./chunk-EYIEWJNI.js";
154
+ import {
155
+ log
156
+ } from "./chunk-NU3CSQ4H.js";
148
157
  import {
149
158
  exportCapsule
150
- } from "./chunk-KQAFEZQX.js";
159
+ } from "./chunk-FLVL4L3L.js";
160
+ import {
161
+ OFFLINE_SYNC_SNAPSHOT_FORMAT,
162
+ applyOfflineSyncChangeset,
163
+ applyOfflineSyncFileContentChunk,
164
+ buildOfflineSyncSnapshot,
165
+ buildOfflineSyncSnapshotForPaths,
166
+ buildOfflineSyncSnapshotFromBase,
167
+ compileOfflineSyncExcludeGlobs,
168
+ iterateOfflineSyncSnapshotFileRecords,
169
+ readOfflineSyncFileContentChunk
170
+ } from "./chunk-X64YLC24.js";
171
+ import {
172
+ SecureStoreLockedError
173
+ } from "./chunk-J2HSAU72.js";
151
174
  import {
152
175
  evaluateActionConfidence
153
176
  } from "./chunk-Z2YFLXHU.js";
@@ -173,29 +196,6 @@ import {
173
196
  resolveRecallEnhancementCapabilities,
174
197
  resolveSecurityCapabilities
175
198
  } from "./chunk-VSF7AAGE.js";
176
- import {
177
- sanitizeMemoryContent
178
- } from "./chunk-5GKPFL3S.js";
179
- import {
180
- expandTildePath
181
- } from "./chunk-EYIEWJNI.js";
182
- import {
183
- log
184
- } from "./chunk-NU3CSQ4H.js";
185
- import {
186
- OFFLINE_SYNC_SNAPSHOT_FORMAT,
187
- applyOfflineSyncChangeset,
188
- applyOfflineSyncFileContentChunk,
189
- buildOfflineSyncSnapshot,
190
- buildOfflineSyncSnapshotForPaths,
191
- buildOfflineSyncSnapshotFromBase,
192
- compileOfflineSyncExcludeGlobs,
193
- iterateOfflineSyncSnapshotFileRecords,
194
- readOfflineSyncFileContentChunk
195
- } from "./chunk-X64YLC24.js";
196
- import {
197
- SecureStoreLockedError
198
- } from "./chunk-J2HSAU72.js";
199
199
  import {
200
200
  isHandleToken
201
201
  } from "./chunk-JAKIDQB2.js";
@@ -207,6 +207,10 @@ import {
207
207
  hashAccessIdempotencyPayload
208
208
  } from "./chunk-VFUEZZBS.js";
209
209
 
210
+ // src/access-recall-surface.ts
211
+ import { createHash as createHash5 } from "crypto";
212
+ import * as nodePath2 from "path";
213
+
210
214
  // src/access-service.ts
211
215
  import { stat as stat2 } from "fs/promises";
212
216
  import * as nodeFs from "fs/promises";
@@ -4051,7 +4055,7 @@ function isErrnoCode(error, code) {
4051
4055
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
4052
4056
  }
4053
4057
  function sleep(ms) {
4054
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4058
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
4055
4059
  }
4056
4060
  async function acquireClusterWriteLock(memoryDir, clusterHash) {
4057
4061
  const lockRoot = path6.join(path6.resolve(memoryDir), "state", "procedure-miner-locks");
@@ -4090,8 +4094,8 @@ async function withClusterWriteLock(memoryDir, clusterHash, fn) {
4090
4094
  const previous = inProcessClusterWriteLocks.get(inProcessKey) ?? Promise.resolve();
4091
4095
  let releaseInProcess;
4092
4096
  const current = previous.catch(() => void 0).then(
4093
- () => new Promise((resolve2) => {
4094
- releaseInProcess = resolve2;
4097
+ () => new Promise((resolve3) => {
4098
+ releaseInProcess = resolve3;
4095
4099
  })
4096
4100
  );
4097
4101
  inProcessClusterWriteLocks.set(inProcessKey, current);
@@ -4747,44 +4751,29 @@ async function promoteMemory(options) {
4747
4751
 
4748
4752
  // src/access-service.ts
4749
4753
  import * as nodePath from "path";
4750
- var EngramAccessInputError = class extends Error {
4751
- };
4752
- function qmdCollectionPathParts(resultPath) {
4753
- if (!resultPath || nodePath.isAbsolute(resultPath)) return null;
4754
- const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
4755
- const slashIndex = normalized.indexOf("/");
4756
- if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
4757
- const collection = normalized.slice(0, slashIndex);
4758
- if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
4759
- return {
4760
- collection,
4761
- relativePath: normalized.slice(slashIndex + 1)
4762
- };
4763
- }
4764
- function qmdResultPathCandidates(storageDir, resultPath) {
4765
- const candidates = /* @__PURE__ */ new Set();
4766
- const storageRoot = nodePath.resolve(storageDir);
4767
- const addCandidate = (candidate) => {
4768
- const resolved = nodePath.resolve(candidate);
4769
- if (isPathInsideStorageRoot(storageRoot, resolved)) {
4770
- candidates.add(resolved);
4771
- }
4772
- };
4773
- const addRelativeCandidates = (relativePath) => {
4774
- const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
4775
- if (!normalized) return;
4776
- addCandidate(nodePath.join(storageRoot, normalized));
4777
- if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
4778
- addCandidate(nodePath.join(storageRoot, "facts", normalized));
4754
+
4755
+ // src/orchestration/self-deps.ts
4756
+ function selfDeps(self) {
4757
+ const source = self;
4758
+ return new Proxy(/* @__PURE__ */ Object.create(null), {
4759
+ get(_target, prop) {
4760
+ const value = source[prop];
4761
+ return typeof value === "function" ? value.bind(self) : value;
4762
+ },
4763
+ set(_target, prop, value) {
4764
+ if (!(prop in source)) return false;
4765
+ source[prop] = value;
4766
+ return true;
4767
+ },
4768
+ has(_target, prop) {
4769
+ return prop in source;
4779
4770
  }
4780
- };
4781
- if (nodePath.isAbsolute(resultPath)) {
4782
- addCandidate(resultPath);
4783
- } else {
4784
- addRelativeCandidates(resultPath);
4785
- }
4786
- return [...candidates];
4771
+ });
4787
4772
  }
4773
+
4774
+ // src/access-service.ts
4775
+ var EngramAccessInputError = class extends Error {
4776
+ };
4788
4777
  var cachedPackageVersion = null;
4789
4778
  async function getPackageVersion() {
4790
4779
  if (cachedPackageVersion !== null) return cachedPackageVersion;
@@ -4965,6 +4954,16 @@ var EngramAccessService = class {
4965
4954
  budgetLocks = /* @__PURE__ */ new Map();
4966
4955
  budget;
4967
4956
  auditAdapter;
4957
+ /** AccessRecallSurface (access-service decomposition). Lazy; selfDeps live wiring. */
4958
+ _accessRecallSurface;
4959
+ get accessRecallSurface() {
4960
+ if (!this._accessRecallSurface) {
4961
+ this._accessRecallSurface = new AccessRecallSurface(
4962
+ selfDeps(this)
4963
+ );
4964
+ }
4965
+ return this._accessRecallSurface;
4966
+ }
4968
4967
  get briefingEnabled() {
4969
4968
  return this.orchestrator.config.briefing?.enabled === true;
4970
4969
  }
@@ -5453,313 +5452,64 @@ var EngramAccessService = class {
5453
5452
  } : void 0;
5454
5453
  }
5455
5454
  async buildRecallResponseFromXraySnapshot(options) {
5456
- const memoryIds = options.snapshot.results.map((result) => result.memoryId);
5457
- const resultPaths = options.snapshot.results.map((result) => result.path);
5458
- const namespace = options.snapshot.namespace ? this.resolveNamespace(options.snapshot.namespace) : this.orchestrator.config.defaultNamespace;
5459
- const sourcesUsed = Array.from(
5460
- new Set(options.snapshot.results.map((result) => result.servedBy))
5461
- );
5462
- const snapshotForSerialization = {
5463
- sessionKey: options.sessionKey ?? "",
5464
- recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
5465
- queryHash: createHash4("sha256").update(options.query).digest("hex"),
5466
- queryLen: options.query.length,
5467
- memoryIds,
5468
- namespace,
5469
- recallNamespaces: [namespace],
5470
- traceId: options.snapshot.traceId,
5471
- plannerMode: options.normalizedMode,
5472
- requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
5473
- sourcesUsed,
5474
- budgetsApplied: {
5475
- appliedTopK: memoryIds.length,
5476
- recallBudgetChars: options.snapshot.budget.chars,
5477
- maxMemoryTokens: this.orchestrator.config.maxMemoryTokens,
5478
- finalContextChars: options.snapshot.budget.used
5479
- },
5480
- latencyMs: Date.now() - options.startedAt,
5481
- resultPaths
5482
- };
5483
- const results = await this.serializeRecallResults(
5484
- snapshotForSerialization,
5485
- options.disclosure,
5486
- {
5487
- query: options.query,
5488
- ...options.sessionKey ? { sessionKey: options.sessionKey } : {},
5489
- ...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
5490
- ...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
5491
- ...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
5492
- }
5455
+ return this.accessRecallSurface.buildRecallResponseFromXraySnapshot(
5456
+ options
5493
5457
  );
5494
- const context = results.map((result) => {
5495
- const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
5496
- return content || result.preview;
5497
- }).filter((text) => text.length > 0).join("\n\n");
5498
- return {
5499
- query: options.query,
5500
- ...options.sessionKey ? { sessionKey: options.sessionKey } : {},
5501
- namespace,
5502
- context,
5503
- count: memoryIds.length,
5504
- memoryIds,
5505
- results,
5506
- recordedAt: snapshotForSerialization.recordedAt,
5507
- traceId: options.snapshot.traceId,
5508
- plannerMode: options.normalizedMode,
5509
- fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
5510
- sourcesUsed,
5511
- disclosure: options.disclosure,
5512
- budgetsApplied: snapshotForSerialization.budgetsApplied,
5513
- latencyMs: snapshotForSerialization.latencyMs
5514
- };
5515
5458
  }
5516
5459
  async serializeRecallResults(snapshot, disclosure, rawContext = null) {
5517
- if (!snapshot) return [];
5518
- const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
5519
- const storage = await this.orchestrator.getStorage(namespace);
5520
- const storageDir = storage.dir;
5521
- const recallNamespaces = Array.from(
5522
- new Set(
5523
- [
5524
- namespace,
5525
- ...Array.isArray(snapshot.recallNamespaces) ? snapshot.recallNamespaces.map((ns) => this.resolveNamespace(ns)) : [],
5526
- this.orchestrator.config.defaultNamespace,
5527
- this.orchestrator.config.sharedNamespace,
5528
- ...(this.orchestrator.config.namespacePolicies ?? []).map((p) => p.name)
5529
- ].filter((ns) => typeof ns === "string" && ns.length > 0)
5530
- )
5460
+ return this.accessRecallSurface.serializeRecallResults(
5461
+ snapshot,
5462
+ disclosure,
5463
+ rawContext
5531
5464
  );
5532
- const results = [];
5533
- const seen = /* @__PURE__ */ new Set();
5534
- const collectionNamespaceFromPrefix = (collectionPrefix) => {
5535
- const baseCollection = this.orchestrator.config.qmdCollection;
5536
- if (collectionPrefix === baseCollection) return this.orchestrator.config.defaultNamespace;
5537
- const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`) ? collectionPrefix.slice(baseCollection.length + 2) : "";
5538
- if (!namespaceSuffix) return null;
5539
- const decoded = namespaceIdentityFromToken(namespaceSuffix);
5540
- if (decoded !== null) return decoded || this.orchestrator.config.defaultNamespace;
5541
- if (namespaceSuffix.startsWith("ns--")) {
5542
- const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
5543
- return legacyNamespace || null;
5465
+ }
5466
+ async storageForAbsoluteRecallPath(memoryPath, primaryNamespace, recallNamespaces = []) {
5467
+ const resolvedPath = nodePath.resolve(memoryPath);
5468
+ const memoryRoot = nodePath.resolve(this.orchestrator.config.memoryDir);
5469
+ const namespacesRoot = nodePath.join(memoryRoot, "namespaces");
5470
+ const configuredNamespaces = /* @__PURE__ */ new Set();
5471
+ configuredNamespaces.add(primaryNamespace);
5472
+ for (const namespace of recallNamespaces) {
5473
+ configuredNamespaces.add(namespace);
5474
+ }
5475
+ configuredNamespaces.add(this.orchestrator.config.defaultNamespace);
5476
+ configuredNamespaces.add(this.orchestrator.config.sharedNamespace);
5477
+ if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
5478
+ const relativeToNamespaces = nodePath.relative(namespacesRoot, resolvedPath);
5479
+ const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
5480
+ if (namespaceSegment) {
5481
+ configuredNamespaces.add(
5482
+ namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
5483
+ );
5544
5484
  }
5545
- return null;
5546
- };
5547
- const readResultPath = async (memoryPath) => {
5548
- const parts = qmdCollectionPathParts(memoryPath);
5549
- const coldCollection = this.orchestrator.config.qmdColdCollection ?? "openclaw-engram-cold";
5550
- if (parts && parts.collection === coldCollection) {
5551
- const storages = [];
5552
- const seenStorageDirs = /* @__PURE__ */ new Set();
5553
- const addStorage = (candidateStorage) => {
5554
- const candidateDir = nodePath.resolve(candidateStorage.dir);
5555
- if (seenStorageDirs.has(candidateDir)) return;
5556
- seenStorageDirs.add(candidateDir);
5557
- storages.push({ storage: candidateStorage, dir: candidateDir });
5558
- };
5559
- addStorage(storage);
5560
- for (const recallNamespace of recallNamespaces) {
5561
- try {
5562
- addStorage(await this.orchestrator.getStorage(recallNamespace));
5563
- } catch {
5564
- continue;
5565
- }
5566
- }
5567
- for (const candidateStorage of storages) {
5568
- try {
5569
- const coldRoot = nodePath.join(candidateStorage.dir, "cold");
5570
- for (const candidatePath of qmdResultPathCandidates(
5571
- coldRoot,
5572
- parts.relativePath
5573
- )) {
5574
- const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
5575
- if (memory) return { memory, baseDir: candidateStorage.dir };
5576
- }
5577
- } catch (err) {
5578
- if (err instanceof SecureStoreLockedError) throw err;
5579
- continue;
5580
- }
5581
- }
5582
- return null;
5485
+ }
5486
+ for (const policy of this.orchestrator.config.namespacePolicies ?? []) {
5487
+ configuredNamespaces.add(policy.name);
5488
+ }
5489
+ const matches = [];
5490
+ for (const ns of configuredNamespaces) {
5491
+ if (!ns) continue;
5492
+ let candidateStorage;
5493
+ try {
5494
+ candidateStorage = await this.orchestrator.getStorage(ns);
5495
+ } catch {
5496
+ continue;
5583
5497
  }
5584
- const collectionNamespace = parts ? collectionNamespaceFromPrefix(parts.collection) : null;
5585
- if (parts && collectionNamespace) {
5586
- try {
5587
- const collectionStorage = await this.orchestrator.getStorage(collectionNamespace);
5588
- for (const candidate of qmdResultPathCandidates(
5589
- collectionStorage.dir,
5590
- parts.relativePath
5591
- )) {
5592
- const memory = await collectionStorage.readMemoryByPath(candidate);
5593
- if (memory) return { memory, baseDir: collectionStorage.dir };
5594
- }
5595
- return null;
5596
- } catch (err) {
5597
- if (err instanceof SecureStoreLockedError) throw err;
5598
- return null;
5599
- }
5600
- }
5601
- if (nodePath.isAbsolute(memoryPath)) {
5602
- const ownerStorage = await this.storageForAbsoluteRecallPath(
5603
- memoryPath,
5604
- namespace,
5605
- recallNamespaces
5606
- );
5607
- if (!ownerStorage) return null;
5608
- for (const candidate of qmdResultPathCandidates(
5609
- ownerStorage.dir,
5610
- memoryPath
5611
- )) {
5612
- const memory = await ownerStorage.storage.readMemoryByPath(candidate);
5613
- if (memory) return { memory, baseDir: ownerStorage.dir };
5614
- }
5615
- return null;
5616
- }
5617
- for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
5618
- const memory = await storage.readMemoryByPath(candidate);
5619
- if (memory) return { memory, baseDir: storageDir };
5620
- }
5621
- return null;
5622
- };
5623
- const rawExcerptsResult = rawContext?.rawExcerptsSuppressed === true ? (
5624
- // Implicit raw recall with NO readable LCM namespace (#1505 thread
5625
- // NBHWz): emit empty excerpts rather than falling back to the
5626
- // write/overlay `namespace` the read gate excludes.
5627
- []
5628
- ) : await this.fetchRawExcerpts(
5629
- disclosure,
5630
- rawContext ? {
5631
- query: rawContext.query,
5632
- ...rawContext.sessionKey ? { sessionKey: rawContext.sessionKey } : {},
5633
- namespace: rawContext.rawExcerptNamespace ?? namespace,
5634
- ...rawContext.rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawContext.rawExcerptSessionIds } : {}
5635
- } : null
5636
- );
5637
- const rawExcerpts = rawExcerptsResult ?? void 0;
5638
- for (const memoryPath of snapshot.resultPaths ?? []) {
5639
- if (!memoryPath || seen.has(memoryPath)) continue;
5640
- const resolved = await readResultPath(memoryPath);
5641
- if (!resolved) continue;
5642
- const { memory, baseDir } = resolved;
5643
- seen.add(memoryPath);
5644
- results.push(
5645
- this.serializeMemorySummary(
5646
- memory,
5647
- baseDir,
5648
- disclosure,
5649
- // Attach the (possibly empty) raw excerpts to the first raw
5650
- // result; subsequent results do not duplicate the array.
5651
- results.length === 0 ? rawExcerpts : void 0
5652
- )
5653
- );
5654
- }
5655
- if (results.length > 0) return results;
5656
- for (const memoryId of snapshot.memoryIds) {
5657
- const memory = await storage.getMemoryById(memoryId);
5658
- if (!memory || seen.has(memory.path)) continue;
5659
- seen.add(memory.path);
5660
- results.push(
5661
- this.serializeMemorySummary(
5662
- memory,
5663
- storageDir,
5664
- disclosure,
5665
- results.length === 0 ? rawExcerpts : void 0
5666
- )
5667
- );
5668
- }
5669
- return results;
5670
- }
5671
- async storageForAbsoluteRecallPath(memoryPath, primaryNamespace, recallNamespaces = []) {
5672
- const resolvedPath = nodePath.resolve(memoryPath);
5673
- const memoryRoot = nodePath.resolve(this.orchestrator.config.memoryDir);
5674
- const namespacesRoot = nodePath.join(memoryRoot, "namespaces");
5675
- const configuredNamespaces = /* @__PURE__ */ new Set();
5676
- configuredNamespaces.add(primaryNamespace);
5677
- for (const namespace of recallNamespaces) {
5678
- configuredNamespaces.add(namespace);
5679
- }
5680
- configuredNamespaces.add(this.orchestrator.config.defaultNamespace);
5681
- configuredNamespaces.add(this.orchestrator.config.sharedNamespace);
5682
- if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
5683
- const relativeToNamespaces = nodePath.relative(namespacesRoot, resolvedPath);
5684
- const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
5685
- if (namespaceSegment) {
5686
- configuredNamespaces.add(
5687
- namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
5688
- );
5689
- }
5690
- }
5691
- for (const policy of this.orchestrator.config.namespacePolicies ?? []) {
5692
- configuredNamespaces.add(policy.name);
5693
- }
5694
- const matches = [];
5695
- for (const ns of configuredNamespaces) {
5696
- if (!ns) continue;
5697
- let candidateStorage;
5698
- try {
5699
- candidateStorage = await this.orchestrator.getStorage(ns);
5700
- } catch {
5701
- continue;
5702
- }
5703
- const candidateRoot = nodePath.resolve(candidateStorage.dir);
5704
- if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) continue;
5705
- if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
5706
- continue;
5498
+ const candidateRoot = nodePath.resolve(candidateStorage.dir);
5499
+ if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) continue;
5500
+ if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
5501
+ continue;
5707
5502
  }
5708
5503
  matches.push({ storage: candidateStorage, dir: candidateRoot });
5709
5504
  }
5710
5505
  matches.sort((a, b) => b.dir.length - a.dir.length);
5711
5506
  return matches[0] ?? null;
5712
5507
  }
5713
- /**
5714
- * Fetch raw transcript excerpts from the LCM archive for `disclosure ===
5715
- * "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
5716
- * depths, an empty array when LCM is disabled / not initialized / has no
5717
- * matches, and an array of LCM-side excerpts otherwise. Errors are
5718
- * swallowed and treated as "no excerpts" so a failing LCM never breaks
5719
- * the recall response.
5720
- *
5721
- * Namespace handling: LCM archival prefixes non-default-namespace
5722
- * sessions with `${namespace}:${sessionKey}` (see `observe()` around
5723
- * line 2498), so the lookup must mirror that prefix or raw recalls in
5724
- * non-default namespaces miss their own excerpts.
5725
- */
5726
5508
  async fetchRawExcerpts(disclosure, context) {
5727
- if (disclosure !== "raw") return null;
5728
- if (!context || !context.query) return [];
5729
- if (!context.sessionKey) return [];
5730
- const lcm = this.orchestrator.lcmEngine;
5731
- if (!lcm || !lcm.enabled) return [];
5732
- try {
5733
- const legacyKey = context.namespace && context.namespace !== this.orchestrator.config.defaultNamespace ? `${context.namespace}:${context.sessionKey}` : context.sessionKey;
5734
- const lcmSessionIds = context.lcmSessionIds !== void 0 ? context.lcmSessionIds : [legacyKey];
5735
- const limit = 5;
5736
- const seenRows = /* @__PURE__ */ new Set();
5737
- const excerpts = [];
5738
- const settledRows = await Promise.allSettled(
5739
- lcmSessionIds.map(
5740
- async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
5741
- )
5742
- );
5743
- for (const result of settledRows) {
5744
- if (excerpts.length >= limit) break;
5745
- if (result.status !== "fulfilled") continue;
5746
- for (const r of result.value) {
5747
- const dedupeKey = `${r.session_id} ${r.turn_index}`;
5748
- if (seenRows.has(dedupeKey)) continue;
5749
- seenRows.add(dedupeKey);
5750
- excerpts.push({
5751
- turnIndex: r.turn_index,
5752
- role: r.role,
5753
- content: r.content,
5754
- sessionId: r.session_id
5755
- });
5756
- if (excerpts.length >= limit) break;
5757
- }
5758
- }
5759
- return excerpts;
5760
- } catch {
5761
- return [];
5762
- }
5509
+ return this.accessRecallSurface.fetchRawExcerpts(
5510
+ disclosure,
5511
+ context
5512
+ );
5763
5513
  }
5764
5514
  async handleIdempotentWrite(options) {
5765
5515
  if (options.skip === true) {
@@ -5845,8 +5595,8 @@ var EngramAccessService = class {
5845
5595
  async withIdempotencyLock(key, fn) {
5846
5596
  const previous = this.idempotencyLocks.get(key) ?? Promise.resolve();
5847
5597
  let release;
5848
- const current = new Promise((resolve2) => {
5849
- release = resolve2;
5598
+ const current = new Promise((resolve3) => {
5599
+ release = resolve3;
5850
5600
  });
5851
5601
  const queued = previous.then(() => current, () => current);
5852
5602
  this.idempotencyLocks.set(key, queued);
@@ -5865,8 +5615,8 @@ var EngramAccessService = class {
5865
5615
  const key = principal || "__anonymous__";
5866
5616
  const previous = this.budgetLocks.get(key) ?? Promise.resolve();
5867
5617
  let release;
5868
- const current = new Promise((resolve2) => {
5869
- release = resolve2;
5618
+ const current = new Promise((resolve3) => {
5619
+ release = resolve3;
5870
5620
  });
5871
5621
  const queued = previous.then(() => current, () => current);
5872
5622
  this.budgetLocks.set(key, queued);
@@ -6056,14 +5806,14 @@ var EngramAccessService = class {
6056
5806
  const timer = setTimeout(() => controller.abort(), 2e3);
6057
5807
  timer.unref?.();
6058
5808
  try {
6059
- return await new Promise((resolve2) => {
5809
+ return await new Promise((resolve3) => {
6060
5810
  const onAbort = () => {
6061
5811
  controller.signal.removeEventListener("abort", onAbort);
6062
- resolve2(false);
5812
+ resolve3(false);
6063
5813
  };
6064
5814
  controller.signal.addEventListener("abort", onAbort, { once: true });
6065
5815
  const probe = typeof qmd.checkAvailability === "function" ? qmd.checkAvailability({ signal: controller.signal }) : qmd.probe();
6066
- probe.then(resolve2, () => resolve2(false)).finally(() => {
5816
+ probe.then(resolve3, () => resolve3(false)).finally(() => {
6067
5817
  controller.signal.removeEventListener("abort", onAbort);
6068
5818
  });
6069
5819
  });
@@ -6292,588 +6042,90 @@ var EngramAccessService = class {
6292
6042
  });
6293
6043
  }
6294
6044
  async executeRecall(request) {
6295
- const query = request.query;
6296
- const callerProvidedDisclosure = request.disclosure !== void 0 && request.disclosure !== null;
6297
- const requestedDisclosure = (() => {
6298
- if (!callerProvidedDisclosure) {
6299
- return DEFAULT_RECALL_DISCLOSURE;
6300
- }
6301
- if (!isRecallDisclosure(request.disclosure)) {
6302
- throw new EngramAccessInputError(
6303
- `disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
6304
- );
6045
+ return this.accessRecallSurface.executeRecall(
6046
+ request
6047
+ );
6048
+ }
6049
+ async recallExplain(request = {}) {
6050
+ const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
6051
+ const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
6052
+ const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
6053
+ if (requestedNamespace) {
6054
+ if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
6055
+ return { found: false };
6305
6056
  }
6306
- return request.disclosure;
6307
- })();
6308
- if (request.codingContext !== void 0 && request.sessionKey) {
6309
- this.setCodingContext({
6310
- sessionKey: request.sessionKey,
6311
- codingContext: request.codingContext
6312
- });
6057
+ } else if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal) {
6058
+ return { found: false };
6313
6059
  }
6314
- if (request.codingContext === void 0 && request.sessionKey) {
6315
- await this.maybeAttachCodingContext(request.sessionKey, {
6316
- cwd: request.cwd,
6317
- projectTag: request.projectTag
6318
- });
6060
+ const snapshot = request.sessionKey ? (() => {
6061
+ const candidate = this.orchestrator.lastRecall.get(request.sessionKey);
6062
+ if (!candidate) return null;
6063
+ if (!requestedNamespace) return candidate;
6064
+ return candidate.namespace === requestedNamespace ? candidate : null;
6065
+ })() : (() => {
6066
+ const candidate = this.orchestrator.lastRecall.getMostRecent();
6067
+ if (!candidate) return null;
6068
+ if (!requestedNamespace) return candidate;
6069
+ return candidate.namespace === requestedNamespace ? candidate : null;
6070
+ })();
6071
+ const readableSnapshot = (() => {
6072
+ if (!snapshot || !resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return snapshot;
6073
+ const snapshotNamespace = snapshot.namespace ?? this.orchestrator.config.defaultNamespace;
6074
+ return canReadNamespace(principal, snapshotNamespace, this.orchestrator.config) ? snapshot : null;
6075
+ })();
6076
+ const namespace = (() => {
6077
+ if (requestedNamespace) return requestedNamespace;
6078
+ if (readableSnapshot?.namespace) return readableSnapshot.namespace;
6079
+ const fallbackNamespace = this.orchestrator.config.defaultNamespace;
6080
+ if (!resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return fallbackNamespace;
6081
+ return canReadNamespace(principal, fallbackNamespace, this.orchestrator.config) ? fallbackNamespace : null;
6082
+ })();
6083
+ if (!namespace) return { found: false };
6084
+ const [intent, graph] = await Promise.all([
6085
+ this.orchestrator.getLastIntentSnapshot(namespace),
6086
+ this.orchestrator.getLastGraphRecallSnapshot(namespace)
6087
+ ]);
6088
+ if (!readableSnapshot && !intent && !graph) return { found: false };
6089
+ return { found: true, snapshot: readableSnapshot ?? void 0, intent, graph };
6090
+ }
6091
+ async recallTierExplain(sessionKey, namespace, authenticatedPrincipal) {
6092
+ const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
6093
+ const requestedNamespace = namespace?.trim() ? this.resolveNamespace(namespace) : void 0;
6094
+ const principal = authenticatedPrincipal?.trim() || resolvePrincipal(sessionKey, this.orchestrator.config);
6095
+ if (requestedNamespace) {
6096
+ if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
6097
+ return toRecallExplainJson(null);
6098
+ }
6099
+ } else if (namespacesEnabled && !authenticatedPrincipal?.trim() && !sessionKey?.trim()) {
6100
+ return toRecallExplainJson(null);
6319
6101
  }
6320
- const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
6321
- const namespaceOverride = this.resolveRecallNamespace(
6322
- request.namespace,
6323
- request.sessionKey,
6324
- authenticatedPrincipal
6102
+ const candidate = sessionKey ? this.orchestrator.lastRecall.get(sessionKey) : this.orchestrator.lastRecall.getMostRecent();
6103
+ const snapshot = (() => {
6104
+ if (!candidate) return null;
6105
+ if (requestedNamespace) {
6106
+ return candidate.namespace === requestedNamespace ? candidate : null;
6107
+ }
6108
+ if (!namespacesEnabled) return candidate;
6109
+ const snapshotNs = candidate.namespace ?? this.orchestrator.config.defaultNamespace;
6110
+ return canReadNamespace(principal, snapshotNs, this.orchestrator.config) ? candidate : null;
6111
+ })();
6112
+ return toRecallExplainJson(snapshot);
6113
+ }
6114
+ async recallXray(request) {
6115
+ return this.accessRecallSurface.recallXray(
6116
+ request
6325
6117
  );
6326
- const namespace = namespaceOverride ?? this.orchestrator.config.defaultNamespace;
6327
- const mode = this.normalizeRecallMode(request.mode);
6328
- const maybePrincipal = this.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
6329
- if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !maybePrincipal) {
6330
- throw new EngramAccessInputError(
6331
- "authentication required: namespaces are enabled and no principal was supplied"
6332
- );
6333
- }
6334
- const principal = maybePrincipal ?? "default";
6335
- const principalNamespace = defaultNamespaceForPrincipal(principal, this.orchestrator.config);
6336
- const profileCodingContext = request.sessionKey && typeof this.orchestrator.getCodingContextForSession === "function" ? this.orchestrator.getCodingContextForSession(request.sessionKey) : null;
6337
- const profileCodingOverlay = !namespaceOverride && profileCodingContext && resolveNamespaceCapabilities(this.orchestrator.config).namespaces && this.orchestrator.config.codingMode?.projectScope ? resolveCodingNamespaceOverlay(
6338
- profileCodingContext,
6339
- this.orchestrator.config.codingMode,
6340
- this.orchestrator.config.defaultNamespace
6341
- ) : null;
6342
- const profilePlan = namespaceOverride ? null : resolveScopeProfilePlan({
6343
- config: this.orchestrator.config,
6344
- principal,
6345
- codingContext: profileCodingContext,
6346
- codingOverlay: profileCodingOverlay
6347
- });
6348
- const modeSkipsBudget = mode === "no_recall";
6349
- const legacyRecallNamespaces = Array.isArray(this.orchestrator.config.defaultRecallNamespaces) ? recallNamespacesForPrincipal(principal, this.orchestrator.config) : [];
6350
- const effectiveNamespaces = namespaceOverride ? [namespaceOverride] : profilePlan ? expandScopeProfileReadNamespaces({
6351
- profilePlan,
6352
- principalSelfNamespace: profilePlan.baseNamespace,
6353
- config: this.orchestrator.config,
6354
- principal,
6355
- codingOverlay: profileCodingOverlay,
6356
- legacyRecallNamespaces
6357
- }) : legacyRecallNamespaces;
6358
- const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
6359
- let budgetDecision;
6360
- let recordBudgetAfterSuccess = false;
6361
- if (modeSkipsBudget) {
6362
- budgetDecision = {
6363
- allowed: true,
6364
- reason: "allowed-same-namespace",
6365
- count: 0,
6366
- limit: {
6367
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
6368
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
6369
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
6370
- }
6371
- };
6372
- } else {
6373
- let anyCrossNamespace = false;
6374
- let denied = null;
6375
- let crossNamespaceDecision = null;
6376
- for (const ns of effectiveNamespaces) {
6377
- const peek = this.budget.peek({
6378
- principal,
6379
- principalNamespace: budgetPrincipalNamespace,
6380
- queryNamespace: ns
6381
- });
6382
- if (peek.reason !== "allowed-same-namespace") {
6383
- anyCrossNamespace = true;
6384
- crossNamespaceDecision ??= peek;
6385
- }
6386
- if (!peek.allowed) {
6387
- denied = peek;
6388
- break;
6389
- }
6390
- }
6391
- if (denied) {
6392
- budgetDecision = denied;
6393
- } else if (anyCrossNamespace) {
6394
- budgetDecision = crossNamespaceDecision ?? {
6395
- allowed: true,
6396
- reason: "allowed-under-soft",
6397
- count: 0,
6398
- limit: {
6399
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
6400
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
6401
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
6402
- }
6403
- };
6404
- recordBudgetAfterSuccess = true;
6405
- } else {
6406
- budgetDecision = {
6407
- allowed: true,
6408
- reason: "allowed-same-namespace",
6409
- count: 0,
6410
- limit: {
6411
- softLimit: this.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
6412
- hardLimit: this.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
6413
- windowMs: this.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
6414
- }
6415
- };
6416
- }
6417
- if (!budgetDecision.allowed) {
6418
- throw new EngramAccessInputError(
6419
- `recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
6420
- );
6421
- }
6422
- this.budget.gc();
6423
- }
6424
- const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
6425
- let asOf;
6426
- if (request.asOf !== void 0 && request.asOf !== null) {
6427
- if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
6428
- throw new EngramAccessInputError(
6429
- "asOf must be a non-empty ISO 8601 timestamp string"
6430
- );
6431
- }
6432
- const parsed = Date.parse(request.asOf);
6433
- if (!Number.isFinite(parsed)) {
6434
- throw new EngramAccessInputError(
6435
- `asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`
6436
- );
6437
- }
6438
- asOf = request.asOf;
6439
- }
6440
- const recallOptions = {
6441
- namespace: namespaceOverride,
6442
- topK,
6443
- mode,
6444
- ...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
6445
- ...asOf !== void 0 ? { asOf } : {},
6446
- ...request.includeLowConfidence === true ? { includeLowConfidence: true } : {}
6447
- };
6448
- const startedAt = Date.now();
6449
- const context = await this.orchestrator.recall(query, request.sessionKey, recallOptions);
6450
- const snapshot = request.sessionKey ? this.orchestrator.lastRecall.get(request.sessionKey) : null;
6451
- const effectiveNamespace = snapshot?.namespace ? this.resolveNamespace(snapshot.namespace) : namespace;
6452
- const resultsReturned = snapshot?.memoryIds?.length ?? 0;
6453
- const appliedTopK = snapshot?.budgetsApplied?.appliedTopK;
6454
- const configMaxResults = typeof this.orchestrator.config.qmdMaxResults === "number" && Number.isFinite(this.orchestrator.config.qmdMaxResults) && this.orchestrator.config.qmdMaxResults > 0 ? this.orchestrator.config.qmdMaxResults : 0;
6455
- const topKDenominator = typeof appliedTopK === "number" && Number.isFinite(appliedTopK) && appliedTopK > 0 ? Math.max(appliedTopK, resultsReturned) : typeof topK === "number" && topK > 0 ? Math.max(topK, resultsReturned) : Math.max(configMaxResults, resultsReturned, 1);
6456
- const topKConfidence = snapshot && topKDenominator > 0 ? Math.min(1, resultsReturned / topKDenominator) : void 0;
6457
- const escalationDecision = decideDisclosureEscalation({
6458
- mode: this.orchestrator.config.recallDisclosureEscalation,
6459
- threshold: this.orchestrator.config.recallDisclosureEscalationThreshold,
6460
- originalDisclosure: requestedDisclosure,
6461
- callerProvidedDisclosure,
6462
- topKConfidence
6463
- });
6464
- const disclosure = escalationDecision.effective;
6465
- const trimmedSessionKey = request.sessionKey?.trim() || void 0;
6466
- const rawExcerptNamespace = disclosure === "raw" ? this.resolveRawExcerptReadNamespace(
6467
- request.namespace,
6468
- trimmedSessionKey,
6469
- authenticatedPrincipal
6470
- ) : void 0;
6471
- const hasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
6472
- const rawExcerptsSuppressed = disclosure === "raw" && !hasExplicitNamespace && rawExcerptNamespace === void 0;
6473
- const rawExcerptSessionIds = disclosure === "raw" && rawExcerptNamespace && trimmedSessionKey ? this.resolveLcmReadSessionIds(
6474
- request.namespace,
6475
- rawExcerptNamespace,
6476
- trimmedSessionKey,
6477
- authenticatedPrincipal
6478
- ) : void 0;
6479
- let results = await this.serializeRecallResults(snapshot, disclosure, {
6480
- query,
6481
- sessionKey: trimmedSessionKey,
6482
- ...rawExcerptNamespace ? { rawExcerptNamespace } : {},
6483
- ...rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds } : {},
6484
- ...rawExcerptsSuppressed ? { rawExcerptsSuppressed } : {}
6485
- });
6486
- const filterTags = normalizeTags(request.tags);
6487
- let tagMatchMode;
6488
- try {
6489
- tagMatchMode = parseTagMatch(request.tagMatch);
6490
- } catch (err) {
6491
- throw new EngramAccessInputError(
6492
- err instanceof Error ? err.message : String(err)
6493
- );
6494
- }
6495
- let effectiveContext = context;
6496
- if (filterTags && filterTags.length > 0) {
6497
- const beforeIds = results.map((r) => r.id);
6498
- const { results: admitted } = applyTagFilter(results, {
6499
- tags: filterTags,
6500
- tagMatch: tagMatchMode
6501
- });
6502
- results = admitted;
6503
- const admittedIds = new Set(results.map((r) => r.id));
6504
- const droppedAny = beforeIds.some((id) => !admittedIds.has(id));
6505
- if (droppedAny) {
6506
- effectiveContext = results.map((r) => {
6507
- const content = typeof r.content === "string" ? r.content ?? "" : "";
6508
- const preview = typeof r.preview === "string" ? r.preview ?? "" : "";
6509
- return content || preview;
6510
- }).filter((s) => s.length > 0).join("\n\n");
6511
- }
6512
- }
6513
- const filteredMemoryIds = filterTags && filterTags.length > 0 ? results.map((r) => r.id) : snapshot?.memoryIds ?? [];
6514
- const debug = await this.buildRecallDebug(
6515
- snapshot,
6516
- effectiveNamespace,
6517
- request.includeDebug === true,
6518
- request.sessionKey
6519
- );
6520
- let auditAnomalies;
6521
- if (this.auditAdapter) {
6522
- try {
6523
- const resolvedAgentId = principal ?? "__anonymous__";
6524
- const auditEntry = {
6525
- ts: (/* @__PURE__ */ new Date()).toISOString(),
6526
- sessionKey: request.sessionKey ?? "",
6527
- agentId: resolvedAgentId,
6528
- trigger: "access-surface",
6529
- queryText: query,
6530
- candidateMemoryIds: snapshot?.memoryIds ?? [],
6531
- // Audit must reflect what was actually injected, not what
6532
- // recall produced before the tag filter. Using `context`
6533
- // (pre-filter) overstates injectedChars and can leak content
6534
- // from excluded memories into the audit summary (cursor
6535
- // Medium on PR #712).
6536
- summary: effectiveContext.slice(0, 200) || null,
6537
- injectedChars: effectiveContext.length,
6538
- toggleState: "enabled",
6539
- latencyMs: Date.now() - startedAt,
6540
- plannerMode: snapshot?.plannerMode ?? mode,
6541
- requestedMode: mode,
6542
- fallbackUsed: snapshot?.fallbackUsed ?? false
6543
- };
6544
- const auditResult = await this.auditAdapter.record(
6545
- resolvedAgentId || "__anonymous__",
6546
- auditEntry
6547
- );
6548
- auditAnomalies = auditResult.anomalies;
6549
- } catch {
6550
- }
6551
- }
6552
- return {
6553
- response: {
6554
- query,
6555
- sessionKey: request.sessionKey,
6556
- namespace: effectiveNamespace,
6557
- context: effectiveContext,
6558
- count: filterTags && filterTags.length > 0 ? results.length : snapshot?.memoryIds.length ?? results.length,
6559
- memoryIds: filteredMemoryIds,
6560
- results,
6561
- recordedAt: snapshot?.recordedAt,
6562
- traceId: snapshot?.traceId,
6563
- plannerMode: snapshot?.plannerMode ?? mode,
6564
- fallbackUsed: snapshot?.fallbackUsed ?? false,
6565
- sourcesUsed: snapshot?.sourcesUsed ?? [],
6566
- disclosure,
6567
- budgetsApplied: snapshot?.budgetsApplied,
6568
- auditAnomalies,
6569
- budgetWarning: budgetDecision.reason === "warn-over-soft" ? budgetDecision : void 0,
6570
- latencyMs: snapshot?.latencyMs ?? Date.now() - startedAt,
6571
- debug
6572
- },
6573
- budgetRecordPrincipal: recordBudgetAfterSuccess ? principal : null
6574
- };
6575
- }
6576
- async recallExplain(request = {}) {
6577
- const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
6578
- const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
6579
- const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
6580
- if (requestedNamespace) {
6581
- if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
6582
- return { found: false };
6583
- }
6584
- } else if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal) {
6585
- return { found: false };
6586
- }
6587
- const snapshot = request.sessionKey ? (() => {
6588
- const candidate = this.orchestrator.lastRecall.get(request.sessionKey);
6589
- if (!candidate) return null;
6590
- if (!requestedNamespace) return candidate;
6591
- return candidate.namespace === requestedNamespace ? candidate : null;
6592
- })() : (() => {
6593
- const candidate = this.orchestrator.lastRecall.getMostRecent();
6594
- if (!candidate) return null;
6595
- if (!requestedNamespace) return candidate;
6596
- return candidate.namespace === requestedNamespace ? candidate : null;
6597
- })();
6598
- const readableSnapshot = (() => {
6599
- if (!snapshot || !resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return snapshot;
6600
- const snapshotNamespace = snapshot.namespace ?? this.orchestrator.config.defaultNamespace;
6601
- return canReadNamespace(principal, snapshotNamespace, this.orchestrator.config) ? snapshot : null;
6602
- })();
6603
- const namespace = (() => {
6604
- if (requestedNamespace) return requestedNamespace;
6605
- if (readableSnapshot?.namespace) return readableSnapshot.namespace;
6606
- const fallbackNamespace = this.orchestrator.config.defaultNamespace;
6607
- if (!resolveNamespaceCapabilities(this.orchestrator.config).namespaces) return fallbackNamespace;
6608
- return canReadNamespace(principal, fallbackNamespace, this.orchestrator.config) ? fallbackNamespace : null;
6609
- })();
6610
- if (!namespace) return { found: false };
6611
- const [intent, graph] = await Promise.all([
6612
- this.orchestrator.getLastIntentSnapshot(namespace),
6613
- this.orchestrator.getLastGraphRecallSnapshot(namespace)
6614
- ]);
6615
- if (!readableSnapshot && !intent && !graph) return { found: false };
6616
- return { found: true, snapshot: readableSnapshot ?? void 0, intent, graph };
6617
- }
6618
- async recallTierExplain(sessionKey, namespace, authenticatedPrincipal) {
6619
- const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
6620
- const requestedNamespace = namespace?.trim() ? this.resolveNamespace(namespace) : void 0;
6621
- const principal = authenticatedPrincipal?.trim() || resolvePrincipal(sessionKey, this.orchestrator.config);
6622
- if (requestedNamespace) {
6623
- if (!canReadNamespace(principal, requestedNamespace, this.orchestrator.config)) {
6624
- return toRecallExplainJson(null);
6625
- }
6626
- } else if (namespacesEnabled && !authenticatedPrincipal?.trim() && !sessionKey?.trim()) {
6627
- return toRecallExplainJson(null);
6628
- }
6629
- const candidate = sessionKey ? this.orchestrator.lastRecall.get(sessionKey) : this.orchestrator.lastRecall.getMostRecent();
6630
- const snapshot = (() => {
6631
- if (!candidate) return null;
6632
- if (requestedNamespace) {
6633
- return candidate.namespace === requestedNamespace ? candidate : null;
6634
- }
6635
- if (!namespacesEnabled) return candidate;
6636
- const snapshotNs = candidate.namespace ?? this.orchestrator.config.defaultNamespace;
6637
- return canReadNamespace(principal, snapshotNs, this.orchestrator.config) ? candidate : null;
6638
- })();
6639
- return toRecallExplainJson(snapshot);
6640
- }
6641
- /**
6642
- * Recall X-ray (issue #570). Runs a recall with `xrayCapture: true`
6643
- * and returns the resulting snapshot as structured JSON so every
6644
- * surface (CLI / HTTP / MCP) gets the same payload. Namespace scope
6645
- * is enforced before the recall fires (CLAUDE.md rule 42 — read and
6646
- * write paths must resolve through the same namespace layer) so an
6647
- * unauthorized principal cannot capture an x-ray for a namespace it
6648
- * cannot read.
6649
- */
6650
- async recallXray(request) {
6651
- const query = typeof request.query === "string" ? request.query : "";
6652
- if (query.trim().length === 0) {
6653
- throw new Error("recallXray: query is required and must be non-empty");
6654
- }
6655
- if (request.disclosure !== void 0 && !isRecallDisclosure(request.disclosure)) {
6656
- throw new EngramAccessInputError(
6657
- `recallXray: disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
6658
- );
6659
- }
6660
- const namespacesEnabled = resolveNamespaceCapabilities(this.orchestrator.config).namespaces;
6661
- const requestedNamespace = request.namespace?.trim() ? this.resolveNamespace(request.namespace) : void 0;
6662
- const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
6663
- const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.orchestrator.config);
6664
- if (requestedNamespace) {
6665
- if (!canReadNamespace(
6666
- principal,
6667
- requestedNamespace,
6668
- this.orchestrator.config
6669
- )) {
6670
- return { snapshotFound: false };
6671
- }
6672
- } else if (namespacesEnabled && !authenticatedPrincipal && !request.sessionKey?.trim()) {
6673
- return { snapshotFound: false };
6674
- }
6675
- let budgetOverride;
6676
- if (request.budget !== void 0 && request.budget !== null) {
6677
- const parsed = typeof request.budget === "number" ? request.budget : Number(request.budget);
6678
- if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
6679
- throw new Error(
6680
- `recallXray: budget expects a positive integer; got ${JSON.stringify(request.budget)}`
6681
- );
6682
- }
6683
- budgetOverride = parsed;
6684
- }
6685
- const mode = this.normalizeRecallMode(request.mode);
6686
- const disclosure = request.disclosure ?? DEFAULT_RECALL_DISCLOSURE;
6687
- const previousQueue = this.xrayQueue;
6688
- let release = () => {
6689
- };
6690
- this.xrayQueue = new Promise((resolve2) => {
6691
- release = resolve2;
6692
- });
6693
- await previousQueue;
6694
- const recallStartedAt = Date.now();
6695
- const recallSessionKey = request.sessionKey?.trim() || void 0;
6696
- let xrayResponse = { snapshotFound: false };
6697
- try {
6698
- this.orchestrator.clearLastXraySnapshot();
6699
- await this.orchestrator.recall(query, recallSessionKey, {
6700
- xrayCapture: true,
6701
- ...requestedNamespace ? { namespace: requestedNamespace } : {},
6702
- ...budgetOverride !== void 0 ? { budgetCharsOverride: budgetOverride } : {},
6703
- ...mode !== void 0 ? { mode } : {},
6704
- // When the caller supplies an authenticated principal, forward
6705
- // it via the dedicated override channel so orchestrator-side
6706
- // ACL decisions use the SAME principal the access-surface
6707
- // pre-check above authorized. Threading an
6708
- // `authenticatedPrincipal` through `sessionKey` would be wrong:
6709
- // `resolvePrincipal(sessionKey)` only maps configured raw
6710
- // session keys and otherwise collapses to `"default"`, which
6711
- // in namespace-enabled deployments produces false denials /
6712
- // wrong-scope serving despite the pre-check passing
6713
- // (CLAUDE.md rule 42).
6714
- ...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
6715
- ...request.currentContextScopes !== void 0 ? { currentContextScopes: request.currentContextScopes } : {}
6716
- });
6717
- const rawSnapshot = this.orchestrator.getLastXraySnapshot();
6718
- const namespaceMismatch = requestedNamespace !== void 0 && rawSnapshot?.namespace !== requestedNamespace;
6719
- if (!rawSnapshot) {
6720
- xrayResponse = { snapshotFound: false };
6721
- } else if (namespaceMismatch) {
6722
- xrayResponse = { snapshotFound: false };
6723
- } else {
6724
- let snapshot = rawSnapshot;
6725
- const xrayFilterTags = normalizeTags(request.tags);
6726
- let xrayTagMatch;
6727
- try {
6728
- xrayTagMatch = parseTagMatch(request.tagMatch);
6729
- } catch (err) {
6730
- throw new EngramAccessInputError(
6731
- err instanceof Error ? err.message : String(err)
6732
- );
6733
- }
6734
- if (xrayFilterTags && xrayFilterTags.length > 0) {
6735
- const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
6736
- const tagsByIndex = await Promise.all(
6737
- snapshot.results.map(async (result) => {
6738
- try {
6739
- const storage = await this.orchestrator.getStorage(namespace);
6740
- const memory = await storage.readMemoryByPath(result.path);
6741
- const t = memory?.frontmatter?.tags;
6742
- return Array.isArray(t) ? normalizeProjectionTags(t) : [];
6743
- } catch {
6744
- return [];
6745
- }
6746
- })
6747
- );
6748
- const tagged = snapshot.results.map((result, index) => ({
6749
- result,
6750
- tags: tagsByIndex[index] ?? []
6751
- }));
6752
- const { results: admittedTagged, trace } = applyTagFilter(tagged, {
6753
- tags: xrayFilterTags,
6754
- tagMatch: xrayTagMatch
6755
- });
6756
- const admittedResults = admittedTagged.map((entry) => entry.result);
6757
- const filters = trace ? [...snapshot.filters, trace] : snapshot.filters;
6758
- snapshot = { ...snapshot, results: admittedResults, filters };
6759
- }
6760
- if (request.disclosure !== void 0) {
6761
- const disclosure2 = request.disclosure;
6762
- const namespace = snapshot.namespace ? this.resolveNamespace(snapshot.namespace) : this.orchestrator.config.defaultNamespace;
6763
- const trimmedSessionKey = request.sessionKey?.trim() || void 0;
6764
- const rawExcerptNamespace = disclosure2 === "raw" ? this.resolveRawExcerptReadNamespace(
6765
- request.namespace,
6766
- trimmedSessionKey,
6767
- authenticatedPrincipal
6768
- ) : namespace;
6769
- const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
6770
- const rawExcerptsSuppressed = disclosure2 === "raw" && !xrayHasExplicitNamespace && rawExcerptNamespace === void 0;
6771
- const rawExcerptSessionIds = disclosure2 === "raw" && trimmedSessionKey && rawExcerptNamespace ? this.resolveLcmReadSessionIds(
6772
- request.namespace,
6773
- rawExcerptNamespace,
6774
- trimmedSessionKey,
6775
- authenticatedPrincipal
6776
- ) : void 0;
6777
- const rawExcerpts = disclosure2 === "raw" && !rawExcerptsSuppressed ? await this.fetchRawExcerpts(disclosure2, {
6778
- query,
6779
- ...trimmedSessionKey ? { sessionKey: trimmedSessionKey } : {},
6780
- ...rawExcerptNamespace ? { namespace: rawExcerptNamespace } : {},
6781
- ...rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawExcerptSessionIds } : {}
6782
- }) : disclosure2 === "raw" ? [] : null;
6783
- const rawExcerptText = rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts.map((e) => e.content).join("\n") : "";
6784
- const memoryByIndex = await Promise.all(
6785
- snapshot.results.map(async (result) => {
6786
- try {
6787
- const storage = await this.orchestrator.getStorage(namespace);
6788
- return await storage.readMemoryByPath(result.path);
6789
- } catch {
6790
- return null;
6791
- }
6792
- })
6793
- );
6794
- const firstReadableIndex = memoryByIndex.findIndex((m) => m !== null);
6795
- const baseDir = (await this.orchestrator.getStorage(namespace)).dir;
6796
- const decorated = snapshot.results.map((result, index) => {
6797
- const memory = memoryByIndex[index];
6798
- if (!memory) {
6799
- return { ...result, disclosure: disclosure2 };
6800
- }
6801
- const shaped = shapeMemorySummary(
6802
- memory,
6803
- baseDir,
6804
- disclosure2,
6805
- disclosure2 === "raw" && index === firstReadableIndex && rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts : void 0
6806
- );
6807
- return {
6808
- ...result,
6809
- disclosure: disclosure2,
6810
- estimatedTokens: estimateRecallTokens(JSON.stringify(shaped))
6811
- };
6812
- });
6813
- if (disclosure2 === "raw" && firstReadableIndex === -1 && rawExcerptText.length > 0 && decorated.length > 0) {
6814
- decorated[0] = {
6815
- ...decorated[0],
6816
- disclosure: disclosure2,
6817
- estimatedTokens: estimateRecallTokens(rawExcerptText)
6818
- };
6819
- }
6820
- const decoratedSnapshot = { ...snapshot, results: decorated };
6821
- xrayResponse = {
6822
- snapshotFound: true,
6823
- snapshot: decoratedSnapshot
6824
- };
6825
- } else {
6826
- xrayResponse = {
6827
- snapshotFound: true,
6828
- snapshot
6829
- };
6830
- }
6831
- }
6832
- } finally {
6833
- release();
6834
- }
6835
- if (request.includeRecall === true && xrayResponse.snapshotFound === true && xrayResponse.snapshot) {
6836
- const xrayRawExcerptNamespace = disclosure === "raw" ? this.resolveRawExcerptReadNamespace(
6837
- request.namespace,
6838
- recallSessionKey,
6839
- authenticatedPrincipal
6840
- ) : void 0;
6841
- const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
6842
- const xrayRawExcerptsSuppressed = disclosure === "raw" && !xrayHasExplicitNamespace && xrayRawExcerptNamespace === void 0;
6843
- const xrayRawExcerptSessionIds = disclosure === "raw" && xrayRawExcerptNamespace && recallSessionKey ? this.resolveLcmReadSessionIds(
6844
- request.namespace,
6845
- xrayRawExcerptNamespace,
6846
- recallSessionKey,
6847
- authenticatedPrincipal
6848
- ) : void 0;
6849
- return {
6850
- ...xrayResponse,
6851
- recall: await this.buildRecallResponseFromXraySnapshot({
6852
- query,
6853
- sessionKey: recallSessionKey,
6854
- snapshot: xrayResponse.snapshot,
6855
- disclosure,
6856
- startedAt: recallStartedAt,
6857
- requestedMode: request.mode,
6858
- normalizedMode: mode,
6859
- ...xrayRawExcerptNamespace ? { rawExcerptNamespace: xrayRawExcerptNamespace } : {},
6860
- ...xrayRawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: xrayRawExcerptSessionIds } : {},
6861
- ...xrayRawExcerptsSuppressed ? { rawExcerptsSuppressed: xrayRawExcerptsSuppressed } : {}
6862
- })
6863
- };
6864
- }
6865
- return xrayResponse;
6866
- }
6867
- // Sequence lock for `recallXray` — see comment inside the method.
6868
- // Lives on the instance so every x-ray call on the same service
6869
- // shares it, and so separate services in the same process (e.g.
6870
- // per-tenant) do not block each other.
6871
- xrayQueue = Promise.resolve();
6872
- async memoryStore(request, hooks) {
6873
- const namespace = await this.resolveCodingScopedWriteNamespace(request);
6874
- const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
6875
- if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
6876
- throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
6118
+ }
6119
+ // Sequence lock for `recallXray` — see comment inside the method.
6120
+ // Lives on the instance so every x-ray call on the same service
6121
+ // shares it, and so separate services in the same process (e.g.
6122
+ // per-tenant) do not block each other.
6123
+ xrayQueue = Promise.resolve();
6124
+ async memoryStore(request, hooks) {
6125
+ const namespace = await this.resolveCodingScopedWriteNamespace(request);
6126
+ const schemaVersion = request.schemaVersion ?? ENGRAM_ACCESS_WRITE_SCHEMA_VERSION;
6127
+ if (schemaVersion !== ENGRAM_ACCESS_WRITE_SCHEMA_VERSION) {
6128
+ throw new EngramAccessInputError(`unsupported schemaVersion: ${schemaVersion}`);
6877
6129
  }
6878
6130
  const execute = async () => {
6879
6131
  const candidate = this.validateWriteCandidate(request, namespace);
@@ -9242,1123 +8494,2157 @@ ${next}`);
9242
8494
  return { explanation };
9243
8495
  }
9244
8496
  /**
9245
- * Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
9246
- *
9247
- * Reads adjacency from the JSONL edge store written by `GraphIndex` and
9248
- * resolves node metadata via the namespaced storage manager. Namespace
9249
- * resolution mirrors the read-side path used by `recall` /
9250
- * `procedureStats`, so multi-principal deployments can't leak edges from
9251
- * a peer namespace (CLAUDE.md rule 42).
8497
+ * Read-only graph snapshot for the admin pane (issue #691 PR 2/5).
8498
+ *
8499
+ * Reads adjacency from the JSONL edge store written by `GraphIndex` and
8500
+ * resolves node metadata via the namespaced storage manager. Namespace
8501
+ * resolution mirrors the read-side path used by `recall` /
8502
+ * `procedureStats`, so multi-principal deployments can't leak edges from
8503
+ * a peer namespace (CLAUDE.md rule 42).
8504
+ */
8505
+ async graphSnapshot(request, authenticatedPrincipal) {
8506
+ const namespace = this.resolveReadableNamespace(
8507
+ request.namespace,
8508
+ authenticatedPrincipal
8509
+ );
8510
+ const storage = await this.orchestrator.getStorage(namespace);
8511
+ const cfg = this.orchestrator.config;
8512
+ const graphCaps = resolveGraphConstructionCapabilities(cfg);
8513
+ let namespaceRootReal;
8514
+ try {
8515
+ namespaceRootReal = await nodeFs.realpath(storage.dir);
8516
+ } catch {
8517
+ namespaceRootReal = nodePath.resolve(storage.dir);
8518
+ }
8519
+ const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep) ? namespaceRootReal : namespaceRootReal + nodePath.sep;
8520
+ const loadNode = async (relPath) => {
8521
+ if (nodePath.isAbsolute(relPath)) {
8522
+ log.warn(
8523
+ `graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
8524
+ );
8525
+ return null;
8526
+ }
8527
+ const candidate = nodePath.resolve(namespaceRootReal, relPath);
8528
+ if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
8529
+ log.warn(
8530
+ `graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
8531
+ );
8532
+ return null;
8533
+ }
8534
+ let canonical;
8535
+ try {
8536
+ canonical = await nodeFs.realpath(candidate);
8537
+ } catch {
8538
+ canonical = candidate;
8539
+ }
8540
+ if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
8541
+ log.warn(
8542
+ `graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) that resolved outside namespace root`
8543
+ );
8544
+ return null;
8545
+ }
8546
+ const memory = await storage.readMemoryByPath(canonical);
8547
+ if (!memory) return null;
8548
+ const fm = memory.frontmatter;
8549
+ return {
8550
+ category: fm.category ?? "unknown",
8551
+ label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
8552
+ updated: fm.updated
8553
+ };
8554
+ };
8555
+ return buildGraphSnapshot({
8556
+ memoryDir: namespaceRootReal,
8557
+ graphConfig: {
8558
+ entityGraph: graphCaps.entityGraph,
8559
+ timeGraph: graphCaps.timeGraph,
8560
+ causalGraph: graphCaps.causalGraph
8561
+ },
8562
+ request: {
8563
+ limit: request.limit,
8564
+ since: request.since,
8565
+ focusNodeId: request.focusNodeId,
8566
+ categories: request.categories
8567
+ },
8568
+ loadNode
8569
+ });
8570
+ }
8571
+ async memoryFeedback(request) {
8572
+ if (!resolveRecallEnhancementCapabilities(this.orchestrator.config).feedback) {
8573
+ return {
8574
+ recorded: false,
8575
+ enabled: false,
8576
+ reason: "Feedback is disabled. Enable `feedbackEnabled: true` in the Engram config to store feedback."
8577
+ };
8578
+ }
8579
+ await this.orchestrator.recordMemoryFeedback(
8580
+ request.memoryId,
8581
+ request.vote,
8582
+ request.note
8583
+ );
8584
+ return { recorded: true };
8585
+ }
8586
+ /**
8587
+ * Record a Memory Worth outcome observation (issue #560 PR 3).
8588
+ *
8589
+ * This is distinct from `memoryFeedback` — feedback is a human thumbs
8590
+ * up/down on whether a recalled memory was relevant; outcome is an
8591
+ * automated signal about whether the session that consumed the memory
8592
+ * ultimately succeeded or failed. Outcomes feed the Laplace-smoothed
8593
+ * worth score (`computeMemoryWorth`, PR 2) that PR 4 will use to
8594
+ * downweight memories correlated with bad sessions.
8595
+ *
8596
+ * The underlying writer only touches fact-category memories. Corrections,
8597
+ * procedures, and other kinds return `{ ok: false, reason:
8598
+ * "ineligible_category" }` so a ledger drainer doesn't need to pre-filter.
8599
+ */
8600
+ async memoryOutcome(request) {
8601
+ if (request.memoryId.includes("/") || request.memoryId.includes("\\")) {
8602
+ throw new EngramAccessInputError(
8603
+ "memoryId must not contain path separators"
8604
+ );
8605
+ }
8606
+ const resolvedNs = this.writableNamespaceFor(
8607
+ request.namespace,
8608
+ request.sessionKey,
8609
+ request.principal
8610
+ );
8611
+ const storage = await this.orchestrator.getStorage(resolvedNs);
8612
+ return recordMemoryOutcome(storage, {
8613
+ memoryPath: `${request.memoryId}.md`,
8614
+ outcome: request.outcome,
8615
+ timestamp: request.timestamp
8616
+ });
8617
+ }
8618
+ async memoryPromote(request) {
8619
+ const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
8620
+ const storage = await this.orchestrator.getStorage(resolvedNs);
8621
+ await storage.updateMemoryFrontmatter(request.memoryId, {
8622
+ lifecycleState: "active",
8623
+ updated: (/* @__PURE__ */ new Date()).toISOString()
8624
+ });
8625
+ return { promoted: true, memoryId: request.memoryId };
8626
+ }
8627
+ async memoryActionApply(request) {
8628
+ const actionTypes = /* @__PURE__ */ new Set([
8629
+ "store_episode",
8630
+ "store_note",
8631
+ "update_note",
8632
+ "create_artifact",
8633
+ "summarize_node",
8634
+ "discard",
8635
+ "link_graph"
8636
+ ]);
8637
+ if (!actionTypes.has(request.action)) {
8638
+ throw new EngramAccessInputError(
8639
+ `memory_action_apply: invalid action ${JSON.stringify(request.action)}`
8640
+ );
8641
+ }
8642
+ if (resolveCompressionCapabilities(this.orchestrator.config).contextCompressionActions !== true) {
8643
+ throw new EngramAccessInputError(
8644
+ "memory_action_apply is disabled; enable contextCompressionActionsEnabled to use this tool"
8645
+ );
8646
+ }
8647
+ const outcome = request.outcome ?? "skipped";
8648
+ if (outcome !== "applied" && outcome !== "skipped" && outcome !== "failed") {
8649
+ throw new EngramAccessInputError(
8650
+ `memory_action_apply: outcome must be "applied", "skipped", or "failed"; got ${JSON.stringify(outcome)}`
8651
+ );
8652
+ }
8653
+ const resolvedNs = this.writableNamespaceFor(
8654
+ request.namespace,
8655
+ request.sessionKey,
8656
+ request.principal
8657
+ );
8658
+ const inputSummaryParts = [
8659
+ request.content,
8660
+ request.category ? `category=${request.category}` : void 0,
8661
+ request.linkTargetId ? `linkTargetId=${request.linkTargetId}` : void 0,
8662
+ request.linkType ? `linkType=${request.linkType}` : void 0,
8663
+ typeof request.linkStrength === "number" ? `linkStrength=${request.linkStrength}` : void 0,
8664
+ request.artifactType ? `artifactType=${request.artifactType}` : void 0,
8665
+ typeof request.execute === "boolean" ? `execute=${request.execute}` : void 0
8666
+ ].filter((part) => typeof part === "string" && part.length > 0);
8667
+ const event = {
8668
+ action: request.action,
8669
+ outcome,
8670
+ namespace: resolvedNs,
8671
+ actor: "access.memory_action_apply",
8672
+ subsystem: "access.memory_action_apply",
8673
+ reason: request.reason,
8674
+ memoryId: request.memoryId,
8675
+ sourceSessionKey: request.sessionKey,
8676
+ inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
8677
+ dryRun: request.dryRun === true,
8678
+ promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash4("sha256").update(request.sourcePrompt).digest("hex") : void 0
8679
+ };
8680
+ const preview = this.orchestrator.previewMemoryActionEvent(event);
8681
+ if (request.dryRun === true) {
8682
+ return { recorded: false, dryRun: true, event: preview };
8683
+ }
8684
+ const recorded = await this.orchestrator.appendMemoryActionEvent(event);
8685
+ return { recorded, event: preview };
8686
+ }
8687
+ async contextCheckpoint(request) {
8688
+ const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
8689
+ const storage = await this.orchestrator.getStorage(resolvedNs);
8690
+ const storageDir = storage.dir;
8691
+ const { writeFile: writeFile4, mkdir: mkdir5 } = await import("fs/promises");
8692
+ const { join: join3, resolve: resolve3 } = await import("path");
8693
+ const safeKey = request.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
8694
+ if (!safeKey) throw new EngramAccessInputError("sessionKey is required");
8695
+ const checkpointDir = join3(storageDir, "checkpoints", safeKey);
8696
+ const resolved = resolve3(checkpointDir);
8697
+ if (!resolved.startsWith(resolve3(storageDir))) {
8698
+ throw new EngramAccessInputError("Invalid sessionKey");
8699
+ }
8700
+ await mkdir5(checkpointDir, { recursive: true });
8701
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8702
+ const filePath = join3(checkpointDir, `checkpoint-${ts}.md`);
8703
+ await writeFile4(filePath, request.context, "utf-8");
8704
+ return { saved: true };
8705
+ }
8706
+ async lcmStatus() {
8707
+ if (!this.orchestrator.lcmEngine || !this.orchestrator.lcmEngine.enabled) {
8708
+ return {
8709
+ enabled: false,
8710
+ archiveAvailable: false
8711
+ };
8712
+ }
8713
+ const stats = await this.orchestrator.lcmEngine.getStats();
8714
+ return {
8715
+ enabled: true,
8716
+ archiveAvailable: true,
8717
+ stats: {
8718
+ totalTurns: stats.totalMessages
8719
+ }
8720
+ };
8721
+ }
8722
+ /**
8723
+ * Record citation usage from an observed oai-mem-citation block.
8724
+ * For each citation entry, extract the memory ID from the path and
8725
+ * increment its access tracking via the orchestrator. Returns the
8726
+ * count of submitted IDs and the count of IDs that matched real memories.
9252
8727
  */
9253
- async graphSnapshot(request, authenticatedPrincipal) {
9254
- const namespace = this.resolveReadableNamespace(
8728
+ async recordCitationUsage(request) {
8729
+ if (request.entries.length === 0) return { submitted: 0, matched: 0 };
8730
+ const resolvedNamespace = this.writableNamespaceFor(
9255
8731
  request.namespace,
9256
- authenticatedPrincipal
8732
+ request.sessionId,
8733
+ request.authenticatedPrincipal
9257
8734
  );
9258
- const storage = await this.orchestrator.getStorage(namespace);
9259
- const cfg = this.orchestrator.config;
9260
- const graphCaps = resolveGraphConstructionCapabilities(cfg);
9261
- let namespaceRootReal;
9262
- try {
9263
- namespaceRootReal = await nodeFs.realpath(storage.dir);
9264
- } catch {
9265
- namespaceRootReal = nodePath.resolve(storage.dir);
9266
- }
9267
- const namespaceRootWithSep = namespaceRootReal.endsWith(nodePath.sep) ? namespaceRootReal : namespaceRootReal + nodePath.sep;
9268
- const loadNode = async (relPath) => {
9269
- if (nodePath.isAbsolute(relPath)) {
9270
- log.warn(
9271
- `graphSnapshot: rejected absolute edge endpoint (len=${relPath.length}) outside namespace root`
9272
- );
9273
- return null;
9274
- }
9275
- const candidate = nodePath.resolve(namespaceRootReal, relPath);
9276
- if (candidate !== namespaceRootReal && !candidate.startsWith(namespaceRootWithSep)) {
9277
- log.warn(
9278
- `graphSnapshot: rejected traversing edge endpoint (len=${relPath.length}) outside namespace root`
9279
- );
9280
- return null;
8735
+ const memoryIds = [];
8736
+ for (const entry of request.entries) {
8737
+ const basename2 = entry.path.split("/").pop() ?? entry.path;
8738
+ const id = basename2.endsWith(".md") ? basename2.slice(0, -3) : basename2;
8739
+ if (id.length > 0) {
8740
+ memoryIds.push(id);
9281
8741
  }
9282
- let canonical;
8742
+ }
8743
+ if (memoryIds.length === 0) return { submitted: 0, matched: 0 };
8744
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
8745
+ const existingIds = await storage.filterExistingMemoryIds(memoryIds);
8746
+ const matchedIds = memoryIds.filter((id) => existingIds.has(id));
8747
+ if (matchedIds.length > 0) {
9283
8748
  try {
9284
- canonical = await nodeFs.realpath(candidate);
8749
+ this.orchestrator.trackMemoryAccess(matchedIds);
9285
8750
  } catch {
9286
- canonical = candidate;
8751
+ log.debug("citation usage tracking: failed to record access for cited memories");
9287
8752
  }
9288
- if (canonical !== namespaceRootReal && !canonical.startsWith(namespaceRootWithSep)) {
9289
- log.warn(
9290
- `graphSnapshot: rejected symlinked edge endpoint (len=${relPath.length}) that resolved outside namespace root`
8753
+ }
8754
+ return { submitted: memoryIds.length, matched: matchedIds.length };
8755
+ }
8756
+ // ── Operator Console state (issue #688 PR 2/3) ────────────────────────────
8757
+ /**
8758
+ * Gather a point-in-time `ConsoleStateSnapshot` from the orchestrator.
8759
+ *
8760
+ * Principal-aware: `resolveReadableNamespace` enforces ACL before the
8761
+ * snapshot is gathered, so callers cannot read a namespace they don't
8762
+ * have read access to (CLAUDE.md rule 42). The resolved namespace's
8763
+ * storage directory is forwarded as `config.memoryDir` so the ledger-
8764
+ * tail reader in `gatherConsoleState` scans the correct namespace root
8765
+ * rather than the global root. Read-only — never mutates orchestrator state.
8766
+ */
8767
+ async consoleState(namespace, principal) {
8768
+ const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
8769
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
8770
+ const { gatherConsoleState } = await import("./state-ITXJKXRE.js");
8771
+ const orchestratorProxy = Object.create(this.orchestrator, {
8772
+ config: {
8773
+ value: { ...this.orchestrator.config, memoryDir: storage.dir },
8774
+ enumerable: true,
8775
+ configurable: true
8776
+ }
8777
+ });
8778
+ return gatherConsoleState(orchestratorProxy);
8779
+ }
8780
+ // ── Peer Registry surfaces (issue #679 PR 4/5) ────────────────────────────
8781
+ /**
8782
+ * List all registered peers. Returns the array of `Peer` objects in
8783
+ * deterministic alphabetical order (mirroring `listPeers` storage semantics).
8784
+ */
8785
+ async peerList() {
8786
+ const { listPeers } = await import("./peers/index.js");
8787
+ const peers = await listPeers(this.orchestrator.config.memoryDir);
8788
+ return { peers };
8789
+ }
8790
+ /**
8791
+ * Get a single peer by id. Returns `{ found: false }` when the peer does
8792
+ * not exist rather than throwing, matching the `memoryGet` / `entityGet`
8793
+ * pattern used throughout the service.
8794
+ */
8795
+ async peerGet(peerId) {
8796
+ const peers = await import("./peers/index.js");
8797
+ const validateId = peers.assertValidPeerId;
8798
+ try {
8799
+ validateId(peerId);
8800
+ } catch (err) {
8801
+ throw new EngramAccessInputError(err.message);
8802
+ }
8803
+ const peer = await peers.readPeer(this.orchestrator.config.memoryDir, peerId);
8804
+ if (!peer) return { found: false };
8805
+ return { found: true, peer };
8806
+ }
8807
+ /**
8808
+ * Upsert a peer. Writes `peers/{id}/identity.md`. On first write the
8809
+ * `createdAt` timestamp is set to now; on subsequent writes only
8810
+ * `displayName` and `notes` are mutated (kind and createdAt are immutable
8811
+ * once set, per the storage contract).
8812
+ *
8813
+ * Returns `{ created: true }` on first write, `{ created: false }` on update.
8814
+ */
8815
+ async peerSet(input) {
8816
+ const peers = await import("./peers/index.js");
8817
+ const validateId = peers.assertValidPeerId;
8818
+ const { id } = input;
8819
+ try {
8820
+ validateId(id);
8821
+ } catch (err) {
8822
+ throw new EngramAccessInputError(err.message);
8823
+ }
8824
+ const memoryDir = this.orchestrator.config.memoryDir;
8825
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8826
+ const existing = await peers.readPeer(memoryDir, id);
8827
+ const ALLOWED_KINDS = /* @__PURE__ */ new Set(["self", "human", "agent", "integration"]);
8828
+ if (!existing) {
8829
+ const kind = input.kind ?? "human";
8830
+ if (!ALLOWED_KINDS.has(kind)) {
8831
+ throw new EngramAccessInputError(
8832
+ `peer kind must be one of ${[...ALLOWED_KINDS].join(", ")}`
9291
8833
  );
9292
- return null;
9293
8834
  }
9294
- const memory = await storage.readMemoryByPath(canonical);
9295
- if (!memory) return null;
9296
- const fm = memory.frontmatter;
9297
- return {
9298
- category: fm.category ?? "unknown",
9299
- label: fm.id ?? nodePath.basename(canonical, nodePath.extname(canonical)),
9300
- updated: fm.updated
8835
+ const newPeer = {
8836
+ id,
8837
+ kind,
8838
+ displayName: input.displayName ?? id,
8839
+ createdAt: now,
8840
+ updatedAt: now,
8841
+ ...typeof input.notes === "string" ? { notes: input.notes } : {}
9301
8842
  };
8843
+ await peers.writePeer(memoryDir, newPeer);
8844
+ return { ok: true, created: true, peer: newPeer };
8845
+ }
8846
+ const updated = {
8847
+ id: existing.id,
8848
+ kind: existing.kind,
8849
+ createdAt: existing.createdAt,
8850
+ updatedAt: now,
8851
+ displayName: input.displayName !== void 0 ? input.displayName : existing.displayName,
8852
+ ...input.notes !== void 0 ? { notes: input.notes } : existing.notes !== void 0 ? { notes: existing.notes } : {}
9302
8853
  };
9303
- return buildGraphSnapshot({
9304
- memoryDir: namespaceRootReal,
9305
- graphConfig: {
9306
- entityGraph: graphCaps.entityGraph,
9307
- timeGraph: graphCaps.timeGraph,
9308
- causalGraph: graphCaps.causalGraph
9309
- },
9310
- request: {
9311
- limit: request.limit,
9312
- since: request.since,
9313
- focusNodeId: request.focusNodeId,
9314
- categories: request.categories
9315
- },
9316
- loadNode
8854
+ await peers.writePeer(memoryDir, updated);
8855
+ return { ok: true, created: false, peer: updated };
8856
+ }
8857
+ /**
8858
+ * Delete a peer by removing `peers/{id}/identity.md`. If the file does not
8859
+ * exist the call is a no-op (idempotent). The peer directory itself
8860
+ * (`peers/{id}/`) is intentionally left in place — profile and interaction
8861
+ * log data are not destroyed.
8862
+ */
8863
+ async peerDelete(peerId) {
8864
+ const peers = await import("./peers/index.js");
8865
+ const validateId = peers.assertValidPeerId;
8866
+ try {
8867
+ validateId(peerId);
8868
+ } catch (err) {
8869
+ throw new EngramAccessInputError(err.message);
8870
+ }
8871
+ const deleted = await peers.deletePeer(this.orchestrator.config.memoryDir, peerId);
8872
+ return { ok: true, deleted };
8873
+ }
8874
+ /**
8875
+ * Destructively purge the entire peer directory for a given peerId —
8876
+ * `identity.md`, `profile.md`, `interactions.log.md`, and any other
8877
+ * files in `peers/{id}/`. Requires `confirm: "yes"` to prevent
8878
+ * accidental invocation.
8879
+ *
8880
+ * This is the DESTRUCTIVE counterpart to `peerDelete`, which only
8881
+ * removes `identity.md`. All companion files are permanently removed.
8882
+ *
8883
+ * Returns `{ ok: true, purged: true }` when the directory existed and
8884
+ * was removed; `{ ok: true, purged: false }` when the directory did
8885
+ * not exist (idempotent no-op).
8886
+ */
8887
+ async peerForget(peerId, opts) {
8888
+ const peers = await import("./peers/index.js");
8889
+ const validateId = peers.assertValidPeerId;
8890
+ try {
8891
+ validateId(peerId);
8892
+ } catch (err) {
8893
+ throw new EngramAccessInputError(err.message);
8894
+ }
8895
+ if (opts.confirm !== "yes") {
8896
+ throw new EngramAccessInputError(
8897
+ "peerForget requires confirm: 'yes' to prevent accidental data loss"
8898
+ );
8899
+ }
8900
+ const result = await peers.forgetPeer(this.orchestrator.config.memoryDir, peerId, {
8901
+ confirm: "yes"
9317
8902
  });
8903
+ return { ok: true, purged: result.purged };
9318
8904
  }
9319
- async memoryFeedback(request) {
9320
- if (!resolveRecallEnhancementCapabilities(this.orchestrator.config).feedback) {
9321
- return {
9322
- recorded: false,
9323
- enabled: false,
9324
- reason: "Feedback is disabled. Enable `feedbackEnabled: true` in the Engram config to store feedback."
9325
- };
8905
+ /**
8906
+ * Get the evolving cognitive profile for a peer. Returns `{ found: false }`
8907
+ * when no profile file exists yet (profile is written by the async reasoner,
8908
+ * PR 2/5). The peer identity itself need not exist for a profile to exist,
8909
+ * but in practice the reasoner only writes profiles for registered peers.
8910
+ */
8911
+ async peerProfileGet(peerId) {
8912
+ const peers = await import("./peers/index.js");
8913
+ const validateId = peers.assertValidPeerId;
8914
+ try {
8915
+ validateId(peerId);
8916
+ } catch (err) {
8917
+ throw new EngramAccessInputError(err.message);
9326
8918
  }
9327
- await this.orchestrator.recordMemoryFeedback(
9328
- request.memoryId,
9329
- request.vote,
9330
- request.note
9331
- );
9332
- return { recorded: true };
8919
+ const profile = await peers.readPeerProfile(this.orchestrator.config.memoryDir, peerId);
8920
+ if (!profile) return { found: false };
8921
+ return { found: true, profile };
8922
+ }
8923
+ // ── Contradiction Review (issue #520) ──────────────────────────────────────
8924
+ get memoryDir() {
8925
+ return this.orchestrator.config.memoryDir;
9333
8926
  }
9334
8927
  /**
9335
- * Record a Memory Worth outcome observation (issue #560 PR 3).
8928
+ * Resolve the storage directory for a given namespace. Used by the SSE
8929
+ * graph-event handler to subscribe to the correct per-namespace bus rather
8930
+ * than the global root (CLAUDE.md rule 42 — read/write paths must resolve
8931
+ * through the same namespace layer).
9336
8932
  *
9337
- * This is distinct from `memoryFeedback` feedback is a human thumbs
9338
- * up/down on whether a recalled memory was relevant; outcome is an
9339
- * automated signal about whether the session that consumed the memory
9340
- * ultimately succeeded or failed. Outcomes feed the Laplace-smoothed
9341
- * worth score (`computeMemoryWorth`, PR 2) that PR 4 will use to
9342
- * downweight memories correlated with bad sessions.
8933
+ * `principal` must be the transport-bound request principal (from
8934
+ * `resolveRequestPrincipal`). When namespaces are enabled, an absent
8935
+ * principal causes `resolveReadableNamespace` to throw an auth error,
8936
+ * matching the behaviour of every other authenticated read path.
9343
8937
  *
9344
- * The underlying writer only touches fact-category memories. Corrections,
9345
- * procedures, and other kinds return `{ ok: false, reason:
9346
- * "ineligible_category" }` so a ledger drainer doesn't need to pre-filter.
8938
+ * Falls back to `this.memoryDir` when namespaces are disabled or the
8939
+ * namespace is absent, matching the behaviour of every other read path.
9347
8940
  */
9348
- async memoryOutcome(request) {
9349
- if (request.memoryId.includes("/") || request.memoryId.includes("\\")) {
8941
+ async getMemoryDirForNamespace(namespace, principal) {
8942
+ const resolved = this.resolveReadableNamespace(namespace, principal);
8943
+ const storage = await this.orchestrator.getStorage(resolved);
8944
+ return storage.dir;
8945
+ }
8946
+ async getReadableStorageForNamespace(namespace, principal) {
8947
+ const resolved = this.resolveReadableNamespace(namespace, principal);
8948
+ const storage = await this.orchestrator.getStorage(resolved);
8949
+ return { namespace: resolved, storage };
8950
+ }
8951
+ async getWritableStorageForNamespace(namespace, principal) {
8952
+ if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal?.trim()) {
9350
8953
  throw new EngramAccessInputError(
9351
- "memoryId must not contain path separators"
8954
+ "authentication required: namespaces are enabled and no principal was supplied"
9352
8955
  );
9353
8956
  }
9354
- const resolvedNs = this.writableNamespaceFor(
9355
- request.namespace,
9356
- request.sessionKey,
9357
- request.principal
9358
- );
9359
- const storage = await this.orchestrator.getStorage(resolvedNs);
9360
- return recordMemoryOutcome(storage, {
9361
- memoryPath: `${request.memoryId}.md`,
9362
- outcome: request.outcome,
9363
- timestamp: request.timestamp
9364
- });
8957
+ const resolved = this.writableNamespaceFor(namespace, void 0, principal);
8958
+ const storage = await this.orchestrator.getStorage(resolved);
8959
+ return { namespace: resolved, storage };
9365
8960
  }
9366
- async memoryPromote(request) {
9367
- const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
9368
- const storage = await this.orchestrator.getStorage(resolvedNs);
9369
- await storage.updateMemoryFrontmatter(request.memoryId, {
9370
- lifecycleState: "active",
9371
- updated: (/* @__PURE__ */ new Date()).toISOString()
8961
+ get storageRef() {
8962
+ return this.orchestrator.storage;
8963
+ }
8964
+ // #1522: recordCatalogWrite removed — catalog touch handled at the storage chokepoint.
8965
+ get configRef() {
8966
+ return this.orchestrator.config;
8967
+ }
8968
+ get localLlmRef() {
8969
+ return this.orchestrator.localLlm ?? null;
8970
+ }
8971
+ get fallbackLlmRef() {
8972
+ return this.orchestrator.fastGatewayLlm ?? null;
8973
+ }
8974
+ get embeddingLookupFactoryRef() {
8975
+ return (storage) => {
8976
+ if (!resolveMemoryLifecycleCapabilities(this.orchestrator.config).embeddingFallback) return void 0;
8977
+ return async (content, limit) => {
8978
+ try {
8979
+ return await this.orchestrator.semanticDedupLookup(content, limit, storage);
8980
+ } catch {
8981
+ return [];
8982
+ }
8983
+ };
8984
+ };
8985
+ }
8986
+ /**
8987
+ * Import a capsule archive into the orchestrator's memory directory.
8988
+ *
8989
+ * Delegates directly to the standalone {@link importCapsuleFn} function.
8990
+ * The `root` parameter defaults to the orchestrator's `memoryDir` when
8991
+ * omitted, so callers that only have access to the service do not need to
8992
+ * thread the config value through.
8993
+ *
8994
+ * `versioning` defaults to the orchestrator's page-versioning config so
8995
+ * `mode: "overwrite"` automatically snapshots prior content without the
8996
+ * caller having to construct the config object.
8997
+ */
8998
+ async capsuleImport(opts) {
8999
+ const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...importOptions } = opts;
9000
+ const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
9001
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9002
+ const root = explicitRoot ?? storage.dir;
9003
+ const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
9004
+ const versioning = importOptions.versioning ?? {
9005
+ enabled: resolveRecallAuxiliaryCapabilities(this.orchestrator.config).versioning,
9006
+ maxVersionsPerPage: this.orchestrator.config.versioningMaxPerPage,
9007
+ sidecarDir: this.orchestrator.config.versioningSidecarDir
9008
+ };
9009
+ await this.validateCapsuleImportArchivePath(importOptions.archivePath);
9010
+ try {
9011
+ return await importCapsule({ ...importOptions, root, memoryDir, versioning });
9012
+ } catch (err) {
9013
+ const message = err instanceof Error ? err.message : String(err);
9014
+ if (this.isCapsuleImportArchiveInputError(err, message)) {
9015
+ throw new EngramAccessInputError(`capsule import failed: ${message}`);
9016
+ }
9017
+ throw err;
9018
+ }
9019
+ }
9020
+ async validateCapsuleImportArchivePath(archivePath) {
9021
+ let archiveStat;
9022
+ try {
9023
+ archiveStat = await stat2(archivePath);
9024
+ } catch (err) {
9025
+ if (!this.isCapsuleImportPathInputFsError(err)) throw err;
9026
+ const message = err instanceof Error ? err.message : String(err);
9027
+ throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
9028
+ }
9029
+ if (!archiveStat.isFile()) {
9030
+ throw new EngramAccessInputError("capsule import failed: archivePath must point to a file");
9031
+ }
9032
+ try {
9033
+ await nodeFs.access(archivePath, fsConstants.R_OK);
9034
+ } catch (err) {
9035
+ if (!this.isCapsuleImportPathInputFsError(err)) throw err;
9036
+ const message = err instanceof Error ? err.message : String(err);
9037
+ throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
9038
+ }
9039
+ }
9040
+ isCapsuleImportPathInputFsError(err) {
9041
+ const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9042
+ return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP";
9043
+ }
9044
+ isCapsuleImportArchiveInputError(err, message) {
9045
+ if (err instanceof ZodError) return true;
9046
+ const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9047
+ if (typeof code === "string" && code.startsWith("Z_")) return true;
9048
+ return message.startsWith("importCapsule: archive") || message.startsWith("importCapsule: bundle") || message.startsWith("importCapsule: manifest") || message.startsWith("importCapsule: record") || /incorrect header check|invalid stored block lengths|not in gzip format|unexpected end of file/i.test(message);
9049
+ }
9050
+ /**
9051
+ * Export a capsule archive from the orchestrator's memory directory.
9052
+ *
9053
+ * HTTP and future MCP surfaces use this rather than calling the transfer
9054
+ * helper directly so namespace ACL checks stay consistent with the archive
9055
+ * write side effect. The exporter still owns archive construction and
9056
+ * validation.
9057
+ */
9058
+ async capsuleExport(opts) {
9059
+ const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...exportOptions } = opts;
9060
+ const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
9061
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9062
+ const root = explicitRoot ?? storage.dir;
9063
+ const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
9064
+ const pluginVersion = exportOptions.pluginVersion ?? await getPackageVersion();
9065
+ return exportCapsule({
9066
+ ...exportOptions,
9067
+ pluginVersion,
9068
+ root,
9069
+ memoryDir: exportOptions.encrypt === true ? memoryDir : void 0
9372
9070
  });
9373
- return { promoted: true, memoryId: request.memoryId };
9374
9071
  }
9375
- async memoryActionApply(request) {
9376
- const actionTypes = /* @__PURE__ */ new Set([
9377
- "store_episode",
9378
- "store_note",
9379
- "update_note",
9380
- "create_artifact",
9381
- "summarize_node",
9382
- "discard",
9383
- "link_graph"
9384
- ]);
9385
- if (!actionTypes.has(request.action)) {
9386
- throw new EngramAccessInputError(
9387
- `memory_action_apply: invalid action ${JSON.stringify(request.action)}`
9388
- );
9072
+ /**
9073
+ * List capsule archives in the namespace-scoped capsule store.
9074
+ *
9075
+ * MCP uses this access-layer method instead of reading arbitrary paths so
9076
+ * capsule discovery remains bound to the same namespace ACLs as export and
9077
+ * import.
9078
+ */
9079
+ async capsuleList(options) {
9080
+ const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
9081
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9082
+ const capsulesDir = defaultCapsulesDir(storage.dir);
9083
+ let dirEntries;
9084
+ try {
9085
+ const capsulesDirStat = await nodeFs.lstat(capsulesDir);
9086
+ if (capsulesDirStat.isSymbolicLink()) {
9087
+ throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
9088
+ }
9089
+ if (!capsulesDirStat.isDirectory()) {
9090
+ throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
9091
+ }
9092
+ dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
9093
+ } catch (err) {
9094
+ const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9095
+ if (code === "ENOENT") {
9096
+ return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
9097
+ }
9098
+ throw err;
9389
9099
  }
9390
- if (resolveCompressionCapabilities(this.orchestrator.config).contextCompressionActions !== true) {
9391
- throw new EngramAccessInputError(
9392
- "memory_action_apply is disabled; enable contextCompressionActionsEnabled to use this tool"
9393
- );
9100
+ const archiveNames = dirEntries.filter(
9101
+ (entry) => entry.isFile() && (entry.name.endsWith(".capsule.json.gz") || entry.name.endsWith(".capsule.json.gz.enc"))
9102
+ ).map((entry) => entry.name).sort();
9103
+ const capsules = [];
9104
+ for (const archiveName of archiveNames) {
9105
+ const archivePath = nodePath.join(capsulesDir, archiveName);
9106
+ const id = archiveName.replace(/\.capsule\.json\.gz\.enc$/, "").replace(/\.capsule\.json\.gz$/, "");
9107
+ const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
9108
+ let createdAt = null;
9109
+ let pluginVersion = null;
9110
+ let fileCount = null;
9111
+ let description = null;
9112
+ let manifestPathOrNull = manifestPath;
9113
+ try {
9114
+ const manifestStat = await nodeFs.lstat(manifestPath);
9115
+ if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
9116
+ capsules.push({
9117
+ id,
9118
+ archivePath,
9119
+ manifestPath: manifestPathOrNull,
9120
+ createdAt,
9121
+ pluginVersion,
9122
+ fileCount,
9123
+ description
9124
+ });
9125
+ continue;
9126
+ }
9127
+ const raw = await nodeFs.readFile(manifestPath, "utf-8");
9128
+ const sidecar = JSON.parse(raw);
9129
+ createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
9130
+ pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
9131
+ fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
9132
+ const capsule = sidecar.capsule;
9133
+ description = capsule && typeof capsule.description === "string" ? capsule.description : null;
9134
+ } catch (err) {
9135
+ const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9136
+ if (code === "ENOENT") {
9137
+ manifestPathOrNull = null;
9138
+ }
9139
+ }
9140
+ capsules.push({
9141
+ id,
9142
+ archivePath,
9143
+ manifestPath: manifestPathOrNull,
9144
+ createdAt,
9145
+ pluginVersion,
9146
+ fileCount,
9147
+ description
9148
+ });
9394
9149
  }
9395
- const outcome = request.outcome ?? "skipped";
9396
- if (outcome !== "applied" && outcome !== "skipped" && outcome !== "failed") {
9397
- throw new EngramAccessInputError(
9398
- `memory_action_apply: outcome must be "applied", "skipped", or "failed"; got ${JSON.stringify(outcome)}`
9150
+ return { namespace: resolvedNamespace, capsulesDir, capsules };
9151
+ }
9152
+ /**
9153
+ * Operator-configured offline-sync excludes (#1786), compiled once per
9154
+ * service instance. Applied to push-side (snapshot/read) enumeration
9155
+ * only — apply-side stays permissive so remote-authoritative runtime
9156
+ * files keep flowing in.
9157
+ */
9158
+ _offlineSyncUserExcludes = null;
9159
+ get offlineSyncUserExcludes() {
9160
+ if (!this._offlineSyncUserExcludes) {
9161
+ this._offlineSyncUserExcludes = compileOfflineSyncExcludeGlobs(
9162
+ this.orchestrator.config.offlineSyncExcludes ?? []
9399
9163
  );
9400
9164
  }
9401
- const resolvedNs = this.writableNamespaceFor(
9402
- request.namespace,
9403
- request.sessionKey,
9404
- request.principal
9405
- );
9406
- const inputSummaryParts = [
9407
- request.content,
9408
- request.category ? `category=${request.category}` : void 0,
9409
- request.linkTargetId ? `linkTargetId=${request.linkTargetId}` : void 0,
9410
- request.linkType ? `linkType=${request.linkType}` : void 0,
9411
- typeof request.linkStrength === "number" ? `linkStrength=${request.linkStrength}` : void 0,
9412
- request.artifactType ? `artifactType=${request.artifactType}` : void 0,
9413
- typeof request.execute === "boolean" ? `execute=${request.execute}` : void 0
9414
- ].filter((part) => typeof part === "string" && part.length > 0);
9415
- const event = {
9416
- action: request.action,
9417
- outcome,
9418
- namespace: resolvedNs,
9419
- actor: "access.memory_action_apply",
9420
- subsystem: "access.memory_action_apply",
9421
- reason: request.reason,
9422
- memoryId: request.memoryId,
9423
- sourceSessionKey: request.sessionKey,
9424
- inputSummary: inputSummaryParts.length > 0 ? inputSummaryParts.join(" | ").slice(0, 500) : void 0,
9425
- dryRun: request.dryRun === true,
9426
- promptHash: typeof request.sourcePrompt === "string" && request.sourcePrompt.length > 0 ? createHash4("sha256").update(request.sourcePrompt).digest("hex") : void 0
9165
+ return this._offlineSyncUserExcludes;
9166
+ }
9167
+ async offlineSyncSnapshot(options = {}) {
9168
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9169
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9170
+ const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9171
+ const snapshotBuilder = options.includeContent === false && options.baseFiles && options.baseFiles.length > 0 ? buildOfflineSyncSnapshotFromBase : buildOfflineSyncSnapshot;
9172
+ const snapshot = await snapshotBuilder({
9173
+ root: storage.dir,
9174
+ sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9175
+ ...options.baseFiles && options.baseFiles.length > 0 ? { baseFiles: options.baseFiles } : {},
9176
+ includeContent: options.includeContent !== false,
9177
+ includeTranscripts: options.includeTranscripts !== false,
9178
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9179
+ readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9180
+ signal: options.signal,
9181
+ userExcludeRegexps: this.offlineSyncUserExcludes
9182
+ });
9183
+ return {
9184
+ namespace: resolvedNamespace,
9185
+ ...snapshot
9427
9186
  };
9428
- const preview = this.orchestrator.previewMemoryActionEvent(event);
9429
- if (request.dryRun === true) {
9430
- return { recorded: false, dryRun: true, event: preview };
9431
- }
9432
- const recorded = await this.orchestrator.appendMemoryActionEvent(event);
9433
- return { recorded, event: preview };
9434
9187
  }
9435
- async contextCheckpoint(request) {
9436
- const resolvedNs = this.writableNamespaceFor(request.namespace, request.sessionKey, request.principal);
9437
- const storage = await this.orchestrator.getStorage(resolvedNs);
9438
- const storageDir = storage.dir;
9439
- const { writeFile: writeFile4, mkdir: mkdir5 } = await import("fs/promises");
9440
- const { join: join2, resolve: resolve2 } = await import("path");
9441
- const safeKey = request.sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
9442
- if (!safeKey) throw new EngramAccessInputError("sessionKey is required");
9443
- const checkpointDir = join2(storageDir, "checkpoints", safeKey);
9444
- const resolved = resolve2(checkpointDir);
9445
- if (!resolved.startsWith(resolve2(storageDir))) {
9446
- throw new EngramAccessInputError("Invalid sessionKey");
9447
- }
9448
- await mkdir5(checkpointDir, { recursive: true });
9449
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9450
- const filePath = join2(checkpointDir, `checkpoint-${ts}.md`);
9451
- await writeFile4(filePath, request.context, "utf-8");
9452
- return { saved: true };
9188
+ async offlineSyncSnapshotStream(options = {}) {
9189
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9190
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9191
+ const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9192
+ return {
9193
+ namespace: resolvedNamespace,
9194
+ format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
9195
+ schemaVersion: 1,
9196
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9197
+ sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9198
+ includeTranscripts: options.includeTranscripts !== false,
9199
+ files: iterateOfflineSyncSnapshotFileRecords({
9200
+ root: storage.dir,
9201
+ includeContent: options.includeContent === true,
9202
+ includeTranscripts: options.includeTranscripts !== false,
9203
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9204
+ readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9205
+ signal: options.signal,
9206
+ userExcludeRegexps: this.offlineSyncUserExcludes
9207
+ })
9208
+ };
9453
9209
  }
9454
- async lcmStatus() {
9455
- if (!this.orchestrator.lcmEngine || !this.orchestrator.lcmEngine.enabled) {
9210
+ async offlineSyncFiles(options) {
9211
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9212
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9213
+ const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9214
+ try {
9215
+ const snapshot = await buildOfflineSyncSnapshotForPaths({
9216
+ root: storage.dir,
9217
+ sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9218
+ paths: options.paths,
9219
+ includeContent: true,
9220
+ includeTranscripts: options.includeTranscripts !== false,
9221
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9222
+ userExcludeRegexps: this.offlineSyncUserExcludes
9223
+ });
9456
9224
  return {
9457
- enabled: false,
9458
- archiveAvailable: false
9225
+ namespace: resolvedNamespace,
9226
+ ...snapshot
9459
9227
  };
9228
+ } catch (error) {
9229
+ const message = error instanceof Error ? error.message : String(error);
9230
+ if (message.startsWith("paths[]:") || message.startsWith("buildOfflineSyncSnapshotForPaths: record path ") || message.startsWith("offline sync snapshot path is excluded:")) {
9231
+ throw new EngramAccessInputError(message);
9232
+ }
9233
+ throw error;
9460
9234
  }
9461
- const stats = await this.orchestrator.lcmEngine.getStats();
9462
- return {
9463
- enabled: true,
9464
- archiveAvailable: true,
9465
- stats: {
9466
- totalTurns: stats.totalMessages
9235
+ }
9236
+ async offlineSyncFileContent(options) {
9237
+ const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9238
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9239
+ try {
9240
+ const chunk = await readOfflineSyncFileContentChunk({
9241
+ root: storage.dir,
9242
+ path: options.path,
9243
+ offset: options.offset,
9244
+ length: options.length,
9245
+ includeTranscripts: options.includeTranscripts !== false,
9246
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9247
+ userExcludeRegexps: this.offlineSyncUserExcludes
9248
+ });
9249
+ return {
9250
+ namespace: resolvedNamespace,
9251
+ ...chunk
9252
+ };
9253
+ } catch (error) {
9254
+ const message = error instanceof Error ? error.message : String(error);
9255
+ if (message.startsWith("path:") || message.startsWith("offset ") || message.startsWith("offset must ") || message.startsWith("length ") || message.startsWith("offline sync file content ")) {
9256
+ throw new EngramAccessInputError(message);
9467
9257
  }
9468
- };
9258
+ throw error;
9259
+ }
9469
9260
  }
9470
- /**
9471
- * Record citation usage from an observed oai-mem-citation block.
9472
- * For each citation entry, extract the memory ID from the path and
9473
- * increment its access tracking via the orchestrator. Returns the
9474
- * count of submitted IDs and the count of IDs that matched real memories.
9475
- */
9476
- async recordCitationUsage(request) {
9477
- if (request.entries.length === 0) return { submitted: 0, matched: 0 };
9261
+ async offlineSyncApplyFileContent(options) {
9478
9262
  const resolvedNamespace = this.writableNamespaceFor(
9479
- request.namespace,
9480
- request.sessionId,
9481
- request.authenticatedPrincipal
9263
+ options.namespace,
9264
+ void 0,
9265
+ options.principal
9482
9266
  );
9483
- const memoryIds = [];
9484
- for (const entry of request.entries) {
9485
- const basename2 = entry.path.split("/").pop() ?? entry.path;
9486
- const id = basename2.endsWith(".md") ? basename2.slice(0, -3) : basename2;
9487
- if (id.length > 0) {
9488
- memoryIds.push(id);
9267
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9268
+ try {
9269
+ const result = await applyOfflineSyncFileContentChunk({
9270
+ root: storage.dir,
9271
+ sourceId: options.sourceId,
9272
+ path: options.path,
9273
+ sha256: options.sha256,
9274
+ bytes: options.bytes,
9275
+ mtimeMs: options.mtimeMs,
9276
+ offset: options.offset,
9277
+ baseSha256: options.baseSha256,
9278
+ content: options.content,
9279
+ includeTranscripts: options.includeTranscripts !== false,
9280
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9281
+ readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9282
+ writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
9283
+ writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
9284
+ writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks)
9285
+ });
9286
+ return {
9287
+ namespace: resolvedNamespace,
9288
+ ...result
9289
+ };
9290
+ } catch (error) {
9291
+ const message = error instanceof Error ? error.message : String(error);
9292
+ if (message.startsWith("offline sync") || message.startsWith("path:") || message.startsWith("sourceId must ") || message.startsWith("sha256 must ") || message.startsWith("baseSha256 must ") || message.startsWith("bytes must ") || message.startsWith("mtimeMs must ") || message.startsWith("offset must ") || message.startsWith("content chunk ") || message === "content must be a Buffer") {
9293
+ throw new EngramAccessInputError(message);
9489
9294
  }
9295
+ throw error;
9490
9296
  }
9491
- if (memoryIds.length === 0) return { submitted: 0, matched: 0 };
9297
+ }
9298
+ async offlineSyncApply(options) {
9299
+ const resolvedNamespace = this.writableNamespaceFor(
9300
+ options.namespace,
9301
+ void 0,
9302
+ options.principal
9303
+ );
9492
9304
  const storage = await this.orchestrator.getStorage(resolvedNamespace);
9493
- const existingIds = await storage.filterExistingMemoryIds(memoryIds);
9494
- const matchedIds = memoryIds.filter((id) => existingIds.has(id));
9495
- if (matchedIds.length > 0) {
9496
- try {
9497
- this.orchestrator.trackMemoryAccess(matchedIds);
9498
- } catch {
9499
- log.debug("citation usage tracking: failed to record access for cited memories");
9305
+ try {
9306
+ const result = await applyOfflineSyncChangeset({
9307
+ root: storage.dir,
9308
+ changeset: options.changeset,
9309
+ returnCurrentFiles: options.returnCurrentFiles,
9310
+ readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9311
+ readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9312
+ writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
9313
+ deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
9314
+ });
9315
+ return {
9316
+ namespace: resolvedNamespace,
9317
+ ...result
9318
+ };
9319
+ } catch (error) {
9320
+ const message = error instanceof Error ? error.message : String(error);
9321
+ if (message.startsWith("offline sync")) {
9322
+ throw new EngramAccessInputError(message);
9500
9323
  }
9324
+ throw error;
9501
9325
  }
9502
- return { submitted: memoryIds.length, matched: matchedIds.length };
9503
9326
  }
9504
- // ── Operator Console state (issue #688 PR 2/3) ────────────────────────────
9327
+ // ── Dreams pipeline telemetry surfaces (issue #678 PR 3+4) ──────────────
9505
9328
  /**
9506
- * Gather a point-in-time `ConsoleStateSnapshot` from the orchestrator.
9329
+ * Return per-phase Dreams telemetry for the last N hours (default 24).
9330
+ */
9331
+ async dreamsStatus(options) {
9332
+ const { getDreamsStatus, normalizeDreamsStatusWindowHours } = await import("./dreams-ledger-6UODQOX6.js");
9333
+ let windowHours;
9334
+ try {
9335
+ windowHours = normalizeDreamsStatusWindowHours(options?.windowHours);
9336
+ } catch (error) {
9337
+ throw new EngramAccessInputError(error instanceof Error ? error.message : String(error));
9338
+ }
9339
+ const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
9340
+ const storage = await this.orchestrator.getStorage(resolvedNamespace);
9341
+ return getDreamsStatus(storage.dir, windowHours);
9342
+ }
9343
+ /**
9344
+ * Manually invoke a single Dreams phase pass (PR 4/4).
9507
9345
  *
9508
- * Principal-aware: `resolveReadableNamespace` enforces ACL before the
9509
- * snapshot is gathered, so callers cannot read a namespace they don't
9510
- * have read access to (CLAUDE.md rule 42). The resolved namespace's
9511
- * storage directory is forwarded as `config.memoryDir` so the ledger-
9512
- * tail reader in `gatherConsoleState` scans the correct namespace root
9513
- * rather than the global root. Read-only — never mutates orchestrator state.
9346
+ * Deep-sleep delegates to memory governance (shadow → dry-run, apply → live).
9347
+ * Light-sleep and REM scan the observation ledger and memory corpus
9348
+ * respectively, returning the same telemetry shape as a scheduled run.
9514
9349
  */
9515
- async consoleState(namespace, principal) {
9516
- const resolvedNamespace = this.resolveReadableNamespace(namespace, principal);
9350
+ async dreamsRun(options) {
9351
+ const { runDreamsPhase } = await import("./dreams-ledger-6UODQOX6.js");
9352
+ const validPhases = ["lightSleep", "rem", "deepSleep"];
9353
+ if (!validPhases.includes(options.phase)) {
9354
+ throw new EngramAccessInputError(
9355
+ `Invalid phase: ${String(options.phase)}. Must be one of: ${validPhases.join(", ")}`
9356
+ );
9357
+ }
9358
+ const deepSleep = this.orchestrator.config.dreamsPhases.deepSleep;
9359
+ if (options.phase === "deepSleep" && deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
9360
+ throw new EngramAccessInputError(
9361
+ "memory governance is disabled by dreams.phases.deepSleep.enabled=false"
9362
+ );
9363
+ }
9364
+ const dryRun = options.dryRun === true;
9365
+ const resolvedNamespace = this.writableNamespaceFor(
9366
+ options.namespace,
9367
+ void 0,
9368
+ options.authenticatedPrincipal
9369
+ );
9517
9370
  const storage = await this.orchestrator.getStorage(resolvedNamespace);
9518
- const { gatherConsoleState } = await import("./state-ITXJKXRE.js");
9519
- const orchestratorProxy = Object.create(this.orchestrator, {
9520
- config: {
9521
- value: { ...this.orchestrator.config, memoryDir: storage.dir },
9522
- enumerable: true,
9523
- configurable: true
9371
+ const memoryDir = storage.dir;
9372
+ const phaseRunner = dryRun || options.phase === "deepSleep" ? void 0 : async (_opts) => {
9373
+ if (_opts.phase === "lightSleep") {
9374
+ const result3 = await this.orchestrator.runLifecyclePolicyNow(storage);
9375
+ return {
9376
+ itemsProcessed: result3.memoriesAssessed,
9377
+ notes: `scored ${result3.memoriesAssessed} memories`
9378
+ };
9524
9379
  }
9380
+ const result2 = await this.orchestrator.runSemanticConsolidationNow({
9381
+ dryRun: false,
9382
+ storage
9383
+ });
9384
+ const itemsProcessed = result2.clusters.reduce(
9385
+ (sum, cluster) => sum + cluster.memories.length,
9386
+ 0
9387
+ );
9388
+ return {
9389
+ itemsProcessed,
9390
+ notes: `REM consolidation found ${result2.clustersFound} clusters`
9391
+ };
9392
+ };
9393
+ const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
9394
+ return this.orchestrator.runDeepSleepGovernanceNow({
9395
+ storage,
9396
+ dryRun: _opts.dryRun
9397
+ });
9398
+ } : void 0;
9399
+ const result = await runDreamsPhase(
9400
+ { memoryDir, phase: options.phase, dryRun },
9401
+ governanceRunner,
9402
+ phaseRunner
9403
+ );
9404
+ return {
9405
+ phase: result.phase,
9406
+ dryRun: result.dryRun,
9407
+ durationMs: result.durationMs,
9408
+ itemsProcessed: result.itemsProcessed,
9409
+ notes: result.notes
9410
+ };
9411
+ }
9412
+ // ---------------------------------------------------------------------------
9413
+ // Wearables (Limitless / Bee / Omi transcript ingestion)
9414
+ //
9415
+ // Thin delegations to the orchestrator-owned WearablesService — the
9416
+ // same instance behind the CLI, so HTTP/MCP callers observe identical
9417
+ // behavior and validation (renderer-sharing rule).
9418
+ // ---------------------------------------------------------------------------
9419
+ async wearablesStatus() {
9420
+ return this.orchestrator.getWearablesService().status();
9421
+ }
9422
+ async wearablesSync(request) {
9423
+ return this.orchestrator.getWearablesService().sync({
9424
+ source: request.source,
9425
+ date: request.date,
9426
+ days: request.days,
9427
+ forceMemories: request.forceMemories
9525
9428
  });
9526
- return gatherConsoleState(orchestratorProxy);
9527
9429
  }
9528
- // ── Peer Registry surfaces (issue #679 PR 4/5) ────────────────────────────
9430
+ async wearablesTranscriptDay(request) {
9431
+ return this.orchestrator.getWearablesService().dayTranscript(request.date, request.source);
9432
+ }
9433
+ async wearablesTranscriptSearch(request) {
9434
+ return this.orchestrator.getWearablesService().searchTranscripts(request.query, {
9435
+ source: request.source,
9436
+ from: request.from,
9437
+ to: request.to,
9438
+ limit: request.limit
9439
+ });
9440
+ }
9441
+ async wearablesTranscriptMemories(request) {
9442
+ return this.orchestrator.getWearablesService().transcriptMemories({
9443
+ source: request.source,
9444
+ date: request.date,
9445
+ limit: request.limit
9446
+ });
9447
+ }
9448
+ // ── Admin console surfaces (issue #1502) ────────────────────────────────
9449
+ //
9450
+ // Thin delegators. Every method threads `this.orchestrator` references
9451
+ // into the pure `admin/admin-surfaces.ts` functions so the dashboard
9452
+ // never re-resolves scope, re-derives promotion targets, or re-lists
9453
+ // namespaces.
9529
9454
  /**
9530
- * List all registered peers. Returns the array of `Peer` objects in
9531
- * deterministic alphabetical order (mirroring `listPeers` storage semantics).
9455
+ * Resolve an effective scope inspection for the console. Inputs are
9456
+ * redacted before return so an operator-supplied principal preview /
9457
+ * namespace override never echoes a pasted credential back.
9532
9458
  */
9533
- async peerList() {
9534
- const { listPeers } = await import("./peers/index.js");
9535
- const peers = await listPeers(this.orchestrator.config.memoryDir);
9536
- return { peers };
9459
+ async adminInspectScope(options) {
9460
+ const config = this.orchestrator.config;
9461
+ const codingContext = options.sessionKey ? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null : null;
9462
+ const inspection = inspectScope({
9463
+ config,
9464
+ sessionKey: options.sessionKey,
9465
+ namespace: options.namespace,
9466
+ principalOverride: options.principalOverride,
9467
+ codingContext,
9468
+ operation: options.operation
9469
+ });
9470
+ return redactSensitive(inspection);
9537
9471
  }
9538
9472
  /**
9539
- * Get a single peer by id. Returns `{ found: false }` when the peer does
9540
- * not exist rather than throwing, matching the `memoryGet` / `entityGet`
9541
- * pattern used throughout the service.
9473
+ * List configured + discovered namespaces from the catalog with the
9474
+ * admin filters the console exposes.
9542
9475
  */
9543
- async peerGet(peerId) {
9544
- const peers = await import("./peers/index.js");
9545
- const validateId = peers.assertValidPeerId;
9546
- try {
9547
- validateId(peerId);
9548
- } catch (err) {
9549
- throw new EngramAccessInputError(err.message);
9550
- }
9551
- const peer = await peers.readPeer(this.orchestrator.config.memoryDir, peerId);
9552
- if (!peer) return { found: false };
9553
- return { found: true, peer };
9476
+ async adminListNamespaces(filter) {
9477
+ const result = await listAdminNamespaces({
9478
+ catalog: this.orchestrator.namespaceCatalog,
9479
+ filter
9480
+ });
9481
+ return redactSensitive(result);
9554
9482
  }
9555
9483
  /**
9556
- * Upsert a peer. Writes `peers/{id}/identity.md`. On first write the
9557
- * `createdAt` timestamp is set to now; on subsequent writes only
9558
- * `displayName` and `notes` are mutated (kind and createdAt are immutable
9559
- * once set, per the storage contract).
9560
- *
9561
- * Returns `{ created: true }` on first write, `{ created: false }` on update.
9484
+ * Per-namespace maintenance + QMD health. QMD diagnostics are probed via
9485
+ * the orchestrator's search health accessor (one probe per namespace).
9562
9486
  */
9563
- async peerSet(input) {
9564
- const peers = await import("./peers/index.js");
9565
- const validateId = peers.assertValidPeerId;
9566
- const { id } = input;
9567
- try {
9568
- validateId(id);
9569
- } catch (err) {
9570
- throw new EngramAccessInputError(err.message);
9571
- }
9572
- const memoryDir = this.orchestrator.config.memoryDir;
9573
- const now = (/* @__PURE__ */ new Date()).toISOString();
9574
- const existing = await peers.readPeer(memoryDir, id);
9575
- const ALLOWED_KINDS = /* @__PURE__ */ new Set(["self", "human", "agent", "integration"]);
9576
- if (!existing) {
9577
- const kind = input.kind ?? "human";
9578
- if (!ALLOWED_KINDS.has(kind)) {
9579
- throw new EngramAccessInputError(
9580
- `peer kind must be one of ${[...ALLOWED_KINDS].join(", ")}`
9581
- );
9487
+ async adminMaintenanceHealth() {
9488
+ const report = await gatherMaintenanceHealth({
9489
+ catalog: this.orchestrator.namespaceCatalog,
9490
+ qmdHealthProvider: async (namespace) => {
9491
+ try {
9492
+ const health = await this.orchestrator.searchHealthForNamespace(namespace);
9493
+ return {
9494
+ namespace,
9495
+ collection: health.collection,
9496
+ available: health.available,
9497
+ collectionState: health.collectionState,
9498
+ debugStatus: health.debugStatus,
9499
+ installedVersion: health.installedVersion,
9500
+ supportedVersion: health.supportedVersion,
9501
+ supported: health.supported,
9502
+ upgradeAvailable: health.upgradeAvailable,
9503
+ daemonMode: health.daemonMode
9504
+ };
9505
+ } catch (err) {
9506
+ throw err;
9507
+ }
9582
9508
  }
9583
- const newPeer = {
9584
- id,
9585
- kind,
9586
- displayName: input.displayName ?? id,
9587
- createdAt: now,
9588
- updatedAt: now,
9589
- ...typeof input.notes === "string" ? { notes: input.notes } : {}
9590
- };
9591
- await peers.writePeer(memoryDir, newPeer);
9592
- return { ok: true, created: true, peer: newPeer };
9593
- }
9594
- const updated = {
9595
- id: existing.id,
9596
- kind: existing.kind,
9597
- createdAt: existing.createdAt,
9598
- updatedAt: now,
9599
- displayName: input.displayName !== void 0 ? input.displayName : existing.displayName,
9600
- ...input.notes !== void 0 ? { notes: input.notes } : existing.notes !== void 0 ? { notes: existing.notes } : {}
9601
- };
9602
- await peers.writePeer(memoryDir, updated);
9603
- return { ok: true, created: false, peer: updated };
9509
+ });
9510
+ return redactSensitive(report);
9604
9511
  }
9605
9512
  /**
9606
- * Delete a peer by removing `peers/{id}/identity.md`. If the file does not
9607
- * exist the call is a no-op (idempotent). The peer directory itself
9608
- * (`peers/{id}/`) is intentionally left in place — profile and interaction
9609
- * log data are not destroyed.
9513
+ * Dry-run transcript/session audit. Detects mixed `other/default` data
9514
+ * and legacy stranded directories. Never applies a migration.
9610
9515
  */
9611
- async peerDelete(peerId) {
9612
- const peers = await import("./peers/index.js");
9613
- const validateId = peers.assertValidPeerId;
9614
- try {
9615
- validateId(peerId);
9616
- } catch (err) {
9617
- throw new EngramAccessInputError(err.message);
9618
- }
9619
- const deleted = await peers.deletePeer(this.orchestrator.config.memoryDir, peerId);
9620
- return { ok: true, deleted };
9516
+ async adminTranscriptAudit() {
9517
+ const result = await auditTranscripts(this.orchestrator.config.memoryDir);
9518
+ return redactSensitive(result);
9621
9519
  }
9622
9520
  /**
9623
- * Destructively purge the entire peer directory for a given peerId
9624
- * `identity.md`, `profile.md`, `interactions.log.md`, and any other
9625
- * files in `peers/{id}/`. Requires `confirm: "yes"` to prevent
9626
- * accidental invocation.
9627
- *
9628
- * This is the DESTRUCTIVE counterpart to `peerDelete`, which only
9629
- * removes `identity.md`. All companion files are permanently removed.
9630
- *
9631
- * Returns `{ ok: true, purged: true }` when the directory existed and
9632
- * was removed; `{ ok: true, purged: false }` when the directory did
9633
- * not exist (idempotent no-op).
9521
+ * Manually promote a memory into one or more authorized targets. Requires
9522
+ * a non-empty reason (audit-logged). Reuses the scope-profile promotion
9523
+ * resolution and `canWriteNamespace` gate there is no dashboard-only
9524
+ * write path.
9634
9525
  */
9635
- async peerForget(peerId, opts) {
9636
- const peers = await import("./peers/index.js");
9637
- const validateId = peers.assertValidPeerId;
9638
- try {
9639
- validateId(peerId);
9640
- } catch (err) {
9641
- throw new EngramAccessInputError(err.message);
9642
- }
9643
- if (opts.confirm !== "yes") {
9644
- throw new EngramAccessInputError(
9645
- "peerForget requires confirm: 'yes' to prevent accidental data loss"
9526
+ async adminPromoteMemory(request) {
9527
+ const config = this.orchestrator.config;
9528
+ const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
9529
+ let sourceNamespace;
9530
+ if (request.namespace) {
9531
+ sourceNamespace = this.resolveReadableNamespace(
9532
+ request.namespace,
9533
+ request.principal
9534
+ );
9535
+ } else {
9536
+ const codingContext2 = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
9537
+ const plan = resolveScopePlan({
9538
+ config,
9539
+ sessionKey: request.sessionKey,
9540
+ principalOverride: request.principal,
9541
+ codingContext: codingContext2,
9542
+ namespacesEnabled
9543
+ });
9544
+ sourceNamespace = this.resolveReadableNamespace(
9545
+ plan.baseNamespace,
9546
+ request.principal
9646
9547
  );
9647
9548
  }
9648
- const result = await peers.forgetPeer(this.orchestrator.config.memoryDir, peerId, {
9649
- confirm: "yes"
9549
+ const codingContext = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
9550
+ const codingOverlay = codingContext ? resolveCodingNamespaceOverlay(
9551
+ codingContext,
9552
+ config.codingMode,
9553
+ config.defaultNamespace
9554
+ ) : null;
9555
+ const scopeProfilePlan = resolveScopeProfilePlan({
9556
+ config,
9557
+ principal: request.principal,
9558
+ codingContext,
9559
+ codingOverlay
9650
9560
  });
9651
- return { ok: true, purged: result.purged };
9652
- }
9653
- /**
9654
- * Get the evolving cognitive profile for a peer. Returns `{ found: false }`
9655
- * when no profile file exists yet (profile is written by the async reasoner,
9656
- * PR 2/5). The peer identity itself need not exist for a profile to exist,
9657
- * but in practice the reasoner only writes profiles for registered peers.
9658
- */
9659
- async peerProfileGet(peerId) {
9660
- const peers = await import("./peers/index.js");
9661
- const validateId = peers.assertValidPeerId;
9561
+ const storage = {
9562
+ readMemory: async (namespace, memoryId) => {
9563
+ const resolved = await this.orchestrator.getStorage(namespace);
9564
+ const memory = await resolved.getMemoryById(memoryId);
9565
+ if (!memory) return null;
9566
+ return {
9567
+ category: memory.frontmatter.category,
9568
+ content: memory.content,
9569
+ frontmatter: memory.frontmatter
9570
+ };
9571
+ },
9572
+ writePromotedMemory: async (namespace, memory) => {
9573
+ const resolved = await this.orchestrator.getStorage(namespace);
9574
+ const { id, tombstoneBlocked } = await resolved.writeMemory(
9575
+ memory.category,
9576
+ memory.content,
9577
+ {
9578
+ confidence: memory.confidence,
9579
+ tags: memory.tags,
9580
+ entityRef: memory.entityRef,
9581
+ source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
9582
+ lineage: memory.lineage,
9583
+ sourceMemoryId: memory.sourceMemoryId,
9584
+ actor: memory.actor,
9585
+ validAt: memory.validAt
9586
+ }
9587
+ );
9588
+ if (tombstoneBlocked) {
9589
+ throw new Error(
9590
+ "target namespace tombstone-blocked the promoted content (pending_review)"
9591
+ );
9592
+ }
9593
+ return id;
9594
+ }
9595
+ };
9662
9596
  try {
9663
- validateId(peerId);
9597
+ return await promoteMemory({
9598
+ config,
9599
+ sourceMemoryId: request.sourceMemoryId,
9600
+ sourceNamespace,
9601
+ principal: request.principal,
9602
+ targets: request.targets,
9603
+ reason: request.reason,
9604
+ actor: request.principal ?? "admin-console",
9605
+ storage,
9606
+ scopeProfilePlan
9607
+ });
9664
9608
  } catch (err) {
9665
- throw new EngramAccessInputError(err.message);
9609
+ if (err instanceof AdminPromotionError) {
9610
+ throw new EngramAccessInputError(err.message);
9611
+ }
9612
+ throw err;
9666
9613
  }
9667
- const profile = await peers.readPeerProfile(this.orchestrator.config.memoryDir, peerId);
9668
- if (!profile) return { found: false };
9669
- return { found: true, profile };
9670
- }
9671
- // ── Contradiction Review (issue #520) ──────────────────────────────────────
9672
- get memoryDir() {
9673
- return this.orchestrator.config.memoryDir;
9674
- }
9675
- /**
9676
- * Resolve the storage directory for a given namespace. Used by the SSE
9677
- * graph-event handler to subscribe to the correct per-namespace bus rather
9678
- * than the global root (CLAUDE.md rule 42 — read/write paths must resolve
9679
- * through the same namespace layer).
9680
- *
9681
- * `principal` must be the transport-bound request principal (from
9682
- * `resolveRequestPrincipal`). When namespaces are enabled, an absent
9683
- * principal causes `resolveReadableNamespace` to throw an auth error,
9684
- * matching the behaviour of every other authenticated read path.
9685
- *
9686
- * Falls back to `this.memoryDir` when namespaces are disabled or the
9687
- * namespace is absent, matching the behaviour of every other read path.
9688
- */
9689
- async getMemoryDirForNamespace(namespace, principal) {
9690
- const resolved = this.resolveReadableNamespace(namespace, principal);
9691
- const storage = await this.orchestrator.getStorage(resolved);
9692
- return storage.dir;
9693
- }
9694
- async getReadableStorageForNamespace(namespace, principal) {
9695
- const resolved = this.resolveReadableNamespace(namespace, principal);
9696
- const storage = await this.orchestrator.getStorage(resolved);
9697
- return { namespace: resolved, storage };
9698
9614
  }
9699
- async getWritableStorageForNamespace(namespace, principal) {
9700
- if (resolveNamespaceCapabilities(this.orchestrator.config).namespaces && !principal?.trim()) {
9701
- throw new EngramAccessInputError(
9702
- "authentication required: namespaces are enabled and no principal was supplied"
9703
- );
9615
+ };
9616
+
9617
+ // src/orchestration/qmd-result-resolver.ts
9618
+ import path8 from "path";
9619
+ function qmdCollectionPathParts(resultPath) {
9620
+ if (!resultPath || path8.isAbsolute(resultPath)) return null;
9621
+ const normalized = resultPath.replace(/\\/g, "/").replace(/^\/+/, "");
9622
+ const slashIndex = normalized.indexOf("/");
9623
+ if (slashIndex <= 0 || slashIndex >= normalized.length - 1) return null;
9624
+ const collection = normalized.slice(0, slashIndex);
9625
+ if (/^\d{4}-\d{2}-\d{2}$/.test(collection)) return null;
9626
+ return {
9627
+ collection,
9628
+ relativePath: normalized.slice(slashIndex + 1)
9629
+ };
9630
+ }
9631
+ function qmdResultPathCandidates(storageDir, resultPath) {
9632
+ const candidates = /* @__PURE__ */ new Set();
9633
+ const storageRoot = path8.resolve(storageDir);
9634
+ const addCandidate = (candidate) => {
9635
+ const resolved = path8.resolve(candidate);
9636
+ if (isPathInsideStorageRoot(storageRoot, resolved)) {
9637
+ candidates.add(resolved);
9704
9638
  }
9705
- const resolved = this.writableNamespaceFor(namespace, void 0, principal);
9706
- const storage = await this.orchestrator.getStorage(resolved);
9707
- return { namespace: resolved, storage };
9708
- }
9709
- get storageRef() {
9710
- return this.orchestrator.storage;
9711
- }
9712
- // #1522: recordCatalogWrite removed — catalog touch handled at the storage chokepoint.
9713
- get configRef() {
9714
- return this.orchestrator.config;
9715
- }
9716
- get localLlmRef() {
9717
- return this.orchestrator.localLlm ?? null;
9718
- }
9719
- get fallbackLlmRef() {
9720
- return this.orchestrator.fastGatewayLlm ?? null;
9639
+ };
9640
+ const addRelativeCandidates = (relativePath) => {
9641
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
9642
+ if (!normalized) return;
9643
+ addCandidate(path8.join(storageRoot, normalized));
9644
+ if (/^\d{4}-\d{2}-\d{2}\//.test(normalized)) {
9645
+ addCandidate(path8.join(storageRoot, "facts", normalized));
9646
+ }
9647
+ };
9648
+ if (path8.isAbsolute(resultPath)) {
9649
+ addCandidate(resultPath);
9650
+ } else {
9651
+ addRelativeCandidates(resultPath);
9721
9652
  }
9722
- get embeddingLookupFactoryRef() {
9723
- return (storage) => {
9724
- if (!resolveMemoryLifecycleCapabilities(this.orchestrator.config).embeddingFallback) return void 0;
9725
- return async (content, limit) => {
9653
+ return [...candidates];
9654
+ }
9655
+ function storageDirOrNull(storage) {
9656
+ const dir = storage.dir;
9657
+ return typeof dir === "string" && dir.length > 0 ? dir : null;
9658
+ }
9659
+ var QmdResultResolver = class {
9660
+ getConfig;
9661
+ storageFor;
9662
+ storageDirNamespace;
9663
+ qmdCollectionNamespaceFromPrefix;
9664
+ namespaceFromPath;
9665
+ constructor(options) {
9666
+ this.getConfig = options.getConfig;
9667
+ this.storageFor = options.storageFor;
9668
+ this.storageDirNamespace = options.storageDirNamespace;
9669
+ this.qmdCollectionNamespaceFromPrefix = options.qmdCollectionNamespaceFromPrefix;
9670
+ this.namespaceFromPath = options.namespaceFromPath;
9671
+ }
9672
+ async readQmdResultMemory(resultPath, fallbackStorage, recallNamespaces = []) {
9673
+ const parts = qmdCollectionPathParts(resultPath);
9674
+ const fallbackStorageDir = storageDirOrNull(fallbackStorage);
9675
+ const config = this.getConfig();
9676
+ const coldCollection = config.qmdColdCollection ?? "openclaw-engram-cold";
9677
+ if (parts && parts.collection === coldCollection) {
9678
+ const storages = [];
9679
+ const seenStorageDirs = /* @__PURE__ */ new Set();
9680
+ const addStorage = (storage) => {
9681
+ const storageDir = storageDirOrNull(storage);
9682
+ const storageKey = storageDir ? path8.resolve(storageDir) : `storage-without-dir-${storages.length}`;
9683
+ if (seenStorageDirs.has(storageKey)) return;
9684
+ seenStorageDirs.add(storageKey);
9685
+ storages.push(storage);
9686
+ };
9687
+ const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
9688
+ if (recallNamespaces.length === 0 || !resolveNamespaceCapabilities(config).namespaces || recallNamespaces.includes(fallbackNamespace)) {
9689
+ addStorage(fallbackStorage);
9690
+ }
9691
+ if (recallNamespaces.length > 0) {
9692
+ for (const namespace of recallNamespaces) {
9693
+ try {
9694
+ addStorage(await this.storageFor(namespace));
9695
+ } catch (err) {
9696
+ log.debug("qmd cold result namespace storage lookup skipped", {
9697
+ path: resultPath,
9698
+ namespace,
9699
+ error: err.message
9700
+ });
9701
+ }
9702
+ }
9703
+ }
9704
+ for (const storage of storages) {
9705
+ const storageDir = storageDirOrNull(storage);
9706
+ if (!storageDir) {
9707
+ const memory = await storage.readMemoryByPath(resultPath);
9708
+ if (memory) return memory;
9709
+ continue;
9710
+ }
9726
9711
  try {
9727
- return await this.orchestrator.semanticDedupLookup(content, limit, storage);
9728
- } catch {
9729
- return [];
9712
+ const coldRoot = path8.join(storageDir, "cold");
9713
+ for (const candidate of qmdResultPathCandidates(
9714
+ coldRoot,
9715
+ parts.relativePath
9716
+ )) {
9717
+ const memory = await storage.readMemoryByPath(candidate);
9718
+ if (memory) return memory;
9719
+ }
9720
+ } catch (err) {
9721
+ if (err instanceof SecureStoreLockedError) throw err;
9722
+ log.debug("qmd cold result path lookup failed open", {
9723
+ path: resultPath,
9724
+ collection: coldCollection,
9725
+ error: err.message
9726
+ });
9727
+ }
9728
+ }
9729
+ return null;
9730
+ }
9731
+ const collectionNamespace = parts ? this.qmdCollectionNamespaceFromPrefix(parts.collection) : null;
9732
+ if (parts && collectionNamespace) {
9733
+ try {
9734
+ const collectionStorage = await this.storageFor(collectionNamespace);
9735
+ for (const candidate of qmdResultPathCandidates(
9736
+ collectionStorage.dir,
9737
+ parts.relativePath
9738
+ )) {
9739
+ const memory = await collectionStorage.readMemoryByPath(candidate);
9740
+ if (memory) return memory;
9730
9741
  }
9731
- };
9732
- };
9733
- }
9734
- /**
9735
- * Import a capsule archive into the orchestrator's memory directory.
9736
- *
9737
- * Delegates directly to the standalone {@link importCapsuleFn} function.
9738
- * The `root` parameter defaults to the orchestrator's `memoryDir` when
9739
- * omitted, so callers that only have access to the service do not need to
9740
- * thread the config value through.
9741
- *
9742
- * `versioning` defaults to the orchestrator's page-versioning config so
9743
- * `mode: "overwrite"` automatically snapshots prior content without the
9744
- * caller having to construct the config object.
9745
- */
9746
- async capsuleImport(opts) {
9747
- const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...importOptions } = opts;
9748
- const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
9749
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9750
- const root = explicitRoot ?? storage.dir;
9751
- const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
9752
- const versioning = importOptions.versioning ?? {
9753
- enabled: resolveRecallAuxiliaryCapabilities(this.orchestrator.config).versioning,
9754
- maxVersionsPerPage: this.orchestrator.config.versioningMaxPerPage,
9755
- sidecarDir: this.orchestrator.config.versioningSidecarDir
9756
- };
9757
- await this.validateCapsuleImportArchivePath(importOptions.archivePath);
9758
- try {
9759
- return await importCapsule({ ...importOptions, root, memoryDir, versioning });
9760
- } catch (err) {
9761
- const message = err instanceof Error ? err.message : String(err);
9762
- if (this.isCapsuleImportArchiveInputError(err, message)) {
9763
- throw new EngramAccessInputError(`capsule import failed: ${message}`);
9742
+ return null;
9743
+ } catch (err) {
9744
+ if (err instanceof SecureStoreLockedError) throw err;
9745
+ log.debug("qmd result namespace path lookup failed open", {
9746
+ path: resultPath,
9747
+ namespace: collectionNamespace,
9748
+ error: err.message
9749
+ });
9750
+ return null;
9764
9751
  }
9765
- throw err;
9766
9752
  }
9767
- }
9768
- async validateCapsuleImportArchivePath(archivePath) {
9769
- let archiveStat;
9770
- try {
9771
- archiveStat = await stat2(archivePath);
9772
- } catch (err) {
9773
- if (!this.isCapsuleImportPathInputFsError(err)) throw err;
9774
- const message = err instanceof Error ? err.message : String(err);
9775
- throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
9753
+ if (path8.isAbsolute(resultPath)) {
9754
+ if (!fallbackStorageDir) {
9755
+ return await fallbackStorage.readMemoryByPath(resultPath);
9756
+ }
9757
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
9758
+ resultPath,
9759
+ fallbackStorage,
9760
+ recallNamespaces
9761
+ );
9762
+ if (!ownerStorage) return null;
9763
+ for (const candidate of qmdResultPathCandidates(
9764
+ ownerStorage.dir,
9765
+ resultPath
9766
+ )) {
9767
+ const memory = await ownerStorage.storage.readMemoryByPath(candidate);
9768
+ if (memory) return memory;
9769
+ }
9770
+ return null;
9776
9771
  }
9777
- if (!archiveStat.isFile()) {
9778
- throw new EngramAccessInputError("capsule import failed: archivePath must point to a file");
9772
+ if (!fallbackStorageDir) {
9773
+ return await fallbackStorage.readMemoryByPath(resultPath);
9779
9774
  }
9780
- try {
9781
- await nodeFs.access(archivePath, fsConstants.R_OK);
9782
- } catch (err) {
9783
- if (!this.isCapsuleImportPathInputFsError(err)) throw err;
9784
- const message = err instanceof Error ? err.message : String(err);
9785
- throw new EngramAccessInputError(`capsule import failed: archive is not readable: ${message}`);
9775
+ for (const candidate of qmdResultPathCandidates(
9776
+ fallbackStorageDir,
9777
+ resultPath
9778
+ )) {
9779
+ const memory = await fallbackStorage.readMemoryByPath(candidate);
9780
+ if (memory) return memory;
9786
9781
  }
9782
+ return null;
9787
9783
  }
9788
- isCapsuleImportPathInputFsError(err) {
9789
- const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9790
- return code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP";
9784
+ async resolveColdQmdResultForRecall(result, fallbackStorage, recallNamespaces = []) {
9785
+ const memory = await this.readQmdResultMemory(
9786
+ result.path,
9787
+ fallbackStorage,
9788
+ recallNamespaces
9789
+ );
9790
+ if (!memory) return null;
9791
+ let ownerNamespace = null;
9792
+ if (path8.isAbsolute(memory.path)) {
9793
+ const ownerStorage = await this.storageForAbsoluteQmdResultPath(
9794
+ memory.path,
9795
+ fallbackStorage,
9796
+ recallNamespaces
9797
+ );
9798
+ ownerNamespace = ownerStorage?.namespace ?? null;
9799
+ if (!ownerNamespace && resolveNamespaceCapabilities(this.getConfig()).namespaces) return null;
9800
+ }
9801
+ ownerNamespace ??= this.namespaceFromPath(memory.path);
9802
+ if (recallNamespaces.length > 0 && !recallNamespaces.includes(ownerNamespace)) {
9803
+ return null;
9804
+ }
9805
+ return {
9806
+ namespace: ownerNamespace,
9807
+ result: {
9808
+ ...result,
9809
+ docid: result.docid || memory.frontmatter.id,
9810
+ path: memory.path,
9811
+ snippet: result.snippet || memory.content.slice(0, 400)
9812
+ }
9813
+ };
9791
9814
  }
9792
- isCapsuleImportArchiveInputError(err, message) {
9793
- if (err instanceof ZodError) return true;
9794
- const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9795
- if (typeof code === "string" && code.startsWith("Z_")) return true;
9796
- return message.startsWith("importCapsule: archive") || message.startsWith("importCapsule: bundle") || message.startsWith("importCapsule: manifest") || message.startsWith("importCapsule: record") || /incorrect header check|invalid stored block lengths|not in gzip format|unexpected end of file/i.test(message);
9815
+ async storageForAbsoluteQmdResultPath(resultPath, fallbackStorage, recallNamespaces = []) {
9816
+ const resolvedPath = path8.resolve(resultPath);
9817
+ const config = this.getConfig();
9818
+ const memoryRoot = path8.resolve(config.memoryDir);
9819
+ const namespacesRoot = path8.join(memoryRoot, "namespaces");
9820
+ const fallbackStorageDir = storageDirOrNull(fallbackStorage);
9821
+ const matches = [];
9822
+ const seenDirs = /* @__PURE__ */ new Set();
9823
+ const maybeAddStorage = (storage, namespace) => {
9824
+ const storageDir = storageDirOrNull(storage);
9825
+ if (!storageDir) return;
9826
+ const candidateRoot = path8.resolve(storageDir);
9827
+ if (seenDirs.has(candidateRoot)) return;
9828
+ if (!isPathInsideStorageRoot(candidateRoot, resolvedPath)) return;
9829
+ if (candidateRoot === memoryRoot && isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
9830
+ return;
9831
+ }
9832
+ seenDirs.add(candidateRoot);
9833
+ matches.push({ storage, dir: candidateRoot, namespace });
9834
+ };
9835
+ const fallbackNamespace = fallbackStorageDir !== null ? this.storageDirNamespace(fallbackStorageDir) : config.defaultNamespace;
9836
+ maybeAddStorage(fallbackStorage, fallbackNamespace);
9837
+ const candidateNamespaces = /* @__PURE__ */ new Set();
9838
+ candidateNamespaces.add(config.defaultNamespace);
9839
+ candidateNamespaces.add(config.sharedNamespace);
9840
+ for (const ns of recallNamespaces) {
9841
+ candidateNamespaces.add(ns);
9842
+ }
9843
+ if (isPathInsideStorageRoot(namespacesRoot, resolvedPath)) {
9844
+ const relativeToNamespaces = path8.relative(namespacesRoot, resolvedPath);
9845
+ const [namespaceSegment] = relativeToNamespaces.split(/[\\/]/);
9846
+ if (namespaceSegment) {
9847
+ candidateNamespaces.add(
9848
+ namespaceIdentityFromToken(namespaceSegment) ?? namespaceSegment
9849
+ );
9850
+ }
9851
+ }
9852
+ for (const policy of config.namespacePolicies ?? []) {
9853
+ candidateNamespaces.add(policy.name);
9854
+ }
9855
+ for (const ns of candidateNamespaces) {
9856
+ if (!ns) continue;
9857
+ try {
9858
+ maybeAddStorage(await this.storageFor(ns), ns);
9859
+ } catch {
9860
+ continue;
9861
+ }
9862
+ }
9863
+ matches.sort((a, b) => b.dir.length - a.dir.length);
9864
+ return matches[0] ?? null;
9797
9865
  }
9798
- /**
9799
- * Export a capsule archive from the orchestrator's memory directory.
9800
- *
9801
- * HTTP and future MCP surfaces use this rather than calling the transfer
9802
- * helper directly so namespace ACL checks stay consistent with the archive
9803
- * write side effect. The exporter still owns archive construction and
9804
- * validation.
9805
- */
9806
- async capsuleExport(opts) {
9807
- const { namespace, principal, root: explicitRoot, memoryDir: explicitMemoryDir, ...exportOptions } = opts;
9808
- const resolvedNamespace = this.writableNamespaceFor(namespace, void 0, principal);
9809
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9810
- const root = explicitRoot ?? storage.dir;
9811
- const memoryDir = explicitMemoryDir ?? this.orchestrator.config.memoryDir;
9812
- const pluginVersion = exportOptions.pluginVersion ?? await getPackageVersion();
9813
- return exportCapsule({
9814
- ...exportOptions,
9815
- pluginVersion,
9816
- root,
9817
- memoryDir: exportOptions.encrypt === true ? memoryDir : void 0
9818
- });
9866
+ };
9867
+
9868
+ // src/access-recall-surface.ts
9869
+ var AccessRecallSurface = class {
9870
+ constructor(deps) {
9871
+ this.deps = deps;
9819
9872
  }
9820
- /**
9821
- * List capsule archives in the namespace-scoped capsule store.
9822
- *
9823
- * MCP uses this access-layer method instead of reading arbitrary paths so
9824
- * capsule discovery remains bound to the same namespace ACLs as export and
9825
- * import.
9826
- */
9827
- async capsuleList(options) {
9828
- const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
9829
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9830
- const capsulesDir = defaultCapsulesDir(storage.dir);
9831
- let dirEntries;
9832
- try {
9833
- const capsulesDirStat = await nodeFs.lstat(capsulesDir);
9834
- if (capsulesDirStat.isSymbolicLink()) {
9835
- throw new EngramAccessInputError("capsule list failed: capsule store directory must not be a symlink");
9873
+ deps;
9874
+ async buildRecallResponseFromXraySnapshot(options) {
9875
+ const memoryIds = options.snapshot.results.map((result) => result.memoryId);
9876
+ const resultPaths = options.snapshot.results.map((result) => result.path);
9877
+ const namespace = options.snapshot.namespace ? this.deps.resolveNamespace(options.snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
9878
+ const sourcesUsed = Array.from(
9879
+ new Set(options.snapshot.results.map((result) => result.servedBy))
9880
+ );
9881
+ const snapshotForSerialization = {
9882
+ sessionKey: options.sessionKey ?? "",
9883
+ recordedAt: new Date(options.snapshot.capturedAt).toISOString(),
9884
+ queryHash: createHash5("sha256").update(options.query).digest("hex"),
9885
+ queryLen: options.query.length,
9886
+ memoryIds,
9887
+ namespace,
9888
+ recallNamespaces: [namespace],
9889
+ traceId: options.snapshot.traceId,
9890
+ plannerMode: options.normalizedMode,
9891
+ requestedMode: options.requestedMode && options.requestedMode !== "auto" ? options.requestedMode : void 0,
9892
+ sourcesUsed,
9893
+ budgetsApplied: {
9894
+ appliedTopK: memoryIds.length,
9895
+ recallBudgetChars: options.snapshot.budget.chars,
9896
+ maxMemoryTokens: this.deps.orchestrator.config.maxMemoryTokens,
9897
+ finalContextChars: options.snapshot.budget.used
9898
+ },
9899
+ latencyMs: Date.now() - options.startedAt,
9900
+ resultPaths
9901
+ };
9902
+ const results = await this.deps.serializeRecallResults(
9903
+ snapshotForSerialization,
9904
+ options.disclosure,
9905
+ {
9906
+ query: options.query,
9907
+ ...options.sessionKey ? { sessionKey: options.sessionKey } : {},
9908
+ ...options.rawExcerptNamespace ? { rawExcerptNamespace: options.rawExcerptNamespace } : {},
9909
+ ...options.rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: options.rawExcerptSessionIds } : {},
9910
+ ...options.rawExcerptsSuppressed ? { rawExcerptsSuppressed: options.rawExcerptsSuppressed } : {}
9836
9911
  }
9837
- if (!capsulesDirStat.isDirectory()) {
9838
- throw new EngramAccessInputError("capsule list failed: capsule store path must be a directory");
9912
+ );
9913
+ const context = results.map((result) => {
9914
+ const content = typeof result.content === "string" && result.content.length > 0 ? result.content : "";
9915
+ return content || result.preview;
9916
+ }).filter((text) => text.length > 0).join("\n\n");
9917
+ return {
9918
+ query: options.query,
9919
+ ...options.sessionKey ? { sessionKey: options.sessionKey } : {},
9920
+ namespace,
9921
+ context,
9922
+ count: memoryIds.length,
9923
+ memoryIds,
9924
+ results,
9925
+ recordedAt: snapshotForSerialization.recordedAt,
9926
+ traceId: options.snapshot.traceId,
9927
+ plannerMode: options.normalizedMode,
9928
+ fallbackUsed: sourcesUsed.some((source) => source !== "hybrid"),
9929
+ sourcesUsed,
9930
+ disclosure: options.disclosure,
9931
+ budgetsApplied: snapshotForSerialization.budgetsApplied,
9932
+ latencyMs: snapshotForSerialization.latencyMs
9933
+ };
9934
+ }
9935
+ async serializeRecallResults(snapshot, disclosure, rawContext = null) {
9936
+ if (!snapshot) return [];
9937
+ const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
9938
+ const storage = await this.deps.orchestrator.getStorage(namespace);
9939
+ const storageDir = storage.dir;
9940
+ const recallNamespaces = Array.from(
9941
+ new Set(
9942
+ [
9943
+ namespace,
9944
+ ...Array.isArray(snapshot.recallNamespaces) ? snapshot.recallNamespaces.map((ns) => this.deps.resolveNamespace(ns)) : [],
9945
+ this.deps.orchestrator.config.defaultNamespace,
9946
+ this.deps.orchestrator.config.sharedNamespace,
9947
+ ...(this.deps.orchestrator.config.namespacePolicies ?? []).map((p) => p.name)
9948
+ ].filter((ns) => typeof ns === "string" && ns.length > 0)
9949
+ )
9950
+ );
9951
+ const results = [];
9952
+ const seen = /* @__PURE__ */ new Set();
9953
+ const collectionNamespaceFromPrefix = (collectionPrefix) => {
9954
+ const baseCollection = this.deps.orchestrator.config.qmdCollection;
9955
+ if (collectionPrefix === baseCollection) return this.deps.orchestrator.config.defaultNamespace;
9956
+ const namespaceSuffix = collectionPrefix.startsWith(`${baseCollection}--`) ? collectionPrefix.slice(baseCollection.length + 2) : "";
9957
+ if (!namespaceSuffix) return null;
9958
+ const decoded = namespaceIdentityFromToken(namespaceSuffix);
9959
+ if (decoded !== null) return decoded || this.deps.orchestrator.config.defaultNamespace;
9960
+ if (namespaceSuffix.startsWith("ns--")) {
9961
+ const legacyNamespace = namespaceSuffix.slice("ns--".length).trim();
9962
+ return legacyNamespace || null;
9839
9963
  }
9840
- dirEntries = await nodeFs.readdir(capsulesDir, { withFileTypes: true });
9841
- } catch (err) {
9842
- const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9843
- if (code === "ENOENT") {
9844
- return { namespace: resolvedNamespace, capsulesDir, capsules: [] };
9964
+ return null;
9965
+ };
9966
+ const readResultPath = async (memoryPath) => {
9967
+ const parts = qmdCollectionPathParts(memoryPath);
9968
+ const coldCollection = this.deps.orchestrator.config.qmdColdCollection ?? "openclaw-engram-cold";
9969
+ if (parts && parts.collection === coldCollection) {
9970
+ const storages = [];
9971
+ const seenStorageDirs = /* @__PURE__ */ new Set();
9972
+ const addStorage = (candidateStorage) => {
9973
+ const candidateDir = nodePath2.resolve(candidateStorage.dir);
9974
+ if (seenStorageDirs.has(candidateDir)) return;
9975
+ seenStorageDirs.add(candidateDir);
9976
+ storages.push({ storage: candidateStorage, dir: candidateDir });
9977
+ };
9978
+ addStorage(storage);
9979
+ for (const recallNamespace of recallNamespaces) {
9980
+ try {
9981
+ addStorage(await this.deps.orchestrator.getStorage(recallNamespace));
9982
+ } catch {
9983
+ continue;
9984
+ }
9985
+ }
9986
+ for (const candidateStorage of storages) {
9987
+ try {
9988
+ const coldRoot = nodePath2.join(candidateStorage.dir, "cold");
9989
+ for (const candidatePath of qmdResultPathCandidates(
9990
+ coldRoot,
9991
+ parts.relativePath
9992
+ )) {
9993
+ const memory = await candidateStorage.storage.readMemoryByPath(candidatePath);
9994
+ if (memory) return { memory, baseDir: candidateStorage.dir };
9995
+ }
9996
+ } catch (err) {
9997
+ if (err instanceof SecureStoreLockedError) throw err;
9998
+ continue;
9999
+ }
10000
+ }
10001
+ return null;
9845
10002
  }
9846
- throw err;
9847
- }
9848
- const archiveNames = dirEntries.filter(
9849
- (entry) => entry.isFile() && (entry.name.endsWith(".capsule.json.gz") || entry.name.endsWith(".capsule.json.gz.enc"))
9850
- ).map((entry) => entry.name).sort();
9851
- const capsules = [];
9852
- for (const archiveName of archiveNames) {
9853
- const archivePath = nodePath.join(capsulesDir, archiveName);
9854
- const id = archiveName.replace(/\.capsule\.json\.gz\.enc$/, "").replace(/\.capsule\.json\.gz$/, "");
9855
- const manifestPath = nodePath.join(capsulesDir, `${id}.manifest.json`);
9856
- let createdAt = null;
9857
- let pluginVersion = null;
9858
- let fileCount = null;
9859
- let description = null;
9860
- let manifestPathOrNull = manifestPath;
9861
- try {
9862
- const manifestStat = await nodeFs.lstat(manifestPath);
9863
- if (manifestStat.isSymbolicLink() || !manifestStat.isFile()) {
9864
- capsules.push({
9865
- id,
9866
- archivePath,
9867
- manifestPath: manifestPathOrNull,
9868
- createdAt,
9869
- pluginVersion,
9870
- fileCount,
9871
- description
9872
- });
9873
- continue;
10003
+ const collectionNamespace = parts ? collectionNamespaceFromPrefix(parts.collection) : null;
10004
+ if (parts && collectionNamespace) {
10005
+ try {
10006
+ const collectionStorage = await this.deps.orchestrator.getStorage(collectionNamespace);
10007
+ for (const candidate of qmdResultPathCandidates(
10008
+ collectionStorage.dir,
10009
+ parts.relativePath
10010
+ )) {
10011
+ const memory = await collectionStorage.readMemoryByPath(candidate);
10012
+ if (memory) return { memory, baseDir: collectionStorage.dir };
10013
+ }
10014
+ return null;
10015
+ } catch (err) {
10016
+ if (err instanceof SecureStoreLockedError) throw err;
10017
+ return null;
9874
10018
  }
9875
- const raw = await nodeFs.readFile(manifestPath, "utf-8");
9876
- const sidecar = JSON.parse(raw);
9877
- createdAt = typeof sidecar.createdAt === "string" ? sidecar.createdAt : null;
9878
- pluginVersion = typeof sidecar.pluginVersion === "string" ? sidecar.pluginVersion : null;
9879
- fileCount = Array.isArray(sidecar.files) ? sidecar.files.length : null;
9880
- const capsule = sidecar.capsule;
9881
- description = capsule && typeof capsule.description === "string" ? capsule.description : null;
9882
- } catch (err) {
9883
- const code = typeof err === "object" && err !== null && "code" in err ? err.code : void 0;
9884
- if (code === "ENOENT") {
9885
- manifestPathOrNull = null;
10019
+ }
10020
+ if (nodePath2.isAbsolute(memoryPath)) {
10021
+ const ownerStorage = await this.deps.storageForAbsoluteRecallPath(
10022
+ memoryPath,
10023
+ namespace,
10024
+ recallNamespaces
10025
+ );
10026
+ if (!ownerStorage) return null;
10027
+ for (const candidate of qmdResultPathCandidates(
10028
+ ownerStorage.dir,
10029
+ memoryPath
10030
+ )) {
10031
+ const memory = await ownerStorage.storage.readMemoryByPath(candidate);
10032
+ if (memory) return { memory, baseDir: ownerStorage.dir };
9886
10033
  }
10034
+ return null;
9887
10035
  }
9888
- capsules.push({
9889
- id,
9890
- archivePath,
9891
- manifestPath: manifestPathOrNull,
9892
- createdAt,
9893
- pluginVersion,
9894
- fileCount,
9895
- description
9896
- });
9897
- }
9898
- return { namespace: resolvedNamespace, capsulesDir, capsules };
9899
- }
9900
- /**
9901
- * Operator-configured offline-sync excludes (#1786), compiled once per
9902
- * service instance. Applied to push-side (snapshot/read) enumeration
9903
- * only apply-side stays permissive so remote-authoritative runtime
9904
- * files keep flowing in.
9905
- */
9906
- _offlineSyncUserExcludes = null;
9907
- get offlineSyncUserExcludes() {
9908
- if (!this._offlineSyncUserExcludes) {
9909
- this._offlineSyncUserExcludes = compileOfflineSyncExcludeGlobs(
9910
- this.orchestrator.config.offlineSyncExcludes ?? []
10036
+ for (const candidate of qmdResultPathCandidates(storageDir, memoryPath)) {
10037
+ const memory = await storage.readMemoryByPath(candidate);
10038
+ if (memory) return { memory, baseDir: storageDir };
10039
+ }
10040
+ return null;
10041
+ };
10042
+ const rawExcerptsResult = rawContext?.rawExcerptsSuppressed === true ? (
10043
+ // Implicit raw recall with NO readable LCM namespace (#1505 thread
10044
+ // NBHWz): emit empty excerpts rather than falling back to the
10045
+ // write/overlay `namespace` the read gate excludes.
10046
+ []
10047
+ ) : await this.deps.fetchRawExcerpts(
10048
+ disclosure,
10049
+ rawContext ? {
10050
+ query: rawContext.query,
10051
+ ...rawContext.sessionKey ? { sessionKey: rawContext.sessionKey } : {},
10052
+ namespace: rawContext.rawExcerptNamespace ?? namespace,
10053
+ ...rawContext.rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawContext.rawExcerptSessionIds } : {}
10054
+ } : null
10055
+ );
10056
+ const rawExcerpts = rawExcerptsResult ?? void 0;
10057
+ for (const memoryPath of snapshot.resultPaths ?? []) {
10058
+ if (!memoryPath || seen.has(memoryPath)) continue;
10059
+ const resolved = await readResultPath(memoryPath);
10060
+ if (!resolved) continue;
10061
+ const { memory, baseDir } = resolved;
10062
+ seen.add(memoryPath);
10063
+ results.push(
10064
+ this.deps.serializeMemorySummary(
10065
+ memory,
10066
+ baseDir,
10067
+ disclosure,
10068
+ // Attach the (possibly empty) raw excerpts to the first raw
10069
+ // result; subsequent results do not duplicate the array.
10070
+ results.length === 0 ? rawExcerpts : void 0
10071
+ )
9911
10072
  );
9912
10073
  }
9913
- return this._offlineSyncUserExcludes;
9914
- }
9915
- async offlineSyncSnapshot(options = {}) {
9916
- const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9917
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9918
- const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9919
- const snapshotBuilder = options.includeContent === false && options.baseFiles && options.baseFiles.length > 0 ? buildOfflineSyncSnapshotFromBase : buildOfflineSyncSnapshot;
9920
- const snapshot = await snapshotBuilder({
9921
- root: storage.dir,
9922
- sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9923
- ...options.baseFiles && options.baseFiles.length > 0 ? { baseFiles: options.baseFiles } : {},
9924
- includeContent: options.includeContent !== false,
9925
- includeTranscripts: options.includeTranscripts !== false,
9926
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9927
- readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9928
- signal: options.signal,
9929
- userExcludeRegexps: this.offlineSyncUserExcludes
9930
- });
9931
- return {
9932
- namespace: resolvedNamespace,
9933
- ...snapshot
9934
- };
9935
- }
9936
- async offlineSyncSnapshotStream(options = {}) {
9937
- const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9938
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9939
- const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9940
- return {
9941
- namespace: resolvedNamespace,
9942
- format: OFFLINE_SYNC_SNAPSHOT_FORMAT,
9943
- schemaVersion: 1,
9944
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
9945
- sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9946
- includeTranscripts: options.includeTranscripts !== false,
9947
- files: iterateOfflineSyncSnapshotFileRecords({
9948
- root: storage.dir,
9949
- includeContent: options.includeContent === true,
9950
- includeTranscripts: options.includeTranscripts !== false,
9951
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9952
- readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
9953
- signal: options.signal,
9954
- userExcludeRegexps: this.offlineSyncUserExcludes
9955
- })
9956
- };
9957
- }
9958
- async offlineSyncFiles(options) {
9959
- const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9960
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
9961
- const storageHash = createHash4("sha256").update(storage.dir).digest("hex").slice(0, 16);
9962
- try {
9963
- const snapshot = await buildOfflineSyncSnapshotForPaths({
9964
- root: storage.dir,
9965
- sourceId: `remnic:${resolvedNamespace}:${storageHash}`,
9966
- paths: options.paths,
9967
- includeContent: true,
9968
- includeTranscripts: options.includeTranscripts !== false,
9969
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9970
- userExcludeRegexps: this.offlineSyncUserExcludes
9971
- });
9972
- return {
9973
- namespace: resolvedNamespace,
9974
- ...snapshot
9975
- };
9976
- } catch (error) {
9977
- const message = error instanceof Error ? error.message : String(error);
9978
- if (message.startsWith("paths[]:") || message.startsWith("buildOfflineSyncSnapshotForPaths: record path ") || message.startsWith("offline sync snapshot path is excluded:")) {
9979
- throw new EngramAccessInputError(message);
9980
- }
9981
- throw error;
10074
+ if (results.length > 0) return results;
10075
+ for (const memoryId of snapshot.memoryIds) {
10076
+ const memory = await storage.getMemoryById(memoryId);
10077
+ if (!memory || seen.has(memory.path)) continue;
10078
+ seen.add(memory.path);
10079
+ results.push(
10080
+ this.deps.serializeMemorySummary(
10081
+ memory,
10082
+ storageDir,
10083
+ disclosure,
10084
+ results.length === 0 ? rawExcerpts : void 0
10085
+ )
10086
+ );
9982
10087
  }
10088
+ return results;
9983
10089
  }
9984
- async offlineSyncFileContent(options) {
9985
- const resolvedNamespace = this.resolveReadableNamespace(options.namespace, options.principal);
9986
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
10090
+ /**
10091
+ * Fetch raw transcript excerpts from the LCM archive for `disclosure ===
10092
+ * "raw"` recalls (issue #677 PR 2/4). Returns `null` for non-raw recall
10093
+ * depths, an empty array when LCM is disabled / not initialized / has no
10094
+ * matches, and an array of LCM-side excerpts otherwise. Errors are
10095
+ * swallowed and treated as "no excerpts" so a failing LCM never breaks
10096
+ * the recall response.
10097
+ *
10098
+ * Namespace handling: LCM archival prefixes non-default-namespace
10099
+ * sessions with `${namespace}:${sessionKey}` (see `observe()` around
10100
+ * line 2498), so the lookup must mirror that prefix or raw recalls in
10101
+ * non-default namespaces miss their own excerpts.
10102
+ */
10103
+ async fetchRawExcerpts(disclosure, context) {
10104
+ if (disclosure !== "raw") return null;
10105
+ if (!context || !context.query) return [];
10106
+ if (!context.sessionKey) return [];
10107
+ const lcm = this.deps.orchestrator.lcmEngine;
10108
+ if (!lcm || !lcm.enabled) return [];
9987
10109
  try {
9988
- const chunk = await readOfflineSyncFileContentChunk({
9989
- root: storage.dir,
9990
- path: options.path,
9991
- offset: options.offset,
9992
- length: options.length,
9993
- includeTranscripts: options.includeTranscripts !== false,
9994
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
9995
- userExcludeRegexps: this.offlineSyncUserExcludes
9996
- });
9997
- return {
9998
- namespace: resolvedNamespace,
9999
- ...chunk
10000
- };
10001
- } catch (error) {
10002
- const message = error instanceof Error ? error.message : String(error);
10003
- if (message.startsWith("path:") || message.startsWith("offset ") || message.startsWith("offset must ") || message.startsWith("length ") || message.startsWith("offline sync file content ")) {
10004
- throw new EngramAccessInputError(message);
10110
+ const legacyKey = context.namespace && context.namespace !== this.deps.orchestrator.config.defaultNamespace ? `${context.namespace}:${context.sessionKey}` : context.sessionKey;
10111
+ const lcmSessionIds = context.lcmSessionIds !== void 0 ? context.lcmSessionIds : [legacyKey];
10112
+ const limit = 5;
10113
+ const seenRows = /* @__PURE__ */ new Set();
10114
+ const excerpts = [];
10115
+ const settledRows = await Promise.allSettled(
10116
+ lcmSessionIds.map(
10117
+ async (lcmSessionKey) => lcm.searchContextFull(context.query, limit, lcmSessionKey)
10118
+ )
10119
+ );
10120
+ for (const result of settledRows) {
10121
+ if (excerpts.length >= limit) break;
10122
+ if (result.status !== "fulfilled") continue;
10123
+ for (const r of result.value) {
10124
+ const dedupeKey = `${r.session_id} ${r.turn_index}`;
10125
+ if (seenRows.has(dedupeKey)) continue;
10126
+ seenRows.add(dedupeKey);
10127
+ excerpts.push({
10128
+ turnIndex: r.turn_index,
10129
+ role: r.role,
10130
+ content: r.content,
10131
+ sessionId: r.session_id
10132
+ });
10133
+ if (excerpts.length >= limit) break;
10134
+ }
10005
10135
  }
10006
- throw error;
10136
+ return excerpts;
10137
+ } catch {
10138
+ return [];
10007
10139
  }
10008
10140
  }
10009
- async offlineSyncApplyFileContent(options) {
10010
- const resolvedNamespace = this.writableNamespaceFor(
10011
- options.namespace,
10012
- void 0,
10013
- options.principal
10014
- );
10015
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
10016
- try {
10017
- const result = await applyOfflineSyncFileContentChunk({
10018
- root: storage.dir,
10019
- sourceId: options.sourceId,
10020
- path: options.path,
10021
- sha256: options.sha256,
10022
- bytes: options.bytes,
10023
- mtimeMs: options.mtimeMs,
10024
- offset: options.offset,
10025
- baseSha256: options.baseSha256,
10026
- content: options.content,
10027
- includeTranscripts: options.includeTranscripts !== false,
10028
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
10029
- readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
10030
- writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
10031
- writeStagingFile: async ({ filePath, content }) => storage.writeOfflineSyncStagingFile(filePath, content),
10032
- writeFileChunks: async ({ filePath, chunks }) => storage.writeOfflineSyncFileChunks(filePath, chunks)
10033
- });
10034
- return {
10035
- namespace: resolvedNamespace,
10036
- ...result
10037
- };
10038
- } catch (error) {
10039
- const message = error instanceof Error ? error.message : String(error);
10040
- if (message.startsWith("offline sync") || message.startsWith("path:") || message.startsWith("sourceId must ") || message.startsWith("sha256 must ") || message.startsWith("baseSha256 must ") || message.startsWith("bytes must ") || message.startsWith("mtimeMs must ") || message.startsWith("offset must ") || message.startsWith("content chunk ") || message === "content must be a Buffer") {
10041
- throw new EngramAccessInputError(message);
10141
+ async executeRecall(request) {
10142
+ const query = request.query;
10143
+ const callerProvidedDisclosure = request.disclosure !== void 0 && request.disclosure !== null;
10144
+ const requestedDisclosure = (() => {
10145
+ if (!callerProvidedDisclosure) {
10146
+ return DEFAULT_RECALL_DISCLOSURE;
10042
10147
  }
10043
- throw error;
10148
+ if (!isRecallDisclosure(request.disclosure)) {
10149
+ throw new EngramAccessInputError(
10150
+ `disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
10151
+ );
10152
+ }
10153
+ return request.disclosure;
10154
+ })();
10155
+ if (request.codingContext !== void 0 && request.sessionKey) {
10156
+ this.deps.setCodingContext({
10157
+ sessionKey: request.sessionKey,
10158
+ codingContext: request.codingContext
10159
+ });
10044
10160
  }
10045
- }
10046
- async offlineSyncApply(options) {
10047
- const resolvedNamespace = this.writableNamespaceFor(
10048
- options.namespace,
10049
- void 0,
10050
- options.principal
10051
- );
10052
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
10053
- try {
10054
- const result = await applyOfflineSyncChangeset({
10055
- root: storage.dir,
10056
- changeset: options.changeset,
10057
- returnCurrentFiles: options.returnCurrentFiles,
10058
- readFile: async ({ filePath }) => storage.readOfflineSyncFile(filePath),
10059
- readFileDigest: async ({ filePath }) => storage.digestOfflineSyncFile(filePath),
10060
- writeFile: async ({ filePath, content }) => storage.writeOfflineSyncFile(filePath, content),
10061
- deleteFile: async ({ filePath }) => storage.deleteOfflineSyncFile(filePath)
10161
+ if (request.codingContext === void 0 && request.sessionKey) {
10162
+ await this.deps.maybeAttachCodingContext(request.sessionKey, {
10163
+ cwd: request.cwd,
10164
+ projectTag: request.projectTag
10062
10165
  });
10063
- return {
10064
- namespace: resolvedNamespace,
10065
- ...result
10166
+ }
10167
+ const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
10168
+ const namespaceOverride = this.deps.resolveRecallNamespace(
10169
+ request.namespace,
10170
+ request.sessionKey,
10171
+ authenticatedPrincipal
10172
+ );
10173
+ const namespace = namespaceOverride ?? this.deps.orchestrator.config.defaultNamespace;
10174
+ const mode = this.deps.normalizeRecallMode(request.mode);
10175
+ const maybePrincipal = this.deps.resolveRequestPrincipal(request.sessionKey, authenticatedPrincipal);
10176
+ if (resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && !maybePrincipal) {
10177
+ throw new EngramAccessInputError(
10178
+ "authentication required: namespaces are enabled and no principal was supplied"
10179
+ );
10180
+ }
10181
+ const principal = maybePrincipal ?? "default";
10182
+ const principalNamespace = defaultNamespaceForPrincipal(principal, this.deps.orchestrator.config);
10183
+ const profileCodingContext = request.sessionKey && typeof this.deps.orchestrator.getCodingContextForSession === "function" ? this.deps.orchestrator.getCodingContextForSession(request.sessionKey) : null;
10184
+ const profileCodingOverlay = !namespaceOverride && profileCodingContext && resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces && this.deps.orchestrator.config.codingMode?.projectScope ? resolveCodingNamespaceOverlay(
10185
+ profileCodingContext,
10186
+ this.deps.orchestrator.config.codingMode,
10187
+ this.deps.orchestrator.config.defaultNamespace
10188
+ ) : null;
10189
+ const profilePlan = namespaceOverride ? null : resolveScopeProfilePlan({
10190
+ config: this.deps.orchestrator.config,
10191
+ principal,
10192
+ codingContext: profileCodingContext,
10193
+ codingOverlay: profileCodingOverlay
10194
+ });
10195
+ const modeSkipsBudget = mode === "no_recall";
10196
+ const legacyRecallNamespaces = Array.isArray(this.deps.orchestrator.config.defaultRecallNamespaces) ? recallNamespacesForPrincipal(principal, this.deps.orchestrator.config) : [];
10197
+ const effectiveNamespaces = namespaceOverride ? [namespaceOverride] : profilePlan ? expandScopeProfileReadNamespaces({
10198
+ profilePlan,
10199
+ principalSelfNamespace: profilePlan.baseNamespace,
10200
+ config: this.deps.orchestrator.config,
10201
+ principal,
10202
+ codingOverlay: profileCodingOverlay,
10203
+ legacyRecallNamespaces
10204
+ }) : legacyRecallNamespaces;
10205
+ const budgetPrincipalNamespace = profilePlan?.baseNamespace ?? principalNamespace;
10206
+ let budgetDecision;
10207
+ let recordBudgetAfterSuccess = false;
10208
+ if (modeSkipsBudget) {
10209
+ budgetDecision = {
10210
+ allowed: true,
10211
+ reason: "allowed-same-namespace",
10212
+ count: 0,
10213
+ limit: {
10214
+ softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
10215
+ hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
10216
+ windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
10217
+ }
10066
10218
  };
10067
- } catch (error) {
10068
- const message = error instanceof Error ? error.message : String(error);
10069
- if (message.startsWith("offline sync")) {
10070
- throw new EngramAccessInputError(message);
10219
+ } else {
10220
+ let anyCrossNamespace = false;
10221
+ let denied = null;
10222
+ let crossNamespaceDecision = null;
10223
+ for (const ns of effectiveNamespaces) {
10224
+ const peek = this.deps.budget.peek({
10225
+ principal,
10226
+ principalNamespace: budgetPrincipalNamespace,
10227
+ queryNamespace: ns
10228
+ });
10229
+ if (peek.reason !== "allowed-same-namespace") {
10230
+ anyCrossNamespace = true;
10231
+ crossNamespaceDecision ??= peek;
10232
+ }
10233
+ if (!peek.allowed) {
10234
+ denied = peek;
10235
+ break;
10236
+ }
10071
10237
  }
10072
- throw error;
10238
+ if (denied) {
10239
+ budgetDecision = denied;
10240
+ } else if (anyCrossNamespace) {
10241
+ budgetDecision = crossNamespaceDecision ?? {
10242
+ allowed: true,
10243
+ reason: "allowed-under-soft",
10244
+ count: 0,
10245
+ limit: {
10246
+ softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
10247
+ hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
10248
+ windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
10249
+ }
10250
+ };
10251
+ recordBudgetAfterSuccess = true;
10252
+ } else {
10253
+ budgetDecision = {
10254
+ allowed: true,
10255
+ reason: "allowed-same-namespace",
10256
+ count: 0,
10257
+ limit: {
10258
+ softLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetSoftLimit ?? 10,
10259
+ hardLimit: this.deps.orchestrator.config.recallCrossNamespaceBudgetHardLimit ?? 30,
10260
+ windowMs: this.deps.orchestrator.config.recallCrossNamespaceBudgetWindowMs ?? 6e4
10261
+ }
10262
+ };
10263
+ }
10264
+ if (!budgetDecision.allowed) {
10265
+ throw new EngramAccessInputError(
10266
+ `recall denied: cross-namespace budget exceeded (${budgetDecision.count}/${budgetDecision.limit.hardLimit} in ${budgetDecision.limit.windowMs}ms window)`
10267
+ );
10268
+ }
10269
+ this.deps.budget.gc();
10073
10270
  }
10074
- }
10075
- // ── Dreams pipeline telemetry surfaces (issue #678 PR 3+4) ──────────────
10076
- /**
10077
- * Return per-phase Dreams telemetry for the last N hours (default 24).
10078
- */
10079
- async dreamsStatus(options) {
10080
- const { getDreamsStatus, normalizeDreamsStatusWindowHours } = await import("./dreams-ledger-6UODQOX6.js");
10081
- let windowHours;
10082
- try {
10083
- windowHours = normalizeDreamsStatusWindowHours(options?.windowHours);
10084
- } catch (error) {
10085
- throw new EngramAccessInputError(error instanceof Error ? error.message : String(error));
10271
+ const topK = Number.isFinite(request.topK) ? Math.max(0, Math.floor(request.topK ?? 0)) : void 0;
10272
+ let asOf;
10273
+ if (request.asOf !== void 0 && request.asOf !== null) {
10274
+ if (typeof request.asOf !== "string" || request.asOf.trim().length === 0) {
10275
+ throw new EngramAccessInputError(
10276
+ "asOf must be a non-empty ISO 8601 timestamp string"
10277
+ );
10278
+ }
10279
+ const parsed = Date.parse(request.asOf);
10280
+ if (!Number.isFinite(parsed)) {
10281
+ throw new EngramAccessInputError(
10282
+ `asOf must be a parseable ISO 8601 timestamp (got: "${request.asOf}")`
10283
+ );
10284
+ }
10285
+ asOf = request.asOf;
10086
10286
  }
10087
- const resolvedNamespace = this.resolveReadableNamespace(options?.namespace, options?.principal);
10088
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
10089
- return getDreamsStatus(storage.dir, windowHours);
10090
- }
10091
- /**
10092
- * Manually invoke a single Dreams phase pass (PR 4/4).
10093
- *
10094
- * Deep-sleep delegates to memory governance (shadow → dry-run, apply → live).
10095
- * Light-sleep and REM scan the observation ledger and memory corpus
10096
- * respectively, returning the same telemetry shape as a scheduled run.
10097
- */
10098
- async dreamsRun(options) {
10099
- const { runDreamsPhase } = await import("./dreams-ledger-6UODQOX6.js");
10100
- const validPhases = ["lightSleep", "rem", "deepSleep"];
10101
- if (!validPhases.includes(options.phase)) {
10287
+ const recallOptions = {
10288
+ namespace: namespaceOverride,
10289
+ topK,
10290
+ mode,
10291
+ ...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
10292
+ ...asOf !== void 0 ? { asOf } : {},
10293
+ ...request.includeLowConfidence === true ? { includeLowConfidence: true } : {}
10294
+ };
10295
+ const startedAt = Date.now();
10296
+ const context = await this.deps.orchestrator.recall(query, request.sessionKey, recallOptions);
10297
+ const snapshot = request.sessionKey ? this.deps.orchestrator.lastRecall.get(request.sessionKey) : null;
10298
+ const effectiveNamespace = snapshot?.namespace ? this.deps.resolveNamespace(snapshot.namespace) : namespace;
10299
+ const resultsReturned = snapshot?.memoryIds?.length ?? 0;
10300
+ const appliedTopK = snapshot?.budgetsApplied?.appliedTopK;
10301
+ const configMaxResults = typeof this.deps.orchestrator.config.qmdMaxResults === "number" && Number.isFinite(this.deps.orchestrator.config.qmdMaxResults) && this.deps.orchestrator.config.qmdMaxResults > 0 ? this.deps.orchestrator.config.qmdMaxResults : 0;
10302
+ const topKDenominator = typeof appliedTopK === "number" && Number.isFinite(appliedTopK) && appliedTopK > 0 ? Math.max(appliedTopK, resultsReturned) : typeof topK === "number" && topK > 0 ? Math.max(topK, resultsReturned) : Math.max(configMaxResults, resultsReturned, 1);
10303
+ const topKConfidence = snapshot && topKDenominator > 0 ? Math.min(1, resultsReturned / topKDenominator) : void 0;
10304
+ const escalationDecision = decideDisclosureEscalation({
10305
+ mode: this.deps.orchestrator.config.recallDisclosureEscalation,
10306
+ threshold: this.deps.orchestrator.config.recallDisclosureEscalationThreshold,
10307
+ originalDisclosure: requestedDisclosure,
10308
+ callerProvidedDisclosure,
10309
+ topKConfidence
10310
+ });
10311
+ const disclosure = escalationDecision.effective;
10312
+ const trimmedSessionKey = request.sessionKey?.trim() || void 0;
10313
+ const rawExcerptNamespace = disclosure === "raw" ? this.deps.resolveRawExcerptReadNamespace(
10314
+ request.namespace,
10315
+ trimmedSessionKey,
10316
+ authenticatedPrincipal
10317
+ ) : void 0;
10318
+ const hasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
10319
+ const rawExcerptsSuppressed = disclosure === "raw" && !hasExplicitNamespace && rawExcerptNamespace === void 0;
10320
+ const rawExcerptSessionIds = disclosure === "raw" && rawExcerptNamespace && trimmedSessionKey ? this.deps.resolveLcmReadSessionIds(
10321
+ request.namespace,
10322
+ rawExcerptNamespace,
10323
+ trimmedSessionKey,
10324
+ authenticatedPrincipal
10325
+ ) : void 0;
10326
+ let results = await this.deps.serializeRecallResults(snapshot, disclosure, {
10327
+ query,
10328
+ sessionKey: trimmedSessionKey,
10329
+ ...rawExcerptNamespace ? { rawExcerptNamespace } : {},
10330
+ ...rawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds } : {},
10331
+ ...rawExcerptsSuppressed ? { rawExcerptsSuppressed } : {}
10332
+ });
10333
+ const filterTags = normalizeTags(request.tags);
10334
+ let tagMatchMode;
10335
+ try {
10336
+ tagMatchMode = parseTagMatch(request.tagMatch);
10337
+ } catch (err) {
10102
10338
  throw new EngramAccessInputError(
10103
- `Invalid phase: ${String(options.phase)}. Must be one of: ${validPhases.join(", ")}`
10339
+ err instanceof Error ? err.message : String(err)
10104
10340
  );
10105
10341
  }
10106
- const deepSleep = this.orchestrator.config.dreamsPhases.deepSleep;
10107
- if (options.phase === "deepSleep" && deepSleep.enabled === false && deepSleep.enabledExplicitlySet === true) {
10108
- throw new EngramAccessInputError(
10109
- "memory governance is disabled by dreams.phases.deepSleep.enabled=false"
10110
- );
10342
+ let effectiveContext = context;
10343
+ if (filterTags && filterTags.length > 0) {
10344
+ const beforeIds = results.map((r) => r.id);
10345
+ const { results: admitted } = applyTagFilter(results, {
10346
+ tags: filterTags,
10347
+ tagMatch: tagMatchMode
10348
+ });
10349
+ results = admitted;
10350
+ const admittedIds = new Set(results.map((r) => r.id));
10351
+ const droppedAny = beforeIds.some((id) => !admittedIds.has(id));
10352
+ if (droppedAny) {
10353
+ effectiveContext = results.map((r) => {
10354
+ const content = typeof r.content === "string" ? r.content ?? "" : "";
10355
+ const preview = typeof r.preview === "string" ? r.preview ?? "" : "";
10356
+ return content || preview;
10357
+ }).filter((s) => s.length > 0).join("\n\n");
10358
+ }
10111
10359
  }
10112
- const dryRun = options.dryRun === true;
10113
- const resolvedNamespace = this.writableNamespaceFor(
10114
- options.namespace,
10115
- void 0,
10116
- options.authenticatedPrincipal
10360
+ const filteredMemoryIds = filterTags && filterTags.length > 0 ? results.map((r) => r.id) : snapshot?.memoryIds ?? [];
10361
+ const debug = await this.deps.buildRecallDebug(
10362
+ snapshot,
10363
+ effectiveNamespace,
10364
+ request.includeDebug === true,
10365
+ request.sessionKey
10117
10366
  );
10118
- const storage = await this.orchestrator.getStorage(resolvedNamespace);
10119
- const memoryDir = storage.dir;
10120
- const phaseRunner = dryRun || options.phase === "deepSleep" ? void 0 : async (_opts) => {
10121
- if (_opts.phase === "lightSleep") {
10122
- const result3 = await this.orchestrator.runLifecyclePolicyNow(storage);
10123
- return {
10124
- itemsProcessed: result3.memoriesAssessed,
10125
- notes: `scored ${result3.memoriesAssessed} memories`
10367
+ let auditAnomalies;
10368
+ if (this.deps.auditAdapter) {
10369
+ try {
10370
+ const resolvedAgentId = principal ?? "__anonymous__";
10371
+ const auditEntry = {
10372
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
10373
+ sessionKey: request.sessionKey ?? "",
10374
+ agentId: resolvedAgentId,
10375
+ trigger: "access-surface",
10376
+ queryText: query,
10377
+ candidateMemoryIds: snapshot?.memoryIds ?? [],
10378
+ // Audit must reflect what was actually injected, not what
10379
+ // recall produced before the tag filter. Using `context`
10380
+ // (pre-filter) overstates injectedChars and can leak content
10381
+ // from excluded memories into the audit summary (cursor
10382
+ // Medium on PR #712).
10383
+ summary: effectiveContext.slice(0, 200) || null,
10384
+ injectedChars: effectiveContext.length,
10385
+ toggleState: "enabled",
10386
+ latencyMs: Date.now() - startedAt,
10387
+ plannerMode: snapshot?.plannerMode ?? mode,
10388
+ requestedMode: mode,
10389
+ fallbackUsed: snapshot?.fallbackUsed ?? false
10126
10390
  };
10391
+ const auditResult = await this.deps.auditAdapter.record(
10392
+ resolvedAgentId || "__anonymous__",
10393
+ auditEntry
10394
+ );
10395
+ auditAnomalies = auditResult.anomalies;
10396
+ } catch {
10127
10397
  }
10128
- const result2 = await this.orchestrator.runSemanticConsolidationNow({
10129
- dryRun: false,
10130
- storage
10131
- });
10132
- const itemsProcessed = result2.clusters.reduce(
10133
- (sum, cluster) => sum + cluster.memories.length,
10134
- 0
10135
- );
10136
- return {
10137
- itemsProcessed,
10138
- notes: `REM consolidation found ${result2.clustersFound} clusters`
10139
- };
10140
- };
10141
- const governanceRunner = options.phase === "deepSleep" ? async (_opts) => {
10142
- return this.orchestrator.runDeepSleepGovernanceNow({
10143
- storage,
10144
- dryRun: _opts.dryRun
10145
- });
10146
- } : void 0;
10147
- const result = await runDreamsPhase(
10148
- { memoryDir, phase: options.phase, dryRun },
10149
- governanceRunner,
10150
- phaseRunner
10151
- );
10398
+ }
10152
10399
  return {
10153
- phase: result.phase,
10154
- dryRun: result.dryRun,
10155
- durationMs: result.durationMs,
10156
- itemsProcessed: result.itemsProcessed,
10157
- notes: result.notes
10158
- };
10159
- }
10160
- // ---------------------------------------------------------------------------
10161
- // Wearables (Limitless / Bee / Omi transcript ingestion)
10162
- //
10163
- // Thin delegations to the orchestrator-owned WearablesService — the
10164
- // same instance behind the CLI, so HTTP/MCP callers observe identical
10165
- // behavior and validation (renderer-sharing rule).
10166
- // ---------------------------------------------------------------------------
10167
- async wearablesStatus() {
10168
- return this.orchestrator.getWearablesService().status();
10169
- }
10170
- async wearablesSync(request) {
10171
- return this.orchestrator.getWearablesService().sync({
10172
- source: request.source,
10173
- date: request.date,
10174
- days: request.days,
10175
- forceMemories: request.forceMemories
10176
- });
10177
- }
10178
- async wearablesTranscriptDay(request) {
10179
- return this.orchestrator.getWearablesService().dayTranscript(request.date, request.source);
10180
- }
10181
- async wearablesTranscriptSearch(request) {
10182
- return this.orchestrator.getWearablesService().searchTranscripts(request.query, {
10183
- source: request.source,
10184
- from: request.from,
10185
- to: request.to,
10186
- limit: request.limit
10187
- });
10188
- }
10189
- async wearablesTranscriptMemories(request) {
10190
- return this.orchestrator.getWearablesService().transcriptMemories({
10191
- source: request.source,
10192
- date: request.date,
10193
- limit: request.limit
10194
- });
10195
- }
10196
- // ── Admin console surfaces (issue #1502) ────────────────────────────────
10197
- //
10198
- // Thin delegators. Every method threads `this.orchestrator` references
10199
- // into the pure `admin/admin-surfaces.ts` functions so the dashboard
10200
- // never re-resolves scope, re-derives promotion targets, or re-lists
10201
- // namespaces.
10202
- /**
10203
- * Resolve an effective scope inspection for the console. Inputs are
10204
- * redacted before return so an operator-supplied principal preview /
10205
- * namespace override never echoes a pasted credential back.
10206
- */
10207
- async adminInspectScope(options) {
10208
- const config = this.orchestrator.config;
10209
- const codingContext = options.sessionKey ? this.orchestrator.getCodingContextForSession(options.sessionKey) ?? null : null;
10210
- const inspection = inspectScope({
10211
- config,
10212
- sessionKey: options.sessionKey,
10213
- namespace: options.namespace,
10214
- principalOverride: options.principalOverride,
10215
- codingContext,
10216
- operation: options.operation
10217
- });
10218
- return redactSensitive(inspection);
10219
- }
10220
- /**
10221
- * List configured + discovered namespaces from the catalog with the
10222
- * admin filters the console exposes.
10223
- */
10224
- async adminListNamespaces(filter) {
10225
- const result = await listAdminNamespaces({
10226
- catalog: this.orchestrator.namespaceCatalog,
10227
- filter
10228
- });
10229
- return redactSensitive(result);
10230
- }
10231
- /**
10232
- * Per-namespace maintenance + QMD health. QMD diagnostics are probed via
10233
- * the orchestrator's search health accessor (one probe per namespace).
10234
- */
10235
- async adminMaintenanceHealth() {
10236
- const report = await gatherMaintenanceHealth({
10237
- catalog: this.orchestrator.namespaceCatalog,
10238
- qmdHealthProvider: async (namespace) => {
10239
- try {
10240
- const health = await this.orchestrator.searchHealthForNamespace(namespace);
10241
- return {
10242
- namespace,
10243
- collection: health.collection,
10244
- available: health.available,
10245
- collectionState: health.collectionState,
10246
- debugStatus: health.debugStatus,
10247
- installedVersion: health.installedVersion,
10248
- supportedVersion: health.supportedVersion,
10249
- supported: health.supported,
10250
- upgradeAvailable: health.upgradeAvailable,
10251
- daemonMode: health.daemonMode
10252
- };
10253
- } catch (err) {
10254
- throw err;
10255
- }
10256
- }
10257
- });
10258
- return redactSensitive(report);
10259
- }
10260
- /**
10261
- * Dry-run transcript/session audit. Detects mixed `other/default` data
10262
- * and legacy stranded directories. Never applies a migration.
10263
- */
10264
- async adminTranscriptAudit() {
10265
- const result = await auditTranscripts(this.orchestrator.config.memoryDir);
10266
- return redactSensitive(result);
10267
- }
10268
- /**
10269
- * Manually promote a memory into one or more authorized targets. Requires
10270
- * a non-empty reason (audit-logged). Reuses the scope-profile promotion
10271
- * resolution and `canWriteNamespace` gate — there is no dashboard-only
10272
- * write path.
10273
- */
10274
- async adminPromoteMemory(request) {
10275
- const config = this.orchestrator.config;
10276
- const namespacesEnabled = resolveNamespaceCapabilities(config).namespaces === true;
10277
- let sourceNamespace;
10278
- if (request.namespace) {
10279
- sourceNamespace = this.resolveReadableNamespace(
10280
- request.namespace,
10281
- request.principal
10282
- );
10283
- } else {
10284
- const codingContext2 = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
10285
- const plan = resolveScopePlan({
10286
- config,
10400
+ response: {
10401
+ query,
10287
10402
  sessionKey: request.sessionKey,
10288
- principalOverride: request.principal,
10289
- codingContext: codingContext2,
10290
- namespacesEnabled
10291
- });
10292
- sourceNamespace = this.resolveReadableNamespace(
10293
- plan.baseNamespace,
10294
- request.principal
10403
+ namespace: effectiveNamespace,
10404
+ context: effectiveContext,
10405
+ count: filterTags && filterTags.length > 0 ? results.length : snapshot?.memoryIds.length ?? results.length,
10406
+ memoryIds: filteredMemoryIds,
10407
+ results,
10408
+ recordedAt: snapshot?.recordedAt,
10409
+ traceId: snapshot?.traceId,
10410
+ plannerMode: snapshot?.plannerMode ?? mode,
10411
+ fallbackUsed: snapshot?.fallbackUsed ?? false,
10412
+ sourcesUsed: snapshot?.sourcesUsed ?? [],
10413
+ disclosure,
10414
+ budgetsApplied: snapshot?.budgetsApplied,
10415
+ auditAnomalies,
10416
+ budgetWarning: budgetDecision.reason === "warn-over-soft" ? budgetDecision : void 0,
10417
+ latencyMs: snapshot?.latencyMs ?? Date.now() - startedAt,
10418
+ debug
10419
+ },
10420
+ budgetRecordPrincipal: recordBudgetAfterSuccess ? principal : null
10421
+ };
10422
+ }
10423
+ /**
10424
+ * Recall X-ray (issue #570). Runs a recall with `xrayCapture: true`
10425
+ * and returns the resulting snapshot as structured JSON so every
10426
+ * surface (CLI / HTTP / MCP) gets the same payload. Namespace scope
10427
+ * is enforced before the recall fires (CLAUDE.md rule 42 — read and
10428
+ * write paths must resolve through the same namespace layer) so an
10429
+ * unauthorized principal cannot capture an x-ray for a namespace it
10430
+ * cannot read.
10431
+ */
10432
+ async recallXray(request) {
10433
+ const query = typeof request.query === "string" ? request.query : "";
10434
+ if (query.trim().length === 0) {
10435
+ throw new Error("recallXray: query is required and must be non-empty");
10436
+ }
10437
+ if (request.disclosure !== void 0 && !isRecallDisclosure(request.disclosure)) {
10438
+ throw new EngramAccessInputError(
10439
+ `recallXray: disclosure must be one of: chunk, section, raw (got: ${String(request.disclosure)})`
10295
10440
  );
10296
10441
  }
10297
- const codingContext = request.sessionKey ? this.orchestrator.getCodingContextForSession(request.sessionKey) ?? null : null;
10298
- const codingOverlay = codingContext ? resolveCodingNamespaceOverlay(
10299
- codingContext,
10300
- config.codingMode,
10301
- config.defaultNamespace
10302
- ) : null;
10303
- const scopeProfilePlan = resolveScopeProfilePlan({
10304
- config,
10305
- principal: request.principal,
10306
- codingContext,
10307
- codingOverlay
10308
- });
10309
- const storage = {
10310
- readMemory: async (namespace, memoryId) => {
10311
- const resolved = await this.orchestrator.getStorage(namespace);
10312
- const memory = await resolved.getMemoryById(memoryId);
10313
- if (!memory) return null;
10314
- return {
10315
- category: memory.frontmatter.category,
10316
- content: memory.content,
10317
- frontmatter: memory.frontmatter
10318
- };
10319
- },
10320
- writePromotedMemory: async (namespace, memory) => {
10321
- const resolved = await this.orchestrator.getStorage(namespace);
10322
- const { id, tombstoneBlocked } = await resolved.writeMemory(
10323
- memory.category,
10324
- memory.content,
10325
- {
10326
- confidence: memory.confidence,
10327
- tags: memory.tags,
10328
- entityRef: memory.entityRef,
10329
- source: `admin-promotion:${memory.sourceNamespace}:${memory.reason.slice(0, 120)}`,
10330
- lineage: memory.lineage,
10331
- sourceMemoryId: memory.sourceMemoryId,
10332
- actor: memory.actor,
10333
- validAt: memory.validAt
10334
- }
10442
+ const namespacesEnabled = resolveNamespaceCapabilities(this.deps.orchestrator.config).namespaces;
10443
+ const requestedNamespace = request.namespace?.trim() ? this.deps.resolveNamespace(request.namespace) : void 0;
10444
+ const authenticatedPrincipal = request.authenticatedPrincipal?.trim();
10445
+ const principal = authenticatedPrincipal || resolvePrincipal(request.sessionKey, this.deps.orchestrator.config);
10446
+ if (requestedNamespace) {
10447
+ if (!canReadNamespace(
10448
+ principal,
10449
+ requestedNamespace,
10450
+ this.deps.orchestrator.config
10451
+ )) {
10452
+ return { snapshotFound: false };
10453
+ }
10454
+ } else if (namespacesEnabled && !authenticatedPrincipal && !request.sessionKey?.trim()) {
10455
+ return { snapshotFound: false };
10456
+ }
10457
+ let budgetOverride;
10458
+ if (request.budget !== void 0 && request.budget !== null) {
10459
+ const parsed = typeof request.budget === "number" ? request.budget : Number(request.budget);
10460
+ if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
10461
+ throw new Error(
10462
+ `recallXray: budget expects a positive integer; got ${JSON.stringify(request.budget)}`
10335
10463
  );
10336
- if (tombstoneBlocked) {
10337
- throw new Error(
10338
- "target namespace tombstone-blocked the promoted content (pending_review)"
10339
- );
10340
- }
10341
- return id;
10342
10464
  }
10465
+ budgetOverride = parsed;
10466
+ }
10467
+ const mode = this.deps.normalizeRecallMode(request.mode);
10468
+ const disclosure = request.disclosure ?? DEFAULT_RECALL_DISCLOSURE;
10469
+ const previousQueue = this.deps.xrayQueue;
10470
+ let release = () => {
10343
10471
  };
10472
+ this.deps.xrayQueue = new Promise((resolve3) => {
10473
+ release = resolve3;
10474
+ });
10475
+ await previousQueue;
10476
+ const recallStartedAt = Date.now();
10477
+ const recallSessionKey = request.sessionKey?.trim() || void 0;
10478
+ let xrayResponse = { snapshotFound: false };
10344
10479
  try {
10345
- return await promoteMemory({
10346
- config,
10347
- sourceMemoryId: request.sourceMemoryId,
10348
- sourceNamespace,
10349
- principal: request.principal,
10350
- targets: request.targets,
10351
- reason: request.reason,
10352
- actor: request.principal ?? "admin-console",
10353
- storage,
10354
- scopeProfilePlan
10480
+ this.deps.orchestrator.clearLastXraySnapshot();
10481
+ await this.deps.orchestrator.recall(query, recallSessionKey, {
10482
+ xrayCapture: true,
10483
+ ...requestedNamespace ? { namespace: requestedNamespace } : {},
10484
+ ...budgetOverride !== void 0 ? { budgetCharsOverride: budgetOverride } : {},
10485
+ ...mode !== void 0 ? { mode } : {},
10486
+ // When the caller supplies an authenticated principal, forward
10487
+ // it via the dedicated override channel so orchestrator-side
10488
+ // ACL decisions use the SAME principal the access-surface
10489
+ // pre-check above authorized. Threading an
10490
+ // `authenticatedPrincipal` through `sessionKey` would be wrong:
10491
+ // `resolvePrincipal(sessionKey)` only maps configured raw
10492
+ // session keys and otherwise collapses to `"default"`, which
10493
+ // in namespace-enabled deployments produces false denials /
10494
+ // wrong-scope serving despite the pre-check passing
10495
+ // (CLAUDE.md rule 42).
10496
+ ...authenticatedPrincipal ? { principalOverride: authenticatedPrincipal } : {},
10497
+ ...request.currentContextScopes !== void 0 ? { currentContextScopes: request.currentContextScopes } : {}
10355
10498
  });
10356
- } catch (err) {
10357
- if (err instanceof AdminPromotionError) {
10358
- throw new EngramAccessInputError(err.message);
10499
+ const rawSnapshot = this.deps.orchestrator.getLastXraySnapshot();
10500
+ const namespaceMismatch = requestedNamespace !== void 0 && rawSnapshot?.namespace !== requestedNamespace;
10501
+ if (!rawSnapshot) {
10502
+ xrayResponse = { snapshotFound: false };
10503
+ } else if (namespaceMismatch) {
10504
+ xrayResponse = { snapshotFound: false };
10505
+ } else {
10506
+ let snapshot = rawSnapshot;
10507
+ const xrayFilterTags = normalizeTags(request.tags);
10508
+ let xrayTagMatch;
10509
+ try {
10510
+ xrayTagMatch = parseTagMatch(request.tagMatch);
10511
+ } catch (err) {
10512
+ throw new EngramAccessInputError(
10513
+ err instanceof Error ? err.message : String(err)
10514
+ );
10515
+ }
10516
+ if (xrayFilterTags && xrayFilterTags.length > 0) {
10517
+ const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
10518
+ const tagsByIndex = await Promise.all(
10519
+ snapshot.results.map(async (result) => {
10520
+ try {
10521
+ const storage = await this.deps.orchestrator.getStorage(namespace);
10522
+ const memory = await storage.readMemoryByPath(result.path);
10523
+ const t = memory?.frontmatter?.tags;
10524
+ return Array.isArray(t) ? normalizeProjectionTags(t) : [];
10525
+ } catch {
10526
+ return [];
10527
+ }
10528
+ })
10529
+ );
10530
+ const tagged = snapshot.results.map((result, index) => ({
10531
+ result,
10532
+ tags: tagsByIndex[index] ?? []
10533
+ }));
10534
+ const { results: admittedTagged, trace } = applyTagFilter(tagged, {
10535
+ tags: xrayFilterTags,
10536
+ tagMatch: xrayTagMatch
10537
+ });
10538
+ const admittedResults = admittedTagged.map((entry) => entry.result);
10539
+ const filters = trace ? [...snapshot.filters, trace] : snapshot.filters;
10540
+ snapshot = { ...snapshot, results: admittedResults, filters };
10541
+ }
10542
+ if (request.disclosure !== void 0) {
10543
+ const disclosure2 = request.disclosure;
10544
+ const namespace = snapshot.namespace ? this.deps.resolveNamespace(snapshot.namespace) : this.deps.orchestrator.config.defaultNamespace;
10545
+ const trimmedSessionKey = request.sessionKey?.trim() || void 0;
10546
+ const rawExcerptNamespace = disclosure2 === "raw" ? this.deps.resolveRawExcerptReadNamespace(
10547
+ request.namespace,
10548
+ trimmedSessionKey,
10549
+ authenticatedPrincipal
10550
+ ) : namespace;
10551
+ const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
10552
+ const rawExcerptsSuppressed = disclosure2 === "raw" && !xrayHasExplicitNamespace && rawExcerptNamespace === void 0;
10553
+ const rawExcerptSessionIds = disclosure2 === "raw" && trimmedSessionKey && rawExcerptNamespace ? this.deps.resolveLcmReadSessionIds(
10554
+ request.namespace,
10555
+ rawExcerptNamespace,
10556
+ trimmedSessionKey,
10557
+ authenticatedPrincipal
10558
+ ) : void 0;
10559
+ const rawExcerpts = disclosure2 === "raw" && !rawExcerptsSuppressed ? await this.deps.fetchRawExcerpts(disclosure2, {
10560
+ query,
10561
+ ...trimmedSessionKey ? { sessionKey: trimmedSessionKey } : {},
10562
+ ...rawExcerptNamespace ? { namespace: rawExcerptNamespace } : {},
10563
+ ...rawExcerptSessionIds !== void 0 ? { lcmSessionIds: rawExcerptSessionIds } : {}
10564
+ }) : disclosure2 === "raw" ? [] : null;
10565
+ const rawExcerptText = rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts.map((e) => e.content).join("\n") : "";
10566
+ const memoryByIndex = await Promise.all(
10567
+ snapshot.results.map(async (result) => {
10568
+ try {
10569
+ const storage = await this.deps.orchestrator.getStorage(namespace);
10570
+ return await storage.readMemoryByPath(result.path);
10571
+ } catch {
10572
+ return null;
10573
+ }
10574
+ })
10575
+ );
10576
+ const firstReadableIndex = memoryByIndex.findIndex((m) => m !== null);
10577
+ const baseDir = (await this.deps.orchestrator.getStorage(namespace)).dir;
10578
+ const decorated = snapshot.results.map((result, index) => {
10579
+ const memory = memoryByIndex[index];
10580
+ if (!memory) {
10581
+ return { ...result, disclosure: disclosure2 };
10582
+ }
10583
+ const shaped = shapeMemorySummary(
10584
+ memory,
10585
+ baseDir,
10586
+ disclosure2,
10587
+ disclosure2 === "raw" && index === firstReadableIndex && rawExcerpts && rawExcerpts.length > 0 ? rawExcerpts : void 0
10588
+ );
10589
+ return {
10590
+ ...result,
10591
+ disclosure: disclosure2,
10592
+ estimatedTokens: estimateRecallTokens(JSON.stringify(shaped))
10593
+ };
10594
+ });
10595
+ if (disclosure2 === "raw" && firstReadableIndex === -1 && rawExcerptText.length > 0 && decorated.length > 0) {
10596
+ decorated[0] = {
10597
+ ...decorated[0],
10598
+ disclosure: disclosure2,
10599
+ estimatedTokens: estimateRecallTokens(rawExcerptText)
10600
+ };
10601
+ }
10602
+ const decoratedSnapshot = { ...snapshot, results: decorated };
10603
+ xrayResponse = {
10604
+ snapshotFound: true,
10605
+ snapshot: decoratedSnapshot
10606
+ };
10607
+ } else {
10608
+ xrayResponse = {
10609
+ snapshotFound: true,
10610
+ snapshot
10611
+ };
10612
+ }
10359
10613
  }
10360
- throw err;
10614
+ } finally {
10615
+ release();
10616
+ }
10617
+ if (request.includeRecall === true && xrayResponse.snapshotFound === true && xrayResponse.snapshot) {
10618
+ const xrayRawExcerptNamespace = disclosure === "raw" ? this.deps.resolveRawExcerptReadNamespace(
10619
+ request.namespace,
10620
+ recallSessionKey,
10621
+ authenticatedPrincipal
10622
+ ) : void 0;
10623
+ const xrayHasExplicitNamespace = typeof request.namespace === "string" && request.namespace.trim().length > 0;
10624
+ const xrayRawExcerptsSuppressed = disclosure === "raw" && !xrayHasExplicitNamespace && xrayRawExcerptNamespace === void 0;
10625
+ const xrayRawExcerptSessionIds = disclosure === "raw" && xrayRawExcerptNamespace && recallSessionKey ? this.deps.resolveLcmReadSessionIds(
10626
+ request.namespace,
10627
+ xrayRawExcerptNamespace,
10628
+ recallSessionKey,
10629
+ authenticatedPrincipal
10630
+ ) : void 0;
10631
+ return {
10632
+ ...xrayResponse,
10633
+ recall: await this.deps.buildRecallResponseFromXraySnapshot({
10634
+ query,
10635
+ sessionKey: recallSessionKey,
10636
+ snapshot: xrayResponse.snapshot,
10637
+ disclosure,
10638
+ startedAt: recallStartedAt,
10639
+ requestedMode: request.mode,
10640
+ normalizedMode: mode,
10641
+ ...xrayRawExcerptNamespace ? { rawExcerptNamespace: xrayRawExcerptNamespace } : {},
10642
+ ...xrayRawExcerptSessionIds !== void 0 ? { rawExcerptSessionIds: xrayRawExcerptSessionIds } : {},
10643
+ ...xrayRawExcerptsSuppressed ? { rawExcerptsSuppressed: xrayRawExcerptsSuppressed } : {}
10644
+ })
10645
+ };
10361
10646
  }
10647
+ return xrayResponse;
10362
10648
  }
10363
10649
  };
10364
10650
 
@@ -10373,9 +10659,13 @@ export {
10373
10659
  clusterByKey,
10374
10660
  computeProcedureStats,
10375
10661
  formatProcedureStatsText,
10662
+ qmdCollectionPathParts,
10663
+ QmdResultResolver,
10664
+ AccessRecallSurface,
10665
+ selfDeps,
10376
10666
  EngramAccessInputError,
10377
10667
  ENGRAM_ACCESS_WRITE_SCHEMA_VERSION,
10378
10668
  shapeMemorySummary,
10379
10669
  EngramAccessService
10380
10670
  };
10381
- //# sourceMappingURL=chunk-UETBEHBA.js.map
10671
+ //# sourceMappingURL=chunk-VX2RYM2K.js.map