@remnic/core 9.3.613 → 9.3.615

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 (386) hide show
  1. package/dist/access-cli.js +59 -58
  2. package/dist/access-cli.js.map +1 -1
  3. package/dist/access-http.d.ts +4 -2
  4. package/dist/access-http.js +23 -23
  5. package/dist/access-mcp.d.ts +9 -2
  6. package/dist/access-mcp.js +20 -20
  7. package/dist/access-schema.d.ts +26 -14
  8. package/dist/access-schema.js +3 -3
  9. package/dist/{access-service-D2J9dh_9.d.ts → access-service-CBNEKjzN.d.ts} +71 -6
  10. package/dist/access-service.d.ts +2 -2
  11. package/dist/access-service.js +17 -17
  12. package/dist/active-recall.js +20 -3
  13. package/dist/active-recall.js.map +1 -1
  14. package/dist/adapters/index.js +4 -4
  15. package/dist/adapters/registry.js +2 -2
  16. package/dist/behavior-learner.js +2 -3
  17. package/dist/behavior-learner.js.map +1 -1
  18. package/dist/bootstrap.d.ts +1 -1
  19. package/dist/briefing.js +3 -3
  20. package/dist/buffer.d.ts +1 -1
  21. package/dist/buffer.js +1 -1
  22. package/dist/calibration.d.ts +5 -2
  23. package/dist/calibration.js +7 -5
  24. package/dist/calibration.js.map +1 -1
  25. package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
  26. package/dist/causal-consolidation.d.ts +8 -2
  27. package/dist/causal-consolidation.js +13 -11
  28. package/dist/causal-consolidation.js.map +1 -1
  29. package/dist/{chunk-3BP57I6J.js → chunk-2F6NP3NT.js} +2 -1
  30. package/dist/{chunk-3BP57I6J.js.map → chunk-2F6NP3NT.js.map} +1 -1
  31. package/dist/{chunk-AU7Q3LSC.js → chunk-2QSZNTDO.js} +4 -4
  32. package/dist/{chunk-HSVJGWYS.js → chunk-2ROPI5OE.js} +2 -2
  33. package/dist/{chunk-C4SQJZAF.js → chunk-2SGJY2UY.js} +6 -3
  34. package/dist/chunk-2SGJY2UY.js.map +1 -0
  35. package/dist/{chunk-ZDTVJXIP.js → chunk-3MAONBX3.js} +13 -5
  36. package/dist/chunk-3MAONBX3.js.map +1 -0
  37. package/dist/{chunk-G3Z3QEF5.js → chunk-3PY7VHV7.js} +2 -2
  38. package/dist/chunk-3PY7VHV7.js.map +1 -0
  39. package/dist/{chunk-CF3ZF2YU.js → chunk-3QSU4NFF.js} +3 -3
  40. package/dist/{chunk-AJA46VX5.js → chunk-3T74IZB3.js} +11 -2
  41. package/dist/chunk-3T74IZB3.js.map +1 -0
  42. package/dist/{chunk-KVEVLBKC.js → chunk-4HFJQCJZ.js} +13 -8
  43. package/dist/chunk-4HFJQCJZ.js.map +1 -0
  44. package/dist/{chunk-KGK2QKWL.js → chunk-4R4KTDIE.js} +1 -1
  45. package/dist/chunk-4R4KTDIE.js.map +1 -0
  46. package/dist/{chunk-OI27U2HT.js → chunk-5BTCT236.js} +2 -2
  47. package/dist/{chunk-TH67Q46T.js → chunk-5OHHEORR.js} +64 -21
  48. package/dist/chunk-5OHHEORR.js.map +1 -0
  49. package/dist/{chunk-CO7ZO4TU.js → chunk-5VDJMYTF.js} +2 -2
  50. package/dist/{chunk-BFBF3XEF.js → chunk-6BDVBBBY.js} +33 -25
  51. package/dist/{chunk-BFBF3XEF.js.map → chunk-6BDVBBBY.js.map} +1 -1
  52. package/dist/{chunk-EAZGEEG2.js → chunk-6L46YAEZ.js} +45 -9
  53. package/dist/chunk-6L46YAEZ.js.map +1 -0
  54. package/dist/{chunk-YFS5OEKO.js → chunk-7MLB4NCL.js} +2 -2
  55. package/dist/{chunk-LZ3VEOU5.js → chunk-AL4RAJL5.js} +22 -5
  56. package/dist/chunk-AL4RAJL5.js.map +1 -0
  57. package/dist/{chunk-557IAFPD.js → chunk-APRRL26Q.js} +2 -2
  58. package/dist/{chunk-QDDHYAKV.js → chunk-AZDOWD2L.js} +2 -2
  59. package/dist/{chunk-MLT75J5S.js → chunk-B6SU7YSE.js} +3 -3
  60. package/dist/{chunk-FXKPZ3H6.js → chunk-BPSGLMQ4.js} +2 -2
  61. package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
  62. package/dist/{chunk-NOMEVTUD.js → chunk-C6C7XVKG.js} +5 -4
  63. package/dist/chunk-C6C7XVKG.js.map +1 -0
  64. package/dist/{chunk-XKIQZXUB.js → chunk-CI7RKSRE.js} +7 -1
  65. package/dist/chunk-CI7RKSRE.js.map +1 -0
  66. package/dist/{chunk-IK34DVAC.js → chunk-CIOMS6DI.js} +2 -2
  67. package/dist/{chunk-2I5JGH3M.js → chunk-CYEPCZN5.js} +2 -2
  68. package/dist/{chunk-2I5JGH3M.js.map → chunk-CYEPCZN5.js.map} +1 -1
  69. package/dist/{chunk-JHMFYY7L.js → chunk-DCGT4FPP.js} +13 -5
  70. package/dist/chunk-DCGT4FPP.js.map +1 -0
  71. package/dist/{chunk-7DZRO2DC.js → chunk-DEPRLVLK.js} +2 -2
  72. package/dist/{chunk-CSKLPDN6.js → chunk-DEVUWMME.js} +52 -19
  73. package/dist/chunk-DEVUWMME.js.map +1 -0
  74. package/dist/{chunk-DHGSZ3UD.js → chunk-DGNQRNLL.js} +2 -2
  75. package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
  76. package/dist/{chunk-HJNQQICM.js → chunk-EXUAP5LH.js} +108 -51
  77. package/dist/chunk-EXUAP5LH.js.map +1 -0
  78. package/dist/chunk-FAV25DUZ.js +12 -0
  79. package/dist/chunk-FAV25DUZ.js.map +1 -0
  80. package/dist/{chunk-ETUPBUHB.js → chunk-GDASG7NC.js} +2 -2
  81. package/dist/{chunk-L227SKTB.js → chunk-GDB4J2H3.js} +17 -1
  82. package/dist/chunk-GDB4J2H3.js.map +1 -0
  83. package/dist/{chunk-IP73YCZP.js → chunk-GLPBYIXN.js} +4 -2
  84. package/dist/chunk-GLPBYIXN.js.map +1 -0
  85. package/dist/{chunk-4HP7HIE3.js → chunk-HP5FMB6L.js} +2 -2
  86. package/dist/{chunk-EVZFIAPG.js → chunk-IBTZEBUD.js} +23 -10
  87. package/dist/chunk-IBTZEBUD.js.map +1 -0
  88. package/dist/{chunk-DOX2CG6Y.js → chunk-IEUU7O4F.js} +2 -2
  89. package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
  90. package/dist/chunk-IMA6GU4Y.js.map +1 -0
  91. package/dist/{chunk-JNANKJLN.js → chunk-JOASJWQR.js} +2 -2
  92. package/dist/chunk-JOASJWQR.js.map +1 -0
  93. package/dist/{chunk-WSGF57U2.js → chunk-JQDZQ4TB.js} +2 -2
  94. package/dist/{chunk-HINSGUA7.js → chunk-KBL3JJR6.js} +9 -13
  95. package/dist/chunk-KBL3JJR6.js.map +1 -0
  96. package/dist/{chunk-IOTENEVL.js → chunk-KGLPJROV.js} +57 -50
  97. package/dist/chunk-KGLPJROV.js.map +1 -0
  98. package/dist/{chunk-W7L6HXUC.js → chunk-LXOM6IQU.js} +2 -2
  99. package/dist/{chunk-G6R5UD3Q.js → chunk-MGN7VHWQ.js} +42 -1
  100. package/dist/{chunk-G6R5UD3Q.js.map → chunk-MGN7VHWQ.js.map} +1 -1
  101. package/dist/{chunk-DLJ4IR6M.js → chunk-MHQC2WU2.js} +2 -2
  102. package/dist/chunk-MHQC2WU2.js.map +1 -0
  103. package/dist/{chunk-5RPTH6AU.js → chunk-NM5NQYJE.js} +20 -19
  104. package/dist/chunk-NM5NQYJE.js.map +1 -0
  105. package/dist/{chunk-6JGNHWCI.js → chunk-OBIRVF36.js} +3 -3
  106. package/dist/{chunk-CHCA44C3.js → chunk-ODPLEWB6.js} +3 -3
  107. package/dist/chunk-ODPLEWB6.js.map +1 -0
  108. package/dist/{chunk-HENLZHIT.js → chunk-OIF36KGD.js} +7 -4
  109. package/dist/chunk-OIF36KGD.js.map +1 -0
  110. package/dist/{chunk-GUPISBV2.js → chunk-PP2JH3GP.js} +2 -2
  111. package/dist/{chunk-OXJBNGBK.js → chunk-PSUB67YB.js} +2 -2
  112. package/dist/{chunk-UWY7GIVS.js → chunk-PYIFUBRK.js} +45 -13
  113. package/dist/chunk-PYIFUBRK.js.map +1 -0
  114. package/dist/{chunk-KIB7SDIJ.js → chunk-Q6YIJGXJ.js} +2 -2
  115. package/dist/{chunk-ZT3EGNLR.js → chunk-QPD426WT.js} +2 -2
  116. package/dist/{chunk-RLV3PQGH.js → chunk-QVO4YOB7.js} +6 -6
  117. package/dist/{chunk-GMAG2HS4.js → chunk-RG3LBSGH.js} +46 -9
  118. package/dist/chunk-RG3LBSGH.js.map +1 -0
  119. package/dist/{chunk-XSWKORGM.js → chunk-S53OYO3F.js} +3 -1
  120. package/dist/chunk-S53OYO3F.js.map +1 -0
  121. package/dist/{chunk-YCN4BVDK.js → chunk-SCPFRKIT.js} +4 -2
  122. package/dist/chunk-SCPFRKIT.js.map +1 -0
  123. package/dist/{chunk-NZPF2SYV.js → chunk-T7N6KQGS.js} +138 -5
  124. package/dist/chunk-T7N6KQGS.js.map +1 -0
  125. package/dist/{chunk-VJXSUAO7.js → chunk-TNOWU6RP.js} +13 -10
  126. package/dist/chunk-TNOWU6RP.js.map +1 -0
  127. package/dist/{chunk-PCI747N2.js → chunk-TZVQQTG4.js} +48 -19
  128. package/dist/chunk-TZVQQTG4.js.map +1 -0
  129. package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
  130. package/dist/{chunk-IK7DCC5H.js → chunk-VMGLYN42.js} +2 -2
  131. package/dist/{chunk-KM2A35EO.js → chunk-WB3LYXC5.js} +11 -7
  132. package/dist/chunk-WB3LYXC5.js.map +1 -0
  133. package/dist/{chunk-PPPZY2EU.js → chunk-WD2W4234.js} +9 -3
  134. package/dist/chunk-WD2W4234.js.map +1 -0
  135. package/dist/{chunk-NSKYFGDL.js → chunk-X4QQB7O6.js} +2 -2
  136. package/dist/{chunk-HPWVAEET.js → chunk-X6IRLNOO.js} +3 -7
  137. package/dist/chunk-X6IRLNOO.js.map +1 -0
  138. package/dist/{chunk-46GJIW5M.js → chunk-XAZOWLW4.js} +5 -5
  139. package/dist/{chunk-46GJIW5M.js.map → chunk-XAZOWLW4.js.map} +1 -1
  140. package/dist/{chunk-XPSVGJYA.js → chunk-YRMKDTKF.js} +12 -9
  141. package/dist/chunk-YRMKDTKF.js.map +1 -0
  142. package/dist/{chunk-6ZZP4EJF.js → chunk-ZJR7VG5L.js} +3 -3
  143. package/dist/{chunk-6ZZP4EJF.js.map → chunk-ZJR7VG5L.js.map} +1 -1
  144. package/dist/{chunk-2QANQKSQ.js → chunk-ZK32E74R.js} +156 -45
  145. package/dist/chunk-ZK32E74R.js.map +1 -0
  146. package/dist/{cli-OrfKXNU4.d.ts → cli-Cw729yLf.d.ts} +6 -2
  147. package/dist/cli.d.ts +3 -3
  148. package/dist/cli.js +61 -60
  149. package/dist/compounding/engine.js +3 -3
  150. package/dist/compounding/preference-consolidator.js +39 -11
  151. package/dist/compounding/preference-consolidator.js.map +1 -1
  152. package/dist/config.js +1 -1
  153. package/dist/connectors/codex-materialize-runner.js +3 -3
  154. package/dist/connectors/index.js +3 -3
  155. package/dist/consolidation-provenance-check.js +1 -1
  156. package/dist/contradiction/index.js +4 -4
  157. package/dist/conversation-index/backend.js +2 -2
  158. package/dist/conversation-index/indexer.js +1 -1
  159. package/dist/cross-namespace-budget.js +1 -1
  160. package/dist/enrichment/index.js +1 -1
  161. package/dist/entity-retrieval.js +3 -3
  162. package/dist/evals.js +1 -1
  163. package/dist/explicit-capture.d.ts +11 -1
  164. package/dist/explicit-capture.js +1 -1
  165. package/dist/extraction-judge.js +8 -1
  166. package/dist/extraction.js +2 -2
  167. package/dist/fallback-llm.d.ts +23 -6
  168. package/dist/fallback-llm.js +5 -3
  169. package/dist/{first-start-migration-GYJWIH36.js → first-start-migration-FF7YFGRP.js} +6 -6
  170. package/dist/index.d.ts +3 -3
  171. package/dist/index.js +95 -94
  172. package/dist/index.js.map +1 -1
  173. package/dist/lcm/archive.js +2 -2
  174. package/dist/lcm/engine.js +5 -5
  175. package/dist/lcm/index.js +7 -7
  176. package/dist/lcm/summarizer.js +3 -3
  177. package/dist/maintenance/memory-governance-cron.d.ts +6 -4
  178. package/dist/maintenance/memory-governance-cron.js +1 -1
  179. package/dist/maintenance/memory-governance.js +3 -3
  180. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
  181. package/dist/maintenance/rebuild-memory-projection.js +4 -4
  182. package/dist/mcp-memory-inspector-app.d.ts +2 -2
  183. package/dist/mcp-memory-inspector-app.js +1 -1
  184. package/dist/migrate/from-engram.js +1 -1
  185. package/dist/namespaces/migrate.js +16 -15
  186. package/dist/namespaces/search.js +12 -11
  187. package/dist/namespaces/storage.js +3 -3
  188. package/dist/network/webdav.d.ts +2 -0
  189. package/dist/network/webdav.js +1 -1
  190. package/dist/objective-state-writers.js +2 -2
  191. package/dist/operator-toolkit.d.ts +3 -1
  192. package/dist/operator-toolkit.js +21 -20
  193. package/dist/{orchestrator-DTRQG75J.d.ts → orchestrator-CqWOjfgl.d.ts} +46 -3
  194. package/dist/orchestrator.d.ts +1 -1
  195. package/dist/orchestrator.js +48 -45
  196. package/dist/patterns-cli.js +1 -1
  197. package/dist/qmd-recall-cache.d.ts +2 -0
  198. package/dist/qmd-recall-cache.js +1 -1
  199. package/dist/qmd.d.ts +37 -2
  200. package/dist/qmd.js +4 -1
  201. package/dist/recall-explain-renderer.js +3 -3
  202. package/dist/recall-planner-llm.d.ts +57 -0
  203. package/dist/recall-planner-llm.js +167 -0
  204. package/dist/recall-planner-llm.js.map +1 -0
  205. package/dist/recall-xray-cli.js +4 -4
  206. package/dist/recall-xray-renderer.js +3 -3
  207. package/dist/recall-xray.js +2 -2
  208. package/dist/resume-bundles.js +2 -2
  209. package/dist/retrieval-agents.js +2 -2
  210. package/dist/routing/store.js +1 -1
  211. package/dist/schemas.d.ts +22 -22
  212. package/dist/search/factory.js +11 -10
  213. package/dist/search/index.js +11 -10
  214. package/dist/search/lancedb-backend.d.ts +1 -1
  215. package/dist/search/lancedb-backend.js +3 -2
  216. package/dist/search/meilisearch-backend.d.ts +1 -1
  217. package/dist/search/meilisearch-backend.js +3 -2
  218. package/dist/search/noop-backend.d.ts +1 -1
  219. package/dist/search/noop-backend.js +1 -1
  220. package/dist/search/orama-backend.d.ts +1 -1
  221. package/dist/search/orama-backend.js +3 -2
  222. package/dist/search/port.d.ts +6 -1
  223. package/dist/search/port.js +7 -0
  224. package/dist/search/remote-backend.d.ts +1 -1
  225. package/dist/search/remote-backend.js +1 -1
  226. package/dist/semantic-consolidation.js +4 -4
  227. package/dist/semantic-rule-promotion.js +3 -3
  228. package/dist/semantic-rule-verifier.js +3 -3
  229. package/dist/session-observer-state.js +1 -1
  230. package/dist/storage.js +2 -2
  231. package/dist/summarizer.js +2 -2
  232. package/dist/temporal-index.js +1 -1
  233. package/dist/{tier-stats-SKML2OSF.js → tier-stats-3LYQ3VV5.js} +3 -3
  234. package/dist/transfer/backup.js +2 -2
  235. package/dist/transfer/capsule-export.js +2 -2
  236. package/dist/transfer/capsule-import.js +2 -2
  237. package/dist/transfer/export-sqlite.js +1 -1
  238. package/dist/transfer/types.d.ts +12 -12
  239. package/dist/types.d.ts +32 -0
  240. package/dist/types.js +1 -1
  241. package/dist/utility-learner.js +1 -1
  242. package/dist/utility-runtime.js +2 -2
  243. package/dist/verified-recall.js +3 -3
  244. package/dist/work/board.js +2 -2
  245. package/dist/work/storage.d.ts +2 -0
  246. package/dist/work/storage.js +1 -1
  247. package/package.json +1 -1
  248. package/src/access-http.ts +24 -10
  249. package/src/access-mcp.test.ts +160 -0
  250. package/src/access-mcp.ts +72 -7
  251. package/src/access-schema.ts +11 -0
  252. package/src/access-service-coding-write.test.ts +478 -0
  253. package/src/access-service.ts +237 -32
  254. package/src/active-recall.test.ts +40 -0
  255. package/src/active-recall.ts +19 -2
  256. package/src/behavior-learner.ts +5 -3
  257. package/src/buffer-session.test.ts +58 -0
  258. package/src/buffer-surprise-trigger.test.ts +4 -18
  259. package/src/buffer.ts +39 -11
  260. package/src/calibration.ts +10 -4
  261. package/src/causal-consolidation.test.ts +47 -2
  262. package/src/causal-consolidation.ts +13 -9
  263. package/src/cli.ts +19 -4
  264. package/src/compounding/engine.ts +2 -0
  265. package/src/compounding/preference-consolidator.test.ts +292 -0
  266. package/src/compounding/preference-consolidator.ts +55 -19
  267. package/src/config.test.ts +213 -0
  268. package/src/config.ts +175 -4
  269. package/src/connectors/codex-materialize-runner.ts +7 -4
  270. package/src/consolidation-provenance-check.ts +24 -5
  271. package/src/conversation-index/indexer.test.ts +22 -0
  272. package/src/conversation-index/indexer.ts +7 -3
  273. package/src/cross-namespace-budget.test.ts +44 -21
  274. package/src/cross-namespace-budget.ts +2 -2
  275. package/src/enrichment/pipeline.ts +11 -16
  276. package/src/evals.ts +1 -1
  277. package/src/explicit-capture.ts +19 -2
  278. package/src/extraction-judge-chain.test.ts +55 -0
  279. package/src/extraction-judge.ts +7 -9
  280. package/src/extraction.ts +16 -5
  281. package/src/fallback-llm.test.ts +600 -1
  282. package/src/fallback-llm.ts +91 -22
  283. package/src/maintenance/memory-governance-cron.ts +39 -29
  284. package/src/mcp-memory-inspector-app.ts +54 -12
  285. package/src/message-parts/index.ts +6 -0
  286. package/src/message-parts/message-parts.test.ts +30 -0
  287. package/src/migrate/from-engram.ts +19 -5
  288. package/src/namespaces/search.test.ts +15 -2
  289. package/src/namespaces/search.ts +1 -1
  290. package/src/network/webdav.ts +61 -21
  291. package/src/operator-toolkit.ts +6 -2
  292. package/src/orchestrator.ts +173 -20
  293. package/src/qmd-client.test.ts +85 -0
  294. package/src/qmd-recall-cache.test.ts +16 -0
  295. package/src/qmd-recall-cache.ts +7 -0
  296. package/src/qmd.test.ts +54 -0
  297. package/src/qmd.ts +119 -19
  298. package/src/recall-planner-llm.test.ts +224 -0
  299. package/src/recall-planner-llm.ts +289 -0
  300. package/src/routing/store.ts +4 -8
  301. package/src/search/factory.ts +3 -0
  302. package/src/search/lancedb-backend.ts +15 -3
  303. package/src/search/meilisearch-backend.ts +70 -7
  304. package/src/search/noop-backend.ts +5 -1
  305. package/src/search/orama-backend.ts +15 -3
  306. package/src/search/port.ts +15 -0
  307. package/src/search/remote-backend.ts +5 -1
  308. package/src/session-observer-state.ts +1 -1
  309. package/src/summarizer.ts +3 -3
  310. package/src/temporal-index.test.ts +18 -0
  311. package/src/temporal-index.ts +45 -0
  312. package/src/training-export/cli-date-validation.test.ts +36 -0
  313. package/src/training-export/date-parse.ts +21 -2
  314. package/src/transfer/export-sqlite.ts +3 -0
  315. package/src/types.ts +35 -0
  316. package/src/utility-learner.ts +1 -0
  317. package/src/work/storage.ts +23 -0
  318. package/dist/chunk-2QANQKSQ.js.map +0 -1
  319. package/dist/chunk-5RPTH6AU.js.map +0 -1
  320. package/dist/chunk-AJA46VX5.js.map +0 -1
  321. package/dist/chunk-C4SQJZAF.js.map +0 -1
  322. package/dist/chunk-CHCA44C3.js.map +0 -1
  323. package/dist/chunk-CSKLPDN6.js.map +0 -1
  324. package/dist/chunk-DLJ4IR6M.js.map +0 -1
  325. package/dist/chunk-EAZGEEG2.js.map +0 -1
  326. package/dist/chunk-EUML3N6B.js.map +0 -1
  327. package/dist/chunk-EVZFIAPG.js.map +0 -1
  328. package/dist/chunk-G3Z3QEF5.js.map +0 -1
  329. package/dist/chunk-GMAG2HS4.js.map +0 -1
  330. package/dist/chunk-HENLZHIT.js.map +0 -1
  331. package/dist/chunk-HINSGUA7.js.map +0 -1
  332. package/dist/chunk-HJNQQICM.js.map +0 -1
  333. package/dist/chunk-HPWVAEET.js.map +0 -1
  334. package/dist/chunk-IOTENEVL.js.map +0 -1
  335. package/dist/chunk-IP73YCZP.js.map +0 -1
  336. package/dist/chunk-JHMFYY7L.js.map +0 -1
  337. package/dist/chunk-JNANKJLN.js.map +0 -1
  338. package/dist/chunk-KGK2QKWL.js.map +0 -1
  339. package/dist/chunk-KM2A35EO.js.map +0 -1
  340. package/dist/chunk-KVEVLBKC.js.map +0 -1
  341. package/dist/chunk-L227SKTB.js.map +0 -1
  342. package/dist/chunk-LZ3VEOU5.js.map +0 -1
  343. package/dist/chunk-NOMEVTUD.js.map +0 -1
  344. package/dist/chunk-NZPF2SYV.js.map +0 -1
  345. package/dist/chunk-PCI747N2.js.map +0 -1
  346. package/dist/chunk-PPPZY2EU.js.map +0 -1
  347. package/dist/chunk-TH67Q46T.js.map +0 -1
  348. package/dist/chunk-UWY7GIVS.js.map +0 -1
  349. package/dist/chunk-VJXSUAO7.js.map +0 -1
  350. package/dist/chunk-XKIQZXUB.js.map +0 -1
  351. package/dist/chunk-XPSVGJYA.js.map +0 -1
  352. package/dist/chunk-XSWKORGM.js.map +0 -1
  353. package/dist/chunk-YCN4BVDK.js.map +0 -1
  354. package/dist/chunk-ZDTVJXIP.js.map +0 -1
  355. /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
  356. /package/dist/{chunk-AU7Q3LSC.js.map → chunk-2QSZNTDO.js.map} +0 -0
  357. /package/dist/{chunk-HSVJGWYS.js.map → chunk-2ROPI5OE.js.map} +0 -0
  358. /package/dist/{chunk-CF3ZF2YU.js.map → chunk-3QSU4NFF.js.map} +0 -0
  359. /package/dist/{chunk-OI27U2HT.js.map → chunk-5BTCT236.js.map} +0 -0
  360. /package/dist/{chunk-CO7ZO4TU.js.map → chunk-5VDJMYTF.js.map} +0 -0
  361. /package/dist/{chunk-YFS5OEKO.js.map → chunk-7MLB4NCL.js.map} +0 -0
  362. /package/dist/{chunk-557IAFPD.js.map → chunk-APRRL26Q.js.map} +0 -0
  363. /package/dist/{chunk-QDDHYAKV.js.map → chunk-AZDOWD2L.js.map} +0 -0
  364. /package/dist/{chunk-MLT75J5S.js.map → chunk-B6SU7YSE.js.map} +0 -0
  365. /package/dist/{chunk-FXKPZ3H6.js.map → chunk-BPSGLMQ4.js.map} +0 -0
  366. /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
  367. /package/dist/{chunk-IK34DVAC.js.map → chunk-CIOMS6DI.js.map} +0 -0
  368. /package/dist/{chunk-7DZRO2DC.js.map → chunk-DEPRLVLK.js.map} +0 -0
  369. /package/dist/{chunk-DHGSZ3UD.js.map → chunk-DGNQRNLL.js.map} +0 -0
  370. /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
  371. /package/dist/{chunk-ETUPBUHB.js.map → chunk-GDASG7NC.js.map} +0 -0
  372. /package/dist/{chunk-4HP7HIE3.js.map → chunk-HP5FMB6L.js.map} +0 -0
  373. /package/dist/{chunk-DOX2CG6Y.js.map → chunk-IEUU7O4F.js.map} +0 -0
  374. /package/dist/{chunk-WSGF57U2.js.map → chunk-JQDZQ4TB.js.map} +0 -0
  375. /package/dist/{chunk-W7L6HXUC.js.map → chunk-LXOM6IQU.js.map} +0 -0
  376. /package/dist/{chunk-6JGNHWCI.js.map → chunk-OBIRVF36.js.map} +0 -0
  377. /package/dist/{chunk-GUPISBV2.js.map → chunk-PP2JH3GP.js.map} +0 -0
  378. /package/dist/{chunk-OXJBNGBK.js.map → chunk-PSUB67YB.js.map} +0 -0
  379. /package/dist/{chunk-KIB7SDIJ.js.map → chunk-Q6YIJGXJ.js.map} +0 -0
  380. /package/dist/{chunk-ZT3EGNLR.js.map → chunk-QPD426WT.js.map} +0 -0
  381. /package/dist/{chunk-RLV3PQGH.js.map → chunk-QVO4YOB7.js.map} +0 -0
  382. /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
  383. /package/dist/{chunk-IK7DCC5H.js.map → chunk-VMGLYN42.js.map} +0 -0
  384. /package/dist/{chunk-NSKYFGDL.js.map → chunk-X4QQB7O6.js.map} +0 -0
  385. /package/dist/{first-start-migration-GYJWIH36.js.map → first-start-migration-FF7YFGRP.js.map} +0 -0
  386. /package/dist/{tier-stats-SKML2OSF.js.map → tier-stats-3LYQ3VV5.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { log } from "./logger.js";
2
2
  import path from "node:path";
3
- import type { GatewayConfig, ModelProviderConfig, AgentPersona } from "./types.js";
3
+ import type { AgentPersonaModelConfig, GatewayConfig, ModelProviderConfig, PluginConfig } from "./types.js";
4
4
  import { extractJsonCandidates } from "./json-extract.js";
5
5
  import {
6
6
  buildChatCompletionTemperature,
@@ -25,10 +25,34 @@ export interface FallbackLlmOptions {
25
25
  signal?: AbortSignal;
26
26
  /** Explicit "provider/model" override to try before the configured chain. */
27
27
  model?: string;
28
+ /** Explicit model chain override to use instead of the configured agent/default chain. */
29
+ modelChain?: AgentPersonaModelConfig;
28
30
  /** Override which agent persona's model chain to use (by ID from agents.list[]). */
29
31
  agentId?: string;
30
32
  }
31
33
 
34
+ export interface FallbackLlmAvailabilityOptions {
35
+ agentId?: string;
36
+ modelChain?: AgentPersonaModelConfig;
37
+ }
38
+
39
+ /**
40
+ * Resolve the gateway routing options Remnic's background tasks should pass to
41
+ * FallbackLlmClient — extraction, fact/profile/identity consolidation,
42
+ * summarization, calibration, and causal/semantic consolidation. Single source
43
+ * of truth so every task path stays consistent and can't diverge (gotcha #22):
44
+ * in gateway mode an explicit `taskModelChain` wins over the gateway agent
45
+ * persona; otherwise the persona (if any) is used. Returns `{}` in plugin mode
46
+ * because the chain resolves through gateway providers only. Issue #1365.
47
+ */
48
+ export function gatewayTaskChainOptions(
49
+ config: Pick<PluginConfig, "modelSource" | "taskModelChain" | "gatewayAgentId">,
50
+ ): Pick<FallbackLlmOptions, "modelChain" | "agentId"> {
51
+ if (config.modelSource !== "gateway") return {};
52
+ if (config.taskModelChain) return { modelChain: config.taskModelChain };
53
+ return config.gatewayAgentId ? { agentId: config.gatewayAgentId } : {};
54
+ }
55
+
32
56
  export interface FallbackLlmResponse {
33
57
  content: string;
34
58
  modelUsed: string;
@@ -120,8 +144,11 @@ export class FallbackLlmClient {
120
144
  /**
121
145
  * Check if fallback is available (gateway config has at least one model).
122
146
  */
123
- isAvailable(agentId?: string): boolean {
124
- const models = this.getModelChain(agentId);
147
+ isAvailable(agentIdOrOptions?: string | FallbackLlmAvailabilityOptions): boolean {
148
+ const options = typeof agentIdOrOptions === "string"
149
+ ? { agentId: agentIdOrOptions }
150
+ : (agentIdOrOptions ?? {});
151
+ const models = this.getModelChain(options.agentId, undefined, options.modelChain);
125
152
  return models.length > 0;
126
153
  }
127
154
 
@@ -134,7 +161,7 @@ export class FallbackLlmClient {
134
161
  messages: Array<{ role: "system" | "user" | "assistant"; content: string }>,
135
162
  options: FallbackLlmOptions = {},
136
163
  ): Promise<FallbackLlmResponse | null> {
137
- const models = this.getModelChain(options.agentId, options.model);
164
+ const models = this.getModelChain(options.agentId, options.model, options.modelChain);
138
165
  if (models.length === 0) {
139
166
  log.warn("fallback LLM: no models configured in gateway");
140
167
  return null;
@@ -262,18 +289,30 @@ export class FallbackLlmClient {
262
289
  * Get the full model chain from gateway config.
263
290
  * Returns array of models in order: [primary, fallback1, fallback2, ...]
264
291
  *
265
- * When agentId is provided, looks up the matching entry in agents.list[]
266
- * and uses that persona's model chain. Falls back to agents.defaults.model
267
- * if agentId is not found or not provided.
292
+ * When modelChainOverride is provided, uses it instead of any configured
293
+ * agent/default chain. Otherwise, when agentId is provided, looks up the
294
+ * matching entry in agents.list[] and uses that persona's model chain.
295
+ * Falls back to agents.defaults.model if agentId is not found or not provided.
268
296
  */
269
- private getModelChain(agentId?: string, modelOverride?: string): ModelRef[] {
297
+ private getModelChain(
298
+ agentId?: string,
299
+ modelOverride?: string,
300
+ modelChainOverride?: AgentPersonaModelConfig,
301
+ ): ModelRef[] {
270
302
  const chain: ModelRef[] = [];
271
303
  const providers = this.gatewayConfig?.models?.providers ?? {};
272
304
 
273
- // Resolve the model config: agent persona chain or global defaults
274
- let modelConfig: { primary?: string; fallbacks?: string[] } | undefined;
305
+ // Resolve the model config: explicit task chain, agent persona chain, or global defaults
306
+ let modelConfig: AgentPersonaModelConfig | undefined;
275
307
 
276
- if (agentId) {
308
+ if (modelChainOverride?.primary) {
309
+ modelConfig = modelChainOverride;
310
+ log.debug("fallback LLM: using explicit model chain override");
311
+ } else if (modelChainOverride) {
312
+ log.warn("fallback LLM: ignoring explicit model chain override without primary model");
313
+ }
314
+
315
+ if (!modelConfig && agentId) {
277
316
  const persona = this.gatewayConfig?.agents?.list?.find(
278
317
  (a) => a.id === agentId,
279
318
  );
@@ -294,21 +333,20 @@ export class FallbackLlmClient {
294
333
  // Build list of model strings: primary + fallbacks
295
334
  const modelStrings: string[] = [];
296
335
 
297
- if (typeof modelOverride === "string" && modelOverride.trim().length > 0) {
298
- modelStrings.push(modelOverride.trim());
299
- }
300
-
301
- if (modelConfig?.primary) {
302
- if (!modelStrings.includes(modelConfig.primary)) {
303
- modelStrings.push(modelConfig.primary);
336
+ const addModelString = (value: unknown): void => {
337
+ if (typeof value !== "string") return;
338
+ const trimmed = value.trim();
339
+ if (trimmed.length > 0 && !modelStrings.includes(trimmed)) {
340
+ modelStrings.push(trimmed);
304
341
  }
305
- }
342
+ };
343
+
344
+ addModelString(modelOverride);
345
+ addModelString(modelConfig?.primary);
306
346
 
307
347
  if (Array.isArray(modelConfig?.fallbacks)) {
308
348
  for (const fb of modelConfig.fallbacks) {
309
- if (typeof fb === "string" && !modelStrings.includes(fb)) {
310
- modelStrings.push(fb);
311
- }
349
+ addModelString(fb);
312
350
  }
313
351
  }
314
352
 
@@ -320,6 +358,37 @@ export class FallbackLlmClient {
320
358
  }
321
359
  }
322
360
 
361
+ // Implicit last-resort: when a task-specific modelChain override is active,
362
+ // append the gateway default model so a stale or exhausted taskModelChain
363
+ // never leaves the chain empty — Remnic should never be the reason a chat is
364
+ // interrupted by a flush failure. Keyed on `modelChainOverride?.primary` —
365
+ // the SAME activation condition chain resolution uses above — so a
366
+ // primary-less override (e.g. {}) that falls through to a persona/default
367
+ // chain does NOT get the default appended (gotcha #39). Issue #1365 / PR #1370.
368
+ if (modelChainOverride?.primary && modelStrings.length > 0) {
369
+ // Append the FULL gateway default chain (primary + fallbacks), not just
370
+ // the primary — if the default primary is also unreachable, a listed
371
+ // default fallback may still succeed (cursor review #1425).
372
+ const defaults = this.gatewayConfig?.agents?.defaults?.model;
373
+ const defaultStrings: string[] = [
374
+ ...(typeof defaults?.primary === "string" ? [defaults.primary] : []),
375
+ ...(Array.isArray(defaults?.fallbacks) ? defaults.fallbacks : []),
376
+ ];
377
+ for (const candidate of defaultStrings) {
378
+ if (typeof candidate !== "string") continue;
379
+ const trimmed = candidate.trim();
380
+ if (trimmed.length === 0 || modelStrings.includes(trimmed)) continue;
381
+ const defaultRef = this.parseModelString(trimmed, providers);
382
+ if (defaultRef) {
383
+ chain.push(defaultRef);
384
+ modelStrings.push(trimmed); // keep dedupe correct for later default fallbacks
385
+ log.debug(
386
+ `fallback LLM: appended gateway default model "${trimmed}" as implicit last resort`,
387
+ );
388
+ }
389
+ }
390
+ }
391
+
323
392
  return chain;
324
393
  }
325
394
 
@@ -388,40 +388,50 @@ export async function ensureGraphEdgeDecayCron(
388
388
 
389
389
  const scheduleLabel = graphEdgeDecayScheduleLabel(scheduleExpr);
390
390
 
391
- return ensureCronJob(jobsPath, GRAPH_EDGE_DECAY_CRON_ID, () => ({
392
- id: GRAPH_EDGE_DECAY_CRON_ID,
393
- agentId,
394
- // Schedule label reflects the actual cron expression (`daily` /
395
- // `weekly` / `custom`) so cron dashboards do not show "weekly"
396
- // when the schedule is in fact daily — Cursor review on PR #729.
397
- name: `Remnic Graph Edge Decay (${scheduleLabel})`,
398
- enabled: true,
399
- schedule: {
400
- kind: "cron",
401
- expr: scheduleExpr,
402
- tz: options.timezone,
403
- },
404
- sessionTarget: "isolated",
405
- wakeMode: "now",
406
- payload: {
407
- kind: "agentTurn",
408
- timeoutSeconds: 900,
409
- thinking: "off",
410
- message:
411
- "You are OpenClaw automation. Call tool `engram.graph_edge_decay_run` with empty params. " +
412
- "If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool.",
391
+ return ensureCronJob(
392
+ jobsPath,
393
+ GRAPH_EDGE_DECAY_CRON_ID,
394
+ () => ({
395
+ id: GRAPH_EDGE_DECAY_CRON_ID,
396
+ agentId,
397
+ // Schedule label reflects the actual cron expression (`daily` /
398
+ // `weekly` / `custom`) so cron dashboards do not show "weekly"
399
+ // when the schedule is in fact daily — Cursor review on PR #729.
400
+ name: `Remnic Graph Edge Decay (${scheduleLabel})`,
401
+ enabled: true,
402
+ schedule: {
403
+ kind: "cron",
404
+ expr: scheduleExpr,
405
+ tz: options.timezone,
406
+ },
407
+ sessionTarget: "isolated",
408
+ wakeMode: "now",
409
+ payload: {
410
+ kind: "agentTurn",
411
+ timeoutSeconds: 900,
412
+ thinking: "off",
413
+ message:
414
+ "You are OpenClaw automation. Call tool `engram.graph_edge_decay_run` with empty params. " +
415
+ "If successful output exactly NO_REPLY. On error output one concise line. Do NOT use message tool.",
416
+ },
417
+ delivery: { mode: "none" },
418
+ }),
419
+ {
420
+ updateExisting: true,
421
+ updateFields: ["name", "schedule"],
413
422
  },
414
- delivery: { mode: "none" },
415
- }));
423
+ );
416
424
  }
417
425
 
418
426
  /**
419
427
  * Pick a cron expression that approximates a cadence in milliseconds.
420
428
  *
421
- * - cadence < 7 days → daily at 04:13 (sub-daily cadence is not natively
422
- * expressible in 5-field cron without `*\/N` patterns; daily is the
423
- * safe upper bound that won't trip the cron more often than requested).
424
- * - cadence 7 days → weekly on Sunday 04:13.
429
+ * - cadence <= 1 day → daily at 04:13. Sub-daily cadence is not natively
430
+ * expressible in 5-field cron without hour/minute step patterns; daily
431
+ * preserves the previous conservative behavior for those values.
432
+ * - cadence > 1 day → weekly on Sunday 04:13. Multi-day intervals like
433
+ * 2-6 days cannot be represented accurately by a portable 5-field cron
434
+ * expression, and daily would run more often than requested.
425
435
  *
426
436
  * Operators who need finer-grained control should set `scheduleExpr`
427
437
  * directly via `ensureGraphEdgeDecayCron`.
@@ -431,7 +441,7 @@ export function graphEdgeDecayCadenceToCronExpr(cadenceMs: number): string {
431
441
  return "13 4 * * 0";
432
442
  }
433
443
  const day = 24 * 60 * 60 * 1000;
434
- if (cadenceMs < 7 * day) return "13 4 * * *";
444
+ if (cadenceMs <= day) return "13 4 * * *";
435
445
  return "13 4 * * 0";
436
446
  }
437
447
 
@@ -72,9 +72,7 @@ export function buildChatGptMemoryInspectorActionRequest(
72
72
  recall: EngramAccessRecallResponse,
73
73
  xray: RecallXraySnapshot | null,
74
74
  ): ActionConfidenceRequest {
75
- const provenances = xray === null
76
- ? recall.results.map(missingRecallProvenance)
77
- : xray.results.map((result) => result.provenance ?? missingProvenance(result));
75
+ const provenances = buildRecallProvenances(recall, xray);
78
76
  const hasUnsafeOrMissingProvenance = provenances.some(
79
77
  (provenance) => provenance.safeToUse === false || provenance.safety === "blocked",
80
78
  ) || provenances.length < recall.count;
@@ -117,15 +115,7 @@ export function buildChatGptMemoryInspectorResult(
117
115
  actionConfidence: ActionConfidenceResult,
118
116
  ): RemnicChatGptMemoryInspectorResult {
119
117
  const xrayUnavailable = xray === null;
120
- const xrayById = new Map<string, RecallXrayResult>();
121
- const xrayByPath = new Map<string, RecallXrayResult>();
122
- for (const result of xray?.results ?? []) {
123
- xrayById.set(result.memoryId, result);
124
- xrayByPath.set(result.path, result);
125
- }
126
- const matchXrayResult = (summary: EngramAccessRecallResponse["results"][number]) =>
127
- (summary.path ? xrayByPath.get(summary.path) : undefined)
128
- ?? xrayById.get(summary.id);
118
+ const matchXrayResult = buildXrayResultMatcher(xray);
129
119
  const matchedXrayResults = recall.results.map(matchXrayResult);
130
120
 
131
121
  const memories = recall.results.slice(0, 8).map((summary) => {
@@ -375,6 +365,40 @@ function average(values: number[]): number | undefined {
375
365
  return values.reduce((sum, value) => sum + value, 0) / values.length;
376
366
  }
377
367
 
368
+ function buildRecallProvenances(
369
+ recall: EngramAccessRecallResponse,
370
+ xray: RecallXraySnapshot | null,
371
+ ): RetrievedMemoryProvenance[] {
372
+ if (xray === null) {
373
+ return recall.results.map(missingRecallProvenance);
374
+ }
375
+ const matchXrayResult = buildXrayResultMatcher(xray);
376
+ return recall.results.map((summary) => {
377
+ const result = matchXrayResult(summary);
378
+ if (result === undefined) {
379
+ return missingXrayResultProvenance(summary);
380
+ }
381
+ return result.provenance ?? missingProvenance(result);
382
+ });
383
+ }
384
+
385
+ function buildXrayResultMatcher(
386
+ xray: RecallXraySnapshot | null,
387
+ ): (summary: EngramAccessRecallResponse["results"][number]) => RecallXrayResult | undefined {
388
+ const xrayById = new Map<string, RecallXrayResult>();
389
+ const xrayByPath = new Map<string, RecallXrayResult>();
390
+ for (const result of xray?.results ?? []) {
391
+ xrayById.set(result.memoryId, result);
392
+ xrayByPath.set(result.path, result);
393
+ }
394
+ return (summary) => {
395
+ if (summary.path) {
396
+ return xrayByPath.get(summary.path);
397
+ }
398
+ return xrayById.get(summary.id);
399
+ };
400
+ }
401
+
378
402
  function missingProvenance(result: RecallXrayResult): RetrievedMemoryProvenance {
379
403
  return {
380
404
  source: "unknown",
@@ -391,6 +415,24 @@ function missingProvenance(result: RecallXrayResult): RetrievedMemoryProvenance
391
415
  };
392
416
  }
393
417
 
418
+ function missingXrayResultProvenance(
419
+ summary: EngramAccessRecallResponse["results"][number],
420
+ ): RetrievedMemoryProvenance {
421
+ return {
422
+ source: "unknown",
423
+ scope: "unknown",
424
+ userContextScopes: [],
425
+ retrievalReason: `X-ray result missing for ${summary.path || summary.id}`,
426
+ confidence: 0,
427
+ stale: false,
428
+ corrected: false,
429
+ correctionState: "none",
430
+ safeToUse: false,
431
+ safety: "blocked",
432
+ safetyReasons: ["X-ray result was missing for this recalled memory."],
433
+ };
434
+ }
435
+
394
436
  function missingRecallProvenance(
395
437
  summary: EngramAccessRecallResponse["results"][number],
396
438
  ): RetrievedMemoryProvenance {
@@ -143,6 +143,12 @@ export function parseOpenAiMessageParts(
143
143
  }
144
144
  if (type === "message") {
145
145
  for (const block of gatherContentBlocks(item.content)) {
146
+ if (isOpenAiResponseItem(block)) {
147
+ parts.push(
148
+ ...parseOpenAiMessageParts([block]).map(({ ordinal: _ordinal, ...part }) => part),
149
+ );
150
+ continue;
151
+ }
146
152
  const text = asNonEmptyString(block.text ?? block.content);
147
153
  if (text) parts.push(makePart("text", { type, text }, { filePath: firstFilePath(text) }));
148
154
  }
@@ -46,6 +46,36 @@ describe("message-parts parsers", () => {
46
46
  assert.equal(parts[0]!.filePath, "src/config.ts");
47
47
  });
48
48
 
49
+ it("preserves OpenAI message content arrays with mixed tool and result blocks", () => {
50
+ const parts = parseOpenAiMessageParts({
51
+ type: "message",
52
+ content: [
53
+ { type: "output_text", text: "Updated src/a.ts" },
54
+ {
55
+ type: "function_call",
56
+ name: "apply_patch",
57
+ arguments: JSON.stringify({
58
+ patch: "*** Begin Patch\n*** Update File: src/a.ts\n*** End Patch",
59
+ }),
60
+ },
61
+ {
62
+ type: "function_call_output",
63
+ call_id: "call_1",
64
+ output: "Patched src/a.ts",
65
+ },
66
+ ],
67
+ });
68
+
69
+ assert.equal(parts.length, 3);
70
+ assert.equal(parts[0]!.kind, "text");
71
+ assert.equal(parts[0]!.filePath, "src/a.ts");
72
+ assert.equal(parts[1]!.kind, "patch");
73
+ assert.equal(parts[1]!.toolName, "apply_patch");
74
+ assert.equal(parts[1]!.filePath, "src/a.ts");
75
+ assert.equal(parts[2]!.kind, "tool_result");
76
+ assert.equal(parts[2]!.filePath, "src/a.ts");
77
+ });
78
+
49
79
  it("infers top-level OpenAI response item arrays before Anthropic arrays", () => {
50
80
  const parts = parseMessageParts([
51
81
  {
@@ -30,6 +30,7 @@ interface RollbackManifestEntry {
30
30
  backupPath?: string;
31
31
  createdByMigration?: boolean;
32
32
  contentHash?: string;
33
+ mode?: number;
33
34
  }
34
35
 
35
36
  interface RollbackManifest {
@@ -42,6 +43,7 @@ interface ValidatedRollbackManifestEntry extends RollbackManifestEntry {
42
43
  targetPath: string;
43
44
  backupPath?: string;
44
45
  contentHash?: string;
46
+ mode?: number;
45
47
  }
46
48
 
47
49
  export interface MigrationOptions {
@@ -308,11 +310,19 @@ function parseRollbackManifestEntry(raw: unknown, index: number): RollbackManife
308
310
  if (raw.contentHash !== undefined && (typeof raw.contentHash !== "string" || !/^[a-f0-9]{64}$/u.test(raw.contentHash))) {
309
311
  throw new Error(`rollback manifest entry ${index} has an invalid contentHash`);
310
312
  }
313
+ const rawMode = raw.mode;
314
+ if (
315
+ rawMode !== undefined &&
316
+ (typeof rawMode !== "number" || !Number.isInteger(rawMode) || rawMode < 0 || rawMode > 0o777)
317
+ ) {
318
+ throw new Error(`rollback manifest entry ${index} has an invalid mode`);
319
+ }
311
320
  return {
312
321
  targetPath: raw.targetPath,
313
322
  ...(raw.backupPath === undefined ? {} : { backupPath: raw.backupPath }),
314
323
  ...(raw.createdByMigration === undefined ? {} : { createdByMigration: raw.createdByMigration }),
315
324
  ...(raw.contentHash === undefined ? {} : { contentHash: raw.contentHash }),
325
+ ...(rawMode === undefined ? {} : { mode: rawMode }),
316
326
  };
317
327
  }
318
328
 
@@ -486,6 +496,7 @@ async function validateRollbackManifestEntries(
486
496
  ...(backupPath === undefined ? {} : { backupPath }),
487
497
  ...(entry.createdByMigration === undefined ? {} : { createdByMigration: entry.createdByMigration }),
488
498
  ...(entry.contentHash === undefined ? {} : { contentHash: entry.contentHash }),
499
+ ...(entry.mode === undefined ? {} : { mode: entry.mode }),
489
500
  });
490
501
  }
491
502
 
@@ -697,14 +708,16 @@ async function backupFile(
697
708
  }
698
709
  const backupPath = connectorBackupPathForTarget(targetPath, homeDir);
699
710
  await ensureParent(backupPath);
711
+ const originalMode = isRemnicTokenStorePath(targetPath, homeDir)
712
+ ? TOKEN_STORE_MODE
713
+ : (await stat(targetPath)).mode & 0o777;
700
714
  if (isRemnicTokenStorePath(targetPath, homeDir)) {
701
715
  await writeOwnerOnlyFile(backupPath, originalContent);
702
716
  } else {
703
- const originalMode = (await stat(targetPath)).mode & 0o777;
704
717
  await writeFile(backupPath, originalContent, { encoding: "utf8", mode: originalMode });
705
718
  await chmod(backupPath, originalMode);
706
719
  }
707
- manifest.entries.push({ targetPath, backupPath });
720
+ manifest.entries.push({ targetPath, backupPath, mode: originalMode });
708
721
  await persistManifest?.();
709
722
  }
710
723
 
@@ -990,9 +1003,10 @@ export async function rollbackFromEngramMigration(options?: MigrationOptions): P
990
1003
  await assertExistingRegularFileNoFollow(entry.backupPath, "rollback manifest backup");
991
1004
  await ensureParent(entry.targetPath);
992
1005
  await copyFile(entry.backupPath, entry.targetPath);
993
- if (isRemnicTokenStorePath(entry.targetPath, homeDir)) {
994
- await secureTokenFilePermissions(entry.targetPath);
995
- }
1006
+ const restoreMode = isRemnicTokenStorePath(entry.targetPath, homeDir)
1007
+ ? TOKEN_STORE_MODE
1008
+ : entry.mode ?? ((await lstat(entry.backupPath)).mode & 0o777);
1009
+ await chmod(entry.targetPath, restoreMode);
996
1010
  restored.push(entry.targetPath);
997
1011
  continue;
998
1012
  }
@@ -8,6 +8,7 @@ class FakeBackend implements SearchBackend {
8
8
  updates = 0;
9
9
  calls: Array<{ method: string; collection: string | undefined }> = [];
10
10
  ensureSignals: Array<AbortSignal | undefined> = [];
11
+ ensureCollections: Array<string | undefined> = [];
11
12
 
12
13
  constructor(
13
14
  private readonly globalUpdate: boolean,
@@ -64,8 +65,19 @@ class FakeBackend implements SearchBackend {
64
65
 
65
66
  async embedCollection(): Promise<void> {}
66
67
 
67
- async ensureCollection(_memoryDir?: string, execution?: { signal?: AbortSignal }): Promise<"present"> {
68
- this.ensureSignals.push(execution?.signal);
68
+ async ensureCollection(
69
+ _memoryDir?: string,
70
+ collectionOrExecution?: string | { signal?: AbortSignal },
71
+ execution?: { signal?: AbortSignal },
72
+ ): Promise<"present"> {
73
+ const collection = typeof collectionOrExecution === "string"
74
+ ? collectionOrExecution
75
+ : undefined;
76
+ const effectiveExecution = typeof collectionOrExecution === "string"
77
+ ? execution
78
+ : collectionOrExecution ?? execution;
79
+ this.ensureCollections.push(collection);
80
+ this.ensureSignals.push(effectiveExecution?.signal);
69
81
  return "present";
70
82
  }
71
83
  }
@@ -191,4 +203,5 @@ test("ensureNamespaceCollection forwards abort signals to backend collection che
191
203
 
192
204
  assert.equal(state, "present");
193
205
  assert.deepEqual(backend.ensureSignals, [controller.signal]);
206
+ assert.deepEqual(backend.ensureCollections, ["openclaw-engram--ns-6d61696e"]);
194
207
  });
@@ -203,7 +203,7 @@ export class NamespaceSearchRouter {
203
203
  const backend = this.createBackend(scopedConfig);
204
204
  const available = await backend.probe().catch(() => false);
205
205
  const collectionState = available
206
- ? await backend.ensureCollection(storage.dir, execution).catch(() => "unknown" as const)
206
+ ? await backend.ensureCollection(storage.dir, scopedConfig.qmdCollection, execution).catch(() => "unknown" as const)
207
207
  : "unknown";
208
208
  return {
209
209
  backend,
@@ -86,7 +86,7 @@ export class WebDavServer {
86
86
 
87
87
  private constructor(
88
88
  options: Required<Omit<WebDavServerOptions, "auth">> & Pick<WebDavServerOptions, "auth">,
89
- allowedRoots: AllowedRoot[],
89
+ allowedRoots: AllowedRoot[]
90
90
  ) {
91
91
  this.options = options;
92
92
  this.allowedRoots = allowedRoots;
@@ -313,7 +313,9 @@ export class WebDavServer {
313
313
  return out;
314
314
  }
315
315
 
316
- private async resolvePath(requestPathname: string): Promise<
316
+ private async resolvePath(
317
+ requestPathname: string
318
+ ): Promise<
317
319
  | { ok: true; absolutePath: string; displayPath: string; rootAbsolute: string }
318
320
  | { ok: false; code: number; message: string }
319
321
  > {
@@ -355,11 +357,21 @@ export class WebDavServer {
355
357
  if (!this.isPathInside(root.absolute, canonicalCandidate)) {
356
358
  return { ok: false, code: 403, message: "path escaped allowlist via symlink" };
357
359
  }
358
- return { ok: true, absolutePath: canonicalCandidate, displayPath: `/${segments.join("/")}`, rootAbsolute: root.absolute };
360
+ return {
361
+ ok: true,
362
+ absolutePath: canonicalCandidate,
363
+ displayPath: `/${segments.join("/")}`,
364
+ rootAbsolute: root.absolute,
365
+ };
359
366
  } catch (err) {
360
367
  const code = (err as NodeJS.ErrnoException).code;
361
368
  if (code === "ENOENT") {
362
- return { ok: true, absolutePath: candidate, displayPath: `/${segments.join("/")}`, rootAbsolute: root.absolute };
369
+ return {
370
+ ok: true,
371
+ absolutePath: candidate,
372
+ displayPath: `/${segments.join("/")}`,
373
+ rootAbsolute: root.absolute,
374
+ };
363
375
  }
364
376
  if (code === "ENOTDIR" || code === "ELOOP") {
365
377
  return { ok: false, code: 400, message: "invalid path" };
@@ -372,7 +384,7 @@ export class WebDavServer {
372
384
  method: "GET" | "HEAD",
373
385
  absolutePath: string,
374
386
  rootAbsolute: string,
375
- res: ServerResponse,
387
+ res: ServerResponse
376
388
  ): Promise<void> {
377
389
  const revalidated = await this.revalidatePathInsideRoot(absolutePath, rootAbsolute);
378
390
  if (!revalidated.ok) {
@@ -410,7 +422,7 @@ export class WebDavServer {
410
422
  absolutePath: string,
411
423
  rootAbsolute: string,
412
424
  displayPath: string,
413
- res: ServerResponse,
425
+ res: ServerResponse
414
426
  ): Promise<void> {
415
427
  const revalidated = await this.revalidatePathInsideRoot(absolutePath, rootAbsolute);
416
428
  if (!revalidated.ok) {
@@ -432,27 +444,58 @@ export class WebDavServer {
432
444
  if (info.isDirectory()) {
433
445
  const children = await readdir(absolutePath, { withFileTypes: true });
434
446
  for (const child of children) {
435
- const childHref = toEncodedHref(`${displayPath.replace(/\/$/, "")}/${child.name}`);
436
- entries.push(`
437
- <d:response>
438
- <d:href>${xmlEscape(childHref)}</d:href>
439
- <d:propstat><d:prop><d:resourcetype>${child.isDirectory() ? "<d:collection/>" : ""}</d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>
440
- </d:response>`);
447
+ const entry = await this.renderPropfindChildEntry(absolutePath, rootAbsolute, displayPath, child.name);
448
+ if (entry) {
449
+ entries.push(entry);
450
+ }
441
451
  }
442
452
  }
443
453
 
444
454
  const xml = `<?xml version="1.0" encoding="utf-8"?>
445
455
  <d:multistatus xmlns:d="DAV:">
446
- <d:response>
447
- <d:href>${xmlEscape(toEncodedHref(displayPath))}</d:href>
448
- <d:propstat><d:prop><d:resourcetype>${info.isDirectory() ? "<d:collection/>" : ""}</d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>
449
- </d:response>${entries.join("")}
456
+ ${this.renderPropfindResponse(toEncodedHref(displayPath), info.isDirectory())}${entries.join("")}
450
457
  </d:multistatus>`;
451
458
 
452
459
  res.writeHead(207, { "Content-Type": "application/xml; charset=utf-8" });
453
460
  res.end(xml);
454
461
  }
455
462
 
463
+ private async renderPropfindChildEntry(
464
+ parentAbsolutePath: string,
465
+ rootAbsolute: string,
466
+ displayPath: string,
467
+ childName: string
468
+ ): Promise<string | null> {
469
+ const childAbsolutePath = path.join(parentAbsolutePath, childName);
470
+ let revalidated;
471
+ try {
472
+ revalidated = await this.revalidatePathInsideRoot(childAbsolutePath, rootAbsolute);
473
+ } catch {
474
+ return null;
475
+ }
476
+ if (!revalidated.ok) {
477
+ return null;
478
+ }
479
+
480
+ let info;
481
+ try {
482
+ info = await stat(revalidated.absolutePath);
483
+ } catch {
484
+ return null;
485
+ }
486
+
487
+ const childHref = toEncodedHref(`${displayPath.replace(/\/$/, "")}/${childName}`);
488
+ return this.renderPropfindResponse(childHref, info.isDirectory());
489
+ }
490
+
491
+ private renderPropfindResponse(href: string, isDirectory: boolean): string {
492
+ return ` <d:response>
493
+ <d:href>${xmlEscape(href)}</d:href>
494
+ <d:propstat><d:prop><d:resourcetype>${isDirectory ? "<d:collection/>" : ""}</d:resourcetype></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>
495
+ </d:response>
496
+ `;
497
+ }
498
+
456
499
  private isPathInside(root: string, target: string): boolean {
457
500
  if (target === root) return true;
458
501
  if (root === path.parse(root).root) {
@@ -463,11 +506,8 @@ export class WebDavServer {
463
506
 
464
507
  private async revalidatePathInsideRoot(
465
508
  absolutePath: string,
466
- rootAbsolute: string,
467
- ): Promise<
468
- | { ok: true; absolutePath: string }
469
- | { ok: false; code: number; message: string }
470
- > {
509
+ rootAbsolute: string
510
+ ): Promise<{ ok: true; absolutePath: string } | { ok: false; code: number; message: string }> {
471
511
  try {
472
512
  const canonical = await realpath(absolutePath);
473
513
  if (!this.isPathInside(rootAbsolute, canonical)) {