@remnic/core 1.1.11 → 1.1.13

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 (1462) hide show
  1. package/README.md +3 -3
  2. package/dist/access-cli.d.ts +2 -1
  3. package/dist/access-cli.js +293 -104
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +31 -62
  6. package/dist/access-http.js +53 -35
  7. package/dist/access-mcp.d.ts +31 -8
  8. package/dist/access-mcp.js +45 -34
  9. package/dist/access-schema.d.ts +197 -14
  10. package/dist/access-schema.js +16 -5
  11. package/dist/access-service-DcCDmNYC.d.ts +1542 -0
  12. package/dist/access-service.d.ts +30 -9
  13. package/dist/access-service.js +42 -32
  14. package/dist/action-confidence.d.ts +83 -0
  15. package/dist/action-confidence.js +22 -0
  16. package/dist/active-memory-bridge.d.ts +1 -1
  17. package/dist/active-memory-bridge.js +2 -2
  18. package/dist/active-recall.d.ts +1 -1
  19. package/dist/active-recall.js +11 -3
  20. package/dist/active-recall.js.map +1 -1
  21. package/dist/adapters/claude-code.d.ts +24 -0
  22. package/dist/adapters/claude-code.js +9 -0
  23. package/dist/adapters/codex.d.ts +25 -0
  24. package/dist/adapters/codex.js +9 -0
  25. package/dist/adapters/hermes.d.ts +35 -0
  26. package/dist/adapters/hermes.js +9 -0
  27. package/dist/adapters/index.d.ts +6 -0
  28. package/dist/adapters/index.js +26 -0
  29. package/dist/adapters/registry.d.ts +20 -0
  30. package/dist/adapters/registry.js +13 -0
  31. package/dist/adapters/replit.d.ts +28 -0
  32. package/dist/adapters/replit.js +9 -0
  33. package/dist/adapters/types.d.ts +43 -0
  34. package/dist/adapters/types.js +8 -0
  35. package/dist/behavior-learner.d.ts +1 -1
  36. package/dist/behavior-signals.d.ts +1 -1
  37. package/dist/bootstrap.d.ts +23 -6
  38. package/dist/boxes.d.ts +7 -0
  39. package/dist/boxes.js +1 -1
  40. package/dist/briefing.d.ts +5 -3
  41. package/dist/briefing.js +10 -7
  42. package/dist/buffer-surprise-report.d.ts +1 -1
  43. package/dist/buffer-surprise-report.js +1 -1
  44. package/dist/buffer.d.ts +18 -4
  45. package/dist/buffer.js +1 -1
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/calibration.js +6 -6
  48. package/dist/capsule-cli.d.ts +4 -4
  49. package/dist/capsule-cli.js +1 -1
  50. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  51. package/dist/capsule-merge-4MGKE7C5.js +189 -0
  52. package/dist/causal-behavior.d.ts +9 -29
  53. package/dist/causal-behavior.js +6 -3
  54. package/dist/causal-behavior.js.map +1 -1
  55. package/dist/causal-chain.js +3 -2
  56. package/dist/causal-consolidation.d.ts +2 -2
  57. package/dist/causal-consolidation.js +28 -17
  58. package/dist/causal-consolidation.js.map +1 -1
  59. package/dist/causal-retrieval.js +3 -3
  60. package/dist/causal-trajectory.js +1 -1
  61. package/dist/chunk-25MQ7IHJ.js +427 -0
  62. package/dist/chunk-25MQ7IHJ.js.map +1 -0
  63. package/dist/chunk-2F2W355T.js +256 -0
  64. package/dist/chunk-2F2W355T.js.map +1 -0
  65. package/dist/chunk-2KI4QFHU.js +228 -0
  66. package/dist/chunk-2KI4QFHU.js.map +1 -0
  67. package/dist/chunk-2PRQG7PV.js +86 -0
  68. package/dist/chunk-2PRQG7PV.js.map +1 -0
  69. package/dist/chunk-2QR3XXIC.js +2272 -0
  70. package/dist/chunk-2QR3XXIC.js.map +1 -0
  71. package/dist/chunk-2WWLHTZY.js +121 -0
  72. package/dist/chunk-326G7DJK.js +2185 -0
  73. package/dist/chunk-326G7DJK.js.map +1 -0
  74. package/dist/chunk-34DQE4KF.js +174 -0
  75. package/dist/chunk-34DQE4KF.js.map +1 -0
  76. package/dist/chunk-3APJ5EVB.js +601 -0
  77. package/dist/chunk-3APJ5EVB.js.map +1 -0
  78. package/dist/chunk-3HPAPHUK.js +51 -0
  79. package/dist/chunk-3HPAPHUK.js.map +1 -0
  80. package/dist/chunk-3JXBXXM2.js +69 -0
  81. package/dist/chunk-3JXBXXM2.js.map +1 -0
  82. package/dist/chunk-3KW65B36.js +681 -0
  83. package/dist/chunk-3KW65B36.js.map +1 -0
  84. package/dist/chunk-3UXOZBHV.js +20 -0
  85. package/dist/chunk-3UXOZBHV.js.map +1 -0
  86. package/dist/chunk-3VAL7ZL2.js +266 -0
  87. package/dist/chunk-3VAL7ZL2.js.map +1 -0
  88. package/dist/chunk-3Y4P7RXM.js +31 -0
  89. package/dist/chunk-3Y4P7RXM.js.map +1 -0
  90. package/dist/chunk-47VWKCAF.js +273 -0
  91. package/dist/chunk-47VWKCAF.js.map +1 -0
  92. package/dist/chunk-4CRG46BG.js +271 -0
  93. package/dist/chunk-4RA3C3EV.js +60 -0
  94. package/dist/chunk-4RA3C3EV.js.map +1 -0
  95. package/dist/chunk-5375UYTQ.js +914 -0
  96. package/dist/chunk-5375UYTQ.js.map +1 -0
  97. package/dist/chunk-56K5QLHX.js +506 -0
  98. package/dist/chunk-56K5QLHX.js.map +1 -0
  99. package/dist/chunk-5NXIJZFX.js +180 -0
  100. package/dist/chunk-5NXIJZFX.js.map +1 -0
  101. package/dist/chunk-5RGLBDQF.js +596 -0
  102. package/dist/chunk-5RGLBDQF.js.map +1 -0
  103. package/dist/chunk-5UZXUTVO.js +9 -0
  104. package/dist/chunk-5UZXUTVO.js.map +1 -0
  105. package/dist/chunk-65PG43EQ.js +105 -0
  106. package/dist/chunk-65PG43EQ.js.map +1 -0
  107. package/dist/chunk-66DHUKLO.js +57 -0
  108. package/dist/chunk-66DHUKLO.js.map +1 -0
  109. package/dist/chunk-6FC5EGNV.js +46 -0
  110. package/dist/chunk-6FC5EGNV.js.map +1 -0
  111. package/dist/chunk-6H2TESSP.js +62 -0
  112. package/dist/chunk-6H2TESSP.js.map +1 -0
  113. package/dist/chunk-6LVVDPJ4.js +32 -0
  114. package/dist/chunk-6LVVDPJ4.js.map +1 -0
  115. package/dist/chunk-6NKAQ74D.js +2237 -0
  116. package/dist/chunk-6NKAQ74D.js.map +1 -0
  117. package/dist/chunk-6RVI47ZR.js +159 -0
  118. package/dist/chunk-6RVI47ZR.js.map +1 -0
  119. package/dist/chunk-7AAT6G4Q.js +5117 -0
  120. package/dist/chunk-7AAT6G4Q.js.map +1 -0
  121. package/dist/chunk-7DTASS5T.js +29 -0
  122. package/dist/chunk-7DTASS5T.js.map +1 -0
  123. package/dist/chunk-7IASACLB.js +596 -0
  124. package/dist/chunk-7MNMYOFP.js +32 -0
  125. package/dist/chunk-7MNMYOFP.js.map +1 -0
  126. package/dist/chunk-7N4KAIGN.js +133 -0
  127. package/dist/chunk-7N4KAIGN.js.map +1 -0
  128. package/dist/chunk-7OZ53EXP.js +101 -0
  129. package/dist/chunk-7OZ53EXP.js.map +1 -0
  130. package/dist/chunk-7XYTQGCC.js +134 -0
  131. package/dist/chunk-7XYTQGCC.js.map +1 -0
  132. package/dist/chunk-A2XUIMJ3.js +341 -0
  133. package/dist/chunk-A2XUIMJ3.js.map +1 -0
  134. package/dist/chunk-AC5LO7IU.js +308 -0
  135. package/dist/chunk-AC5LO7IU.js.map +1 -0
  136. package/dist/chunk-AGZQD76C.js +201 -0
  137. package/dist/chunk-AGZQD76C.js.map +1 -0
  138. package/dist/chunk-AH2JUU6X.js +336 -0
  139. package/dist/chunk-AH2JUU6X.js.map +1 -0
  140. package/dist/chunk-APO3DCMU.js +361 -0
  141. package/dist/chunk-APO3DCMU.js.map +1 -0
  142. package/dist/chunk-BFBF3XEF.js +283 -0
  143. package/dist/chunk-BFBF3XEF.js.map +1 -0
  144. package/dist/chunk-BJ3KMYTB.js +1974 -0
  145. package/dist/chunk-BJ3KMYTB.js.map +1 -0
  146. package/dist/chunk-C5BCH4ZS.js +317 -0
  147. package/dist/chunk-C5BCH4ZS.js.map +1 -0
  148. package/dist/chunk-CHEL3SKB.js +6758 -0
  149. package/dist/chunk-CHEL3SKB.js.map +1 -0
  150. package/dist/chunk-CQZRLNMV.js +1491 -0
  151. package/dist/chunk-CQZRLNMV.js.map +1 -0
  152. package/dist/chunk-D46YSIYX.js +892 -0
  153. package/dist/chunk-D46YSIYX.js.map +1 -0
  154. package/dist/chunk-DB5A3NHS.js +906 -0
  155. package/dist/chunk-DB5A3NHS.js.map +1 -0
  156. package/dist/chunk-DINWEURR.js +648 -0
  157. package/dist/chunk-DINWEURR.js.map +1 -0
  158. package/dist/chunk-DK5LDEQM.js +530 -0
  159. package/dist/chunk-DK5LDEQM.js.map +1 -0
  160. package/dist/chunk-DOM4GKSW.js +34 -0
  161. package/dist/chunk-DOM4GKSW.js.map +1 -0
  162. package/dist/chunk-EDTHC6UD.js +1075 -0
  163. package/dist/chunk-EDTHC6UD.js.map +1 -0
  164. package/dist/chunk-EFJ3MQ4V.js +721 -0
  165. package/dist/chunk-EHRTFRWW.js +89 -0
  166. package/dist/chunk-EHRTFRWW.js.map +1 -0
  167. package/dist/chunk-FAJ7FZYM.js +11 -0
  168. package/dist/chunk-FAJ7FZYM.js.map +1 -0
  169. package/dist/chunk-FBYESMQ2.js +570 -0
  170. package/dist/chunk-FBYESMQ2.js.map +1 -0
  171. package/dist/chunk-FDU6HUUL.js +147 -0
  172. package/dist/chunk-FF4KLI5W.js +99 -0
  173. package/dist/chunk-FF4KLI5W.js.map +1 -0
  174. package/dist/chunk-FIT6DMX6.js +310 -0
  175. package/dist/chunk-FIT6DMX6.js.map +1 -0
  176. package/dist/chunk-FJ43PRLT.js +272 -0
  177. package/dist/chunk-FJ43PRLT.js.map +1 -0
  178. package/dist/chunk-FKFMOY3N.js +32 -0
  179. package/dist/chunk-FKFMOY3N.js.map +1 -0
  180. package/dist/chunk-FLTNHQK6.js +262 -0
  181. package/dist/chunk-FLTNHQK6.js.map +1 -0
  182. package/dist/chunk-GA454ALV.js +12436 -0
  183. package/dist/chunk-GA454ALV.js.map +1 -0
  184. package/dist/chunk-GGKRUQOO.js +228 -0
  185. package/dist/chunk-GIF42EW3.js +63 -0
  186. package/dist/chunk-GIF42EW3.js.map +1 -0
  187. package/dist/chunk-GL6I6MEQ.js +647 -0
  188. package/dist/chunk-H3ME6L6D.js +709 -0
  189. package/dist/chunk-H3ME6L6D.js.map +1 -0
  190. package/dist/chunk-HHLLAQGZ.js +1 -0
  191. package/dist/chunk-HXXBL2KD.js +2040 -0
  192. package/dist/chunk-I5V2VDIW.js +219 -0
  193. package/dist/chunk-I5V2VDIW.js.map +1 -0
  194. package/dist/chunk-I6K5FBRQ.js +35 -0
  195. package/dist/chunk-I6K5FBRQ.js.map +1 -0
  196. package/dist/chunk-ICRIXAP2.js +121 -0
  197. package/dist/chunk-ICRIXAP2.js.map +1 -0
  198. package/dist/chunk-J4EB7DNW.js +11 -0
  199. package/dist/chunk-J4EB7DNW.js.map +1 -0
  200. package/dist/chunk-JLFA7DQG.js +62 -0
  201. package/dist/chunk-JLFA7DQG.js.map +1 -0
  202. package/dist/chunk-KJTKLXTH.js +9 -0
  203. package/dist/chunk-KJTKLXTH.js.map +1 -0
  204. package/dist/chunk-KLAO5DGL.js +917 -0
  205. package/dist/chunk-KLAO5DGL.js.map +1 -0
  206. package/dist/chunk-KNKUID7G.js +183 -0
  207. package/dist/chunk-KOSORCJG.js +624 -0
  208. package/dist/chunk-KOSORCJG.js.map +1 -0
  209. package/dist/chunk-KUJVMMZQ.js +1262 -0
  210. package/dist/chunk-KUJVMMZQ.js.map +1 -0
  211. package/dist/chunk-LCR46JY5.js +123 -0
  212. package/dist/chunk-LCR46JY5.js.map +1 -0
  213. package/dist/chunk-LLQ2LLWF.js +148 -0
  214. package/dist/chunk-LLQ2LLWF.js.map +1 -0
  215. package/dist/chunk-LPMVBPA3.js +236 -0
  216. package/dist/chunk-LT3NLYSI.js +50 -0
  217. package/dist/chunk-LT3NLYSI.js.map +1 -0
  218. package/dist/chunk-LUDTDZLK.js +287 -0
  219. package/dist/chunk-LUDTDZLK.js.map +1 -0
  220. package/dist/chunk-M23FSH32.js +3963 -0
  221. package/dist/chunk-M23FSH32.js.map +1 -0
  222. package/dist/chunk-MC26UJIM.js +118 -0
  223. package/dist/chunk-ME6ESPZU.js +119 -0
  224. package/dist/chunk-ME6ESPZU.js.map +1 -0
  225. package/dist/chunk-MGKYQQYF.js +272 -0
  226. package/dist/chunk-MGKYQQYF.js.map +1 -0
  227. package/dist/chunk-MJFNCJXV.js +66 -0
  228. package/dist/chunk-MJFNCJXV.js.map +1 -0
  229. package/dist/chunk-MSWG7JI6.js +237 -0
  230. package/dist/chunk-MSWG7JI6.js.map +1 -0
  231. package/dist/chunk-MT25YHYH.js +141 -0
  232. package/dist/chunk-MT25YHYH.js.map +1 -0
  233. package/dist/chunk-MT4HVDUZ.js +53 -0
  234. package/dist/chunk-MY6TPVXW.js +219 -0
  235. package/dist/chunk-N2D6GXBM.js +267 -0
  236. package/dist/chunk-N2D6GXBM.js.map +1 -0
  237. package/dist/chunk-NJ3MJQZX.js +46 -0
  238. package/dist/chunk-NJ3MJQZX.js.map +1 -0
  239. package/dist/chunk-NMZY542O.js +335 -0
  240. package/dist/chunk-NMZY542O.js.map +1 -0
  241. package/dist/chunk-NNVTUXEB.js +23 -0
  242. package/dist/chunk-NZL6GGQE.js +375 -0
  243. package/dist/chunk-NZL6GGQE.js.map +1 -0
  244. package/dist/chunk-OAZ5MFUB.js +4124 -0
  245. package/dist/chunk-OAZ5MFUB.js.map +1 -0
  246. package/dist/chunk-OIGNEXKZ.js +237 -0
  247. package/dist/chunk-OIGNEXKZ.js.map +1 -0
  248. package/dist/chunk-OZKZ2TRP.js +3729 -0
  249. package/dist/chunk-OZKZ2TRP.js.map +1 -0
  250. package/dist/chunk-P4NEIHUT.js +108 -0
  251. package/dist/chunk-P7FMDTKL.js +103 -0
  252. package/dist/chunk-P7FMDTKL.js.map +1 -0
  253. package/dist/chunk-PD6O7AXF.js +110 -0
  254. package/dist/chunk-PD6O7AXF.js.map +1 -0
  255. package/dist/chunk-PHK3HARR.js +32 -0
  256. package/dist/chunk-PHK3HARR.js.map +1 -0
  257. package/dist/chunk-PIRJPV5T.js +98 -0
  258. package/dist/chunk-PIRJPV5T.js.map +1 -0
  259. package/dist/chunk-PK7H5L6Y.js +159 -0
  260. package/dist/chunk-PK7H5L6Y.js.map +1 -0
  261. package/dist/chunk-PR5FBTFU.js +233 -0
  262. package/dist/chunk-PR5FBTFU.js.map +1 -0
  263. package/dist/chunk-PU63GXWS.js +174 -0
  264. package/dist/chunk-PU63GXWS.js.map +1 -0
  265. package/dist/chunk-PYPOFEMK.js +294 -0
  266. package/dist/chunk-PYPOFEMK.js.map +1 -0
  267. package/dist/chunk-PZIAX57I.js +124 -0
  268. package/dist/chunk-PZIAX57I.js.map +1 -0
  269. package/dist/chunk-Q7P4WJDP.js +26 -0
  270. package/dist/chunk-Q7P4WJDP.js.map +1 -0
  271. package/dist/chunk-QDZ2RLEC.js +908 -0
  272. package/dist/chunk-QDZ2RLEC.js.map +1 -0
  273. package/dist/chunk-QQUAB63I.js +63 -0
  274. package/dist/chunk-QQUAB63I.js.map +1 -0
  275. package/dist/chunk-QRNI5JBH.js +18 -0
  276. package/dist/chunk-RHY3HH7P.js +601 -0
  277. package/dist/chunk-RHY3HH7P.js.map +1 -0
  278. package/dist/chunk-RK6F44Y6.js +84 -0
  279. package/dist/chunk-RK6F44Y6.js.map +1 -0
  280. package/dist/chunk-RRF5UOBJ.js +91 -0
  281. package/dist/chunk-RXDLTSWT.js +124 -0
  282. package/dist/chunk-RXDLTSWT.js.map +1 -0
  283. package/dist/chunk-RYED3SPJ.js +42 -0
  284. package/dist/chunk-RYED3SPJ.js.map +1 -0
  285. package/dist/chunk-S7KDBTWT.js +106 -0
  286. package/dist/chunk-S7KDBTWT.js.map +1 -0
  287. package/dist/chunk-SEDEKFYQ.js +1 -0
  288. package/dist/chunk-SOAU2OE2.js +125 -0
  289. package/dist/chunk-SOAU2OE2.js.map +1 -0
  290. package/dist/chunk-TECVW3JP.js +36 -0
  291. package/dist/chunk-TECVW3JP.js.map +1 -0
  292. package/dist/chunk-TFO23QT4.js +88 -0
  293. package/dist/chunk-TFO23QT4.js.map +1 -0
  294. package/dist/chunk-TK4UEOSK.js +76 -0
  295. package/dist/chunk-TK4UEOSK.js.map +1 -0
  296. package/dist/chunk-TKWGAOLV.js +122 -0
  297. package/dist/chunk-TKWGAOLV.js.map +1 -0
  298. package/dist/chunk-TMM4S4IJ.js +597 -0
  299. package/dist/chunk-TMM4S4IJ.js.map +1 -0
  300. package/dist/chunk-TMQLARTH.js +188 -0
  301. package/dist/chunk-TMQLARTH.js.map +1 -0
  302. package/dist/chunk-TPDBFYEG.js +130 -0
  303. package/dist/chunk-TPDBFYEG.js.map +1 -0
  304. package/dist/chunk-TPMQ3G6Z.js +145 -0
  305. package/dist/chunk-TPMQ3G6Z.js.map +1 -0
  306. package/dist/chunk-TZOLIGIG.js +61 -0
  307. package/dist/chunk-TZOLIGIG.js.map +1 -0
  308. package/dist/chunk-U3PN77QT.js +113 -0
  309. package/dist/chunk-U3WSW6PZ.js +277 -0
  310. package/dist/chunk-U4SCL7B7.js +640 -0
  311. package/dist/chunk-U4SCL7B7.js.map +1 -0
  312. package/dist/chunk-UWK5OXUJ.js +156 -0
  313. package/dist/chunk-UWK5OXUJ.js.map +1 -0
  314. package/dist/chunk-UWVJF25J.js +74 -0
  315. package/dist/chunk-UXHQAFNA.js +1317 -0
  316. package/dist/chunk-UXHQAFNA.js.map +1 -0
  317. package/dist/chunk-V5OCT34X.js +1 -0
  318. package/dist/chunk-V5OCT34X.js.map +1 -0
  319. package/dist/chunk-VLXA6PI2.js +304 -0
  320. package/dist/chunk-VLXA6PI2.js.map +1 -0
  321. package/dist/chunk-VNO6ZJ35.js +500 -0
  322. package/dist/chunk-VNO6ZJ35.js.map +1 -0
  323. package/dist/chunk-VW676BEI.js +827 -0
  324. package/dist/chunk-VW676BEI.js.map +1 -0
  325. package/dist/chunk-VWT3F4IV.js +2161 -0
  326. package/dist/chunk-VWT3F4IV.js.map +1 -0
  327. package/dist/chunk-W3LR522O.js +2296 -0
  328. package/dist/chunk-W3LR522O.js.map +1 -0
  329. package/dist/chunk-W4L6CZKA.js +96 -0
  330. package/dist/chunk-W4L6CZKA.js.map +1 -0
  331. package/dist/chunk-W4RVMTHR.js +372 -0
  332. package/dist/chunk-W4RVMTHR.js.map +1 -0
  333. package/dist/chunk-WEHSQBFR.js +188 -0
  334. package/dist/chunk-WEHSQBFR.js.map +1 -0
  335. package/dist/chunk-WELDCG6C.js +380 -0
  336. package/dist/chunk-WELDCG6C.js.map +1 -0
  337. package/dist/chunk-WZYKANL3.js +2800 -0
  338. package/dist/chunk-WZYKANL3.js.map +1 -0
  339. package/dist/chunk-XIG5PDM7.js +48 -0
  340. package/dist/chunk-XJNBEDFE.js +193 -0
  341. package/dist/chunk-XJNBEDFE.js.map +1 -0
  342. package/dist/chunk-XVVIG67A.js +291 -0
  343. package/dist/chunk-XVVIG67A.js.map +1 -0
  344. package/dist/chunk-XVZ7B3HG.js +135 -0
  345. package/dist/chunk-YBPYIAA5.js +73 -0
  346. package/dist/chunk-YBPYIAA5.js.map +1 -0
  347. package/dist/chunk-Z734BLO3.js +21 -0
  348. package/dist/chunk-Z734BLO3.js.map +1 -0
  349. package/dist/chunk-ZKSK55RC.js +269 -0
  350. package/dist/chunk-ZKSK55RC.js.map +1 -0
  351. package/dist/chunk-ZTFCYYEZ.js +69 -0
  352. package/dist/chunk-ZTFCYYEZ.js.map +1 -0
  353. package/dist/chunk-ZY2MNJR6.js +329 -0
  354. package/dist/chunk-ZY2MNJR6.js.map +1 -0
  355. package/dist/cli-D3VpkVwB.d.ts +1136 -0
  356. package/dist/cli.d.ts +42 -10
  357. package/dist/cli.js +121 -58
  358. package/dist/codex-cli-fallback.d.ts +1 -0
  359. package/dist/codex-cli-fallback.js +1 -1
  360. package/dist/commitment-ledger.js +1 -1
  361. package/dist/compat/checks.d.ts +5 -0
  362. package/dist/compat/checks.js +11 -0
  363. package/dist/compat/checks.js.map +1 -0
  364. package/dist/compat/types.d.ts +30 -0
  365. package/dist/compat/types.js +1 -0
  366. package/dist/compat/types.js.map +1 -0
  367. package/dist/compounding/engine.d.ts +221 -0
  368. package/dist/compounding/engine.js +32 -0
  369. package/dist/compounding/engine.js.map +1 -0
  370. package/dist/compounding/preference-consolidator.d.ts +92 -0
  371. package/dist/compounding/preference-consolidator.js +553 -0
  372. package/dist/compounding/preference-consolidator.js.map +1 -0
  373. package/dist/compression-optimizer.d.ts +1 -1
  374. package/dist/config.d.ts +5 -3
  375. package/dist/config.js +9 -4
  376. package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
  377. package/dist/connectors/codex-materialize-runner.d.ts +64 -0
  378. package/dist/connectors/codex-materialize-runner.js +33 -0
  379. package/dist/connectors/codex-materialize-runner.js.map +1 -0
  380. package/dist/connectors/codex-materialize.d.ts +195 -0
  381. package/dist/connectors/codex-materialize.js +38 -0
  382. package/dist/connectors/codex-materialize.js.map +1 -0
  383. package/dist/connectors/index.d.ts +444 -0
  384. package/dist/connectors/index.js +115 -0
  385. package/dist/connectors/index.js.map +1 -0
  386. package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
  387. package/dist/connectors-cli.d.ts +1 -1
  388. package/dist/consolidation-provenance-check.d.ts +4 -2
  389. package/dist/consolidation-undo.d.ts +4 -2
  390. package/dist/contradiction/index.d.ts +258 -0
  391. package/dist/contradiction/index.js +43 -0
  392. package/dist/contradiction/index.js.map +1 -0
  393. package/dist/contradiction-review-ATP4S6IC.js +30 -0
  394. package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
  395. package/dist/contradiction-scan-5A4IDZV5.js +13 -0
  396. package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
  397. package/dist/conversation-index/backend.d.ts +97 -0
  398. package/dist/conversation-index/backend.js +13 -0
  399. package/dist/conversation-index/backend.js.map +1 -0
  400. package/dist/conversation-index/chunker.d.ts +16 -0
  401. package/dist/conversation-index/chunker.js +8 -0
  402. package/dist/conversation-index/chunker.js.map +1 -0
  403. package/dist/conversation-index/cleanup.d.ts +11 -0
  404. package/dist/conversation-index/cleanup.js +9 -0
  405. package/dist/conversation-index/cleanup.js.map +1 -0
  406. package/dist/conversation-index/faiss-adapter.d.ts +6 -0
  407. package/dist/conversation-index/faiss-adapter.js +16 -0
  408. package/dist/conversation-index/faiss-adapter.js.map +1 -0
  409. package/dist/conversation-index/indexer.d.ts +23 -0
  410. package/dist/conversation-index/indexer.js +15 -0
  411. package/dist/conversation-index/indexer.js.map +1 -0
  412. package/dist/conversation-index/search.d.ts +6 -0
  413. package/dist/conversation-index/search.js +11 -0
  414. package/dist/conversation-index/search.js.map +1 -0
  415. package/dist/day-summary.d.ts +1 -1
  416. package/dist/delinearize.d.ts +1 -1
  417. package/dist/direct-answer-wiring.d.ts +1 -1
  418. package/dist/direct-answer-wiring.js +1 -1
  419. package/dist/direct-answer.d.ts +1 -1
  420. package/dist/embedding-fallback.d.ts +1 -1
  421. package/dist/embedding-fallback.js +2 -2
  422. package/dist/enrichment/index.d.ts +163 -0
  423. package/dist/enrichment/index.js +18 -0
  424. package/dist/enrichment/index.js.map +1 -0
  425. package/dist/entity-retrieval.d.ts +4 -2
  426. package/dist/entity-retrieval.js +9 -6
  427. package/dist/entity-schema.d.ts +1 -1
  428. package/dist/evals.js +1 -1
  429. package/dist/event-order-recall.d.ts +17 -0
  430. package/dist/event-order-recall.js +11 -0
  431. package/dist/event-order-recall.js.map +1 -0
  432. package/dist/evidence-pack.d.ts +3 -1
  433. package/dist/evidence-pack.js +5 -3
  434. package/dist/explicit-capture.d.ts +23 -6
  435. package/dist/explicit-capture.js +2 -2
  436. package/dist/explicit-cue-recall.d.ts +4 -1
  437. package/dist/explicit-cue-recall.js +4 -2
  438. package/dist/extraction-judge-telemetry.d.ts +1 -1
  439. package/dist/extraction-judge-training.d.ts +1 -1
  440. package/dist/extraction-judge-training.js +1 -1
  441. package/dist/extraction-judge.d.ts +1 -1
  442. package/dist/extraction.d.ts +1 -1
  443. package/dist/extraction.js +11 -10
  444. package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
  445. package/dist/fallback-llm.d.ts +4 -1
  446. package/dist/fallback-llm.js +6 -6
  447. package/dist/focused-list-recall.d.ts +17 -0
  448. package/dist/focused-list-recall.js +11 -0
  449. package/dist/focused-list-recall.js.map +1 -0
  450. package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
  451. package/dist/identity-continuity.d.ts +1 -1
  452. package/dist/importance.d.ts +1 -1
  453. package/dist/index-DJ9QWMw-.d.ts +35 -0
  454. package/dist/index.d.ts +107 -715
  455. package/dist/index.js +657 -2611
  456. package/dist/index.js.map +1 -1
  457. package/dist/intent.d.ts +1 -1
  458. package/dist/intent.js +1 -1
  459. package/dist/lcm/archive.d.ts +89 -0
  460. package/dist/lcm/archive.js +12 -0
  461. package/dist/lcm/archive.js.map +1 -0
  462. package/dist/lcm/dag.d.ts +48 -0
  463. package/dist/lcm/dag.js +8 -0
  464. package/dist/lcm/dag.js.map +1 -0
  465. package/dist/lcm/engine.d.ts +116 -0
  466. package/dist/lcm/engine.js +20 -0
  467. package/dist/lcm/engine.js.map +1 -0
  468. package/dist/lcm/index.d.ts +12 -0
  469. package/dist/lcm/index.js +44 -0
  470. package/dist/lcm/index.js.map +1 -0
  471. package/dist/lcm/queue.d.ts +62 -0
  472. package/dist/lcm/queue.js +8 -0
  473. package/dist/lcm/queue.js.map +1 -0
  474. package/dist/lcm/recall.d.ts +20 -0
  475. package/dist/lcm/recall.js +8 -0
  476. package/dist/lcm/recall.js.map +1 -0
  477. package/dist/lcm/schema.d.ts +16 -0
  478. package/dist/lcm/schema.js +14 -0
  479. package/dist/lcm/schema.js.map +1 -0
  480. package/dist/lcm/summarizer.d.ts +38 -0
  481. package/dist/lcm/summarizer.js +12 -0
  482. package/dist/lcm/summarizer.js.map +1 -0
  483. package/dist/lcm/tools.d.ts +29 -0
  484. package/dist/lcm/tools.js +8 -0
  485. package/dist/lcm/tools.js.map +1 -0
  486. package/dist/lifecycle.d.ts +1 -1
  487. package/dist/live-connectors-runner.d.ts +1 -1
  488. package/dist/live-connectors-runner.js +5 -5
  489. package/dist/local-llm.d.ts +8 -4
  490. package/dist/local-llm.js +3 -3
  491. package/dist/maintenance/archive-observations.d.ts +18 -0
  492. package/dist/maintenance/archive-observations.js +8 -0
  493. package/dist/maintenance/archive-observations.js.map +1 -0
  494. package/dist/maintenance/backup-stamp.d.ts +3 -0
  495. package/dist/maintenance/backup-stamp.js +8 -0
  496. package/dist/maintenance/backup-stamp.js.map +1 -0
  497. package/dist/maintenance/memory-governance-cron.d.ts +85 -0
  498. package/dist/maintenance/memory-governance-cron.js +22 -0
  499. package/dist/maintenance/memory-governance-cron.js.map +1 -0
  500. package/dist/maintenance/memory-governance.d.ts +137 -0
  501. package/dist/maintenance/memory-governance.js +40 -0
  502. package/dist/maintenance/memory-governance.js.map +1 -0
  503. package/dist/maintenance/migrate-observations.d.ts +18 -0
  504. package/dist/maintenance/migrate-observations.js +9 -0
  505. package/dist/maintenance/migrate-observations.js.map +1 -0
  506. package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
  507. package/dist/maintenance/observation-ledger-utils.js +10 -0
  508. package/dist/maintenance/observation-ledger-utils.js.map +1 -0
  509. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
  510. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
  511. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
  512. package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
  513. package/dist/maintenance/rebuild-memory-projection.js +35 -0
  514. package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
  515. package/dist/maintenance/rebuild-observations.d.ts +17 -0
  516. package/dist/maintenance/rebuild-observations.js +9 -0
  517. package/dist/maintenance/rebuild-observations.js.map +1 -0
  518. package/dist/mcp-memory-inspector-app.d.ts +124 -0
  519. package/dist/mcp-memory-inspector-app.js +20 -0
  520. package/dist/mcp-memory-inspector-app.js.map +1 -0
  521. package/dist/memory-action-policy.d.ts +1 -1
  522. package/dist/memory-cache.d.ts +1 -1
  523. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  524. package/dist/memory-projection-store.d.ts +108 -3
  525. package/dist/memory-projection-store.js +2 -1
  526. package/dist/memory-provenance.d.ts +57 -0
  527. package/dist/memory-provenance.js +13 -0
  528. package/dist/memory-provenance.js.map +1 -0
  529. package/dist/memory-worth-outcomes.d.ts +4 -2
  530. package/dist/migrate/from-engram.d.ts +24 -0
  531. package/dist/migrate/from-engram.js +12 -0
  532. package/dist/migrate/from-engram.js.map +1 -0
  533. package/dist/models-json.d.ts +1 -1
  534. package/dist/namespaces/migrate.d.ts +50 -0
  535. package/dist/namespaces/migrate.js +50 -0
  536. package/dist/namespaces/migrate.js.map +1 -0
  537. package/dist/namespaces/principal.d.ts +17 -0
  538. package/dist/namespaces/principal.js +16 -0
  539. package/dist/namespaces/principal.js.map +1 -0
  540. package/dist/namespaces/search.d.ts +46 -0
  541. package/dist/namespaces/search.js +28 -0
  542. package/dist/namespaces/search.js.map +1 -0
  543. package/dist/namespaces/storage.d.ts +32 -0
  544. package/dist/namespaces/storage.js +28 -0
  545. package/dist/namespaces/storage.js.map +1 -0
  546. package/dist/native-knowledge.d.ts +1 -1
  547. package/dist/network/tailscale.d.ts +41 -0
  548. package/dist/network/tailscale.js +9 -0
  549. package/dist/network/tailscale.js.map +1 -0
  550. package/dist/network/webdav.d.ts +39 -0
  551. package/dist/network/webdav.js +10 -0
  552. package/dist/network/webdav.js.map +1 -0
  553. package/dist/objective-state-writers.d.ts +1 -1
  554. package/dist/objective-state-writers.js +2 -2
  555. package/dist/operator-toolkit.d.ts +4 -2
  556. package/dist/operator-toolkit.js +35 -17
  557. package/dist/opik-exporter.js +2 -2
  558. package/dist/opik-exporter.js.map +1 -1
  559. package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
  560. package/dist/orchestrator.d.ts +24 -7
  561. package/dist/orchestrator.js +107 -65
  562. package/dist/path-MR5JPYOP.js +9 -0
  563. package/dist/path-MR5JPYOP.js.map +1 -0
  564. package/dist/patterns-cli.d.ts +1 -1
  565. package/dist/policy-runtime.d.ts +1 -1
  566. package/dist/qmd-recall-cache.d.ts +2 -2
  567. package/dist/qmd.d.ts +103 -4
  568. package/dist/qmd.js +23 -5
  569. package/dist/recall-disclosure-escalation.d.ts +1 -1
  570. package/dist/recall-explain-renderer.d.ts +3 -1
  571. package/dist/recall-explain-renderer.js +5 -3
  572. package/dist/recall-state.d.ts +1 -1
  573. package/dist/recall-tag-filter.d.ts +3 -1
  574. package/dist/recall-xray-cli.d.ts +3 -1
  575. package/dist/recall-xray-cli.js +6 -4
  576. package/dist/recall-xray-renderer.d.ts +3 -1
  577. package/dist/recall-xray-renderer.js +5 -3
  578. package/dist/recall-xray.d.ts +8 -1
  579. package/dist/recall-xray.js +4 -2
  580. package/dist/replay/normalizers/chatgpt.d.ts +6 -0
  581. package/dist/replay/normalizers/chatgpt.js +11 -0
  582. package/dist/replay/normalizers/chatgpt.js.map +1 -0
  583. package/dist/replay/normalizers/claude.d.ts +6 -0
  584. package/dist/replay/normalizers/claude.js +11 -0
  585. package/dist/replay/normalizers/claude.js.map +1 -0
  586. package/dist/replay/normalizers/openclaw.d.ts +6 -0
  587. package/dist/replay/normalizers/openclaw.js +11 -0
  588. package/dist/replay/normalizers/openclaw.js.map +1 -0
  589. package/dist/replay/normalizers/shared.d.ts +16 -0
  590. package/dist/replay/normalizers/shared.js +14 -0
  591. package/dist/replay/normalizers/shared.js.map +1 -0
  592. package/dist/replay/runner.d.ts +35 -0
  593. package/dist/replay/runner.js +16 -0
  594. package/dist/replay/runner.js.map +1 -0
  595. package/dist/replay/types.d.ts +57 -0
  596. package/dist/replay/types.js +19 -0
  597. package/dist/replay/types.js.map +1 -0
  598. package/dist/resolution-B7FNQSSP.js +12 -0
  599. package/dist/resolution-B7FNQSSP.js.map +1 -0
  600. package/dist/resolve-auth-token.d.ts +1 -1
  601. package/dist/resolve-provider-secret.js +2 -2
  602. package/dist/response-guidance-recall.d.ts +18 -0
  603. package/dist/response-guidance-recall.js +11 -0
  604. package/dist/response-guidance-recall.js.map +1 -0
  605. package/dist/resume-bundles.js +7 -5
  606. package/dist/retrieval-agents.d.ts +2 -2
  607. package/dist/retrieval-tiers.d.ts +1 -1
  608. package/dist/routing/engine.d.ts +35 -0
  609. package/dist/routing/engine.js +16 -0
  610. package/dist/routing/engine.js.map +1 -0
  611. package/dist/routing/store.d.ts +27 -0
  612. package/dist/routing/store.js +10 -0
  613. package/dist/routing/store.js.map +1 -0
  614. package/dist/runtime/better-sqlite.d.ts +8 -0
  615. package/dist/runtime/better-sqlite.js +10 -0
  616. package/dist/runtime/better-sqlite.js.map +1 -0
  617. package/dist/runtime/child-process.d.ts +32 -0
  618. package/dist/runtime/child-process.js +10 -0
  619. package/dist/runtime/child-process.js.map +1 -0
  620. package/dist/runtime/env.d.ts +5 -0
  621. package/dist/runtime/env.js +12 -0
  622. package/dist/runtime/env.js.map +1 -0
  623. package/dist/sdk-compat.js +1 -1
  624. package/dist/search/document-scanner.d.ts +22 -0
  625. package/dist/search/document-scanner.js +8 -0
  626. package/dist/search/document-scanner.js.map +1 -0
  627. package/dist/search/embed-helper.d.ts +35 -0
  628. package/dist/search/embed-helper.js +9 -0
  629. package/dist/search/embed-helper.js.map +1 -0
  630. package/dist/search/factory.d.ts +32 -0
  631. package/dist/search/factory.js +29 -0
  632. package/dist/search/factory.js.map +1 -0
  633. package/dist/search/index.d.ts +15 -0
  634. package/dist/search/index.js +50 -0
  635. package/dist/search/index.js.map +1 -0
  636. package/dist/search/lancedb-backend.d.ts +51 -0
  637. package/dist/search/lancedb-backend.js +10 -0
  638. package/dist/search/lancedb-backend.js.map +1 -0
  639. package/dist/search/meilisearch-backend.d.ts +48 -0
  640. package/dist/search/meilisearch-backend.js +10 -0
  641. package/dist/search/meilisearch-backend.js.map +1 -0
  642. package/dist/search/noop-backend.d.ts +26 -0
  643. package/dist/search/noop-backend.js +8 -0
  644. package/dist/search/noop-backend.js.map +1 -0
  645. package/dist/search/orama-backend.d.ts +53 -0
  646. package/dist/search/orama-backend.js +10 -0
  647. package/dist/search/orama-backend.js.map +1 -0
  648. package/dist/search/port.d.ts +61 -0
  649. package/dist/search/port.js +1 -0
  650. package/dist/search/port.js.map +1 -0
  651. package/dist/search/remote-backend.d.ts +39 -0
  652. package/dist/search/remote-backend.js +9 -0
  653. package/dist/search/remote-backend.js.map +1 -0
  654. package/dist/secure-store/index.d.ts +890 -0
  655. package/dist/secure-store/index.js +156 -0
  656. package/dist/secure-store/index.js.map +1 -0
  657. package/dist/semantic-VwGI14Ok.d.ts +69 -0
  658. package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
  659. package/dist/semantic-consolidation.d.ts +3 -3
  660. package/dist/semantic-consolidation.js +15 -8
  661. package/dist/semantic-rule-promotion.js +9 -6
  662. package/dist/semantic-rule-verifier.d.ts +1 -1
  663. package/dist/semantic-rule-verifier.js +10 -7
  664. package/dist/session-observer-bands.d.ts +1 -1
  665. package/dist/session-observer-state.d.ts +1 -1
  666. package/dist/shared-context/manager.d.ts +131 -0
  667. package/dist/shared-context/manager.js +15 -0
  668. package/dist/shared-context/manager.js.map +1 -0
  669. package/dist/signal.d.ts +1 -1
  670. package/dist/skills-registry.js +13 -1
  671. package/dist/skills-registry.js.map +1 -1
  672. package/dist/state-store-VZU2IA53.js +16 -0
  673. package/dist/state-store-VZU2IA53.js.map +1 -0
  674. package/dist/storage-paths.d.ts +9 -0
  675. package/dist/storage-paths.js +20 -0
  676. package/dist/storage-paths.js.map +1 -0
  677. package/dist/storage.d.ts +6 -2
  678. package/dist/storage.js +8 -5
  679. package/dist/summarizer.d.ts +6 -1
  680. package/dist/summarizer.js +11 -10
  681. package/dist/summary-snapshot.d.ts +1 -1
  682. package/dist/summary-snapshot.js +2 -1
  683. package/dist/surfaces/dreams.d.ts +16 -0
  684. package/dist/surfaces/dreams.js +282 -0
  685. package/dist/surfaces/dreams.js.map +1 -0
  686. package/dist/surfaces/heartbeat.d.ts +17 -0
  687. package/dist/surfaces/heartbeat.js +265 -0
  688. package/dist/surfaces/heartbeat.js.map +1 -0
  689. package/dist/targeted-fact-recall.d.ts +17 -0
  690. package/dist/targeted-fact-recall.js +11 -0
  691. package/dist/targeted-fact-recall.js.map +1 -0
  692. package/dist/telemetry-transcript.d.ts +7 -0
  693. package/dist/telemetry-transcript.js +16 -0
  694. package/dist/telemetry-transcript.js.map +1 -0
  695. package/dist/temporal-supersession.d.ts +4 -2
  696. package/dist/temporal-supersession.js +2 -1
  697. package/dist/temporal-validity.d.ts +1 -1
  698. package/dist/threading.d.ts +6 -1
  699. package/dist/threading.js +2 -1
  700. package/dist/tier-migration.d.ts +5 -3
  701. package/dist/tier-routing.d.ts +1 -1
  702. package/dist/tokens.js +2 -2
  703. package/dist/topics.d.ts +1 -1
  704. package/dist/transcript.d.ts +16 -2
  705. package/dist/transcript.js +2 -1
  706. package/dist/transfer/autodetect.d.ts +4 -0
  707. package/dist/transfer/autodetect.js +15 -0
  708. package/dist/transfer/autodetect.js.map +1 -0
  709. package/dist/transfer/backup.d.ts +21 -0
  710. package/dist/transfer/backup.js +17 -0
  711. package/dist/transfer/backup.js.map +1 -0
  712. package/dist/transfer/capsule-export.d.ts +113 -0
  713. package/dist/transfer/capsule-export.js +19 -0
  714. package/dist/transfer/capsule-export.js.map +1 -0
  715. package/dist/transfer/capsule-import.d.ts +124 -0
  716. package/dist/transfer/capsule-import.js +16 -0
  717. package/dist/transfer/capsule-import.js.map +1 -0
  718. package/dist/transfer/constants.d.ts +13 -0
  719. package/dist/transfer/constants.js +12 -0
  720. package/dist/transfer/constants.js.map +1 -0
  721. package/dist/transfer/export-json.d.ts +11 -0
  722. package/dist/transfer/export-json.js +11 -0
  723. package/dist/transfer/export-json.js.map +1 -0
  724. package/dist/transfer/export-md.d.ts +10 -0
  725. package/dist/transfer/export-md.js +13 -0
  726. package/dist/transfer/export-md.js.map +1 -0
  727. package/dist/transfer/export-sqlite.d.ts +9 -0
  728. package/dist/transfer/export-sqlite.js +12 -0
  729. package/dist/transfer/export-sqlite.js.map +1 -0
  730. package/dist/transfer/fs-utils.d.ts +61 -0
  731. package/dist/transfer/fs-utils.js +40 -0
  732. package/dist/transfer/fs-utils.js.map +1 -0
  733. package/dist/transfer/import-json.d.ts +16 -0
  734. package/dist/transfer/import-json.js +13 -0
  735. package/dist/transfer/import-json.js.map +1 -0
  736. package/dist/transfer/import-md.d.ts +14 -0
  737. package/dist/transfer/import-md.js +11 -0
  738. package/dist/transfer/import-md.js.map +1 -0
  739. package/dist/transfer/import-sqlite.d.ts +14 -0
  740. package/dist/transfer/import-sqlite.js +12 -0
  741. package/dist/transfer/import-sqlite.js.map +1 -0
  742. package/dist/transfer/sqlite-schema.d.ts +4 -0
  743. package/dist/transfer/sqlite-schema.js +10 -0
  744. package/dist/transfer/sqlite-schema.js.map +1 -0
  745. package/dist/transfer/types.d.ts +916 -0
  746. package/dist/transfer/types.js +30 -0
  747. package/dist/transfer/types.js.map +1 -0
  748. package/dist/trust-zones.d.ts +3 -2
  749. package/dist/trust-zones.js +1 -1
  750. package/dist/types.d.ts +88 -3
  751. package/dist/types.js +1 -1
  752. package/dist/user-model.d.ts +37 -0
  753. package/dist/user-model.js +28 -0
  754. package/dist/user-model.js.map +1 -0
  755. package/dist/utility-runtime.d.ts +1 -1
  756. package/dist/verified-recall.js +11 -8
  757. package/dist/work/board.d.ts +43 -0
  758. package/dist/work/board.js +14 -0
  759. package/dist/work/board.js.map +1 -0
  760. package/dist/work/boundary.d.ts +8 -0
  761. package/dist/work/boundary.js +14 -0
  762. package/dist/work/boundary.js.map +1 -0
  763. package/dist/work/storage.d.ts +39 -0
  764. package/dist/work/storage.js +11 -0
  765. package/dist/work/storage.js.map +1 -0
  766. package/dist/work/types.d.ts +75 -0
  767. package/dist/work/types.js +1 -0
  768. package/dist/work/types.js.map +1 -0
  769. package/package.json +2767 -6
  770. package/scripts/faiss_index.py +816 -0
  771. package/scripts/faiss_requirements.txt +3 -0
  772. package/skills/remnic-entities/SKILL.md +51 -0
  773. package/skills/remnic-memory-workflow/SKILL.md +61 -0
  774. package/skills/remnic-recall/SKILL.md +51 -0
  775. package/skills/remnic-remember/SKILL.md +56 -0
  776. package/skills/remnic-search/SKILL.md +51 -0
  777. package/skills/remnic-status/SKILL.md +51 -0
  778. package/src/abort-error.test.ts +49 -0
  779. package/src/abort-error.ts +46 -0
  780. package/src/abstraction-nodes.ts +162 -0
  781. package/src/access-audit.test.ts +178 -0
  782. package/src/access-audit.ts +125 -0
  783. package/src/access-cli.test.ts +439 -0
  784. package/src/access-cli.ts +438 -0
  785. package/src/access-http.test.ts +225 -0
  786. package/src/access-http.ts +1899 -0
  787. package/src/access-idempotency.ts +232 -0
  788. package/src/access-mcp.test.ts +568 -0
  789. package/src/access-mcp.ts +3056 -0
  790. package/src/access-schema-pi.test.ts +60 -0
  791. package/src/access-schema.ts +522 -0
  792. package/src/access-service-namespace.test.ts +123 -0
  793. package/src/access-service.ts +5629 -0
  794. package/src/action-confidence.test.ts +206 -0
  795. package/src/action-confidence.ts +466 -0
  796. package/src/active-memory-bridge.test.ts +285 -0
  797. package/src/active-memory-bridge.ts +217 -0
  798. package/src/active-recall.test.ts +484 -0
  799. package/src/active-recall.ts +459 -0
  800. package/src/adapters/claude-code.ts +56 -0
  801. package/src/adapters/codex.ts +57 -0
  802. package/src/adapters/hermes.ts +64 -0
  803. package/src/adapters/index.ts +6 -0
  804. package/src/adapters/registry.ts +41 -0
  805. package/src/adapters/replit.ts +55 -0
  806. package/src/adapters/types.ts +51 -0
  807. package/src/behavior-learner.ts +144 -0
  808. package/src/behavior-signals.ts +73 -0
  809. package/src/binary-lifecycle/backend.ts +117 -0
  810. package/src/binary-lifecycle/index.ts +35 -0
  811. package/src/binary-lifecycle/manifest.ts +79 -0
  812. package/src/binary-lifecycle/pipeline.ts +352 -0
  813. package/src/binary-lifecycle/scanner.ts +89 -0
  814. package/src/binary-lifecycle/types.ts +89 -0
  815. package/src/bootstrap.ts +178 -0
  816. package/src/boxes.ts +521 -0
  817. package/src/briefing.test.ts +1535 -0
  818. package/src/briefing.ts +1382 -0
  819. package/src/buffer-session.test.ts +443 -0
  820. package/src/buffer-surprise-report.ts +176 -0
  821. package/src/buffer-surprise-telemetry.test.ts +606 -0
  822. package/src/buffer-surprise-trigger.test.ts +766 -0
  823. package/src/buffer-surprise.test.ts +339 -0
  824. package/src/buffer-surprise.ts +203 -0
  825. package/src/buffer.ts +900 -0
  826. package/src/bulk-import/cli-command.test.ts +204 -0
  827. package/src/bulk-import/index.ts +34 -0
  828. package/src/bulk-import/pipeline.test.ts +445 -0
  829. package/src/bulk-import/pipeline.ts +178 -0
  830. package/src/bulk-import/registry.test.ts +151 -0
  831. package/src/bulk-import/registry.ts +72 -0
  832. package/src/bulk-import/types.test.ts +272 -0
  833. package/src/bulk-import/types.ts +145 -0
  834. package/src/calibration.ts +394 -0
  835. package/src/capsule-cli.test.ts +398 -0
  836. package/src/capsule-cli.ts +565 -0
  837. package/src/causal-behavior.ts +308 -0
  838. package/src/causal-chain.ts +419 -0
  839. package/src/causal-consolidation.ts +370 -0
  840. package/src/causal-retrieval.ts +286 -0
  841. package/src/causal-trajectory-graph.ts +60 -0
  842. package/src/causal-trajectory.ts +303 -0
  843. package/src/chunking.ts +220 -0
  844. package/src/citations.ts +232 -0
  845. package/src/cli.ts +9403 -0
  846. package/src/codex-cli-fallback.ts +162 -0
  847. package/src/codex-thread-key.ts +1 -0
  848. package/src/coding/access-coding-context.test.ts +197 -0
  849. package/src/coding/coding-branch-scope.test.ts +281 -0
  850. package/src/coding/coding-namespace.test.ts +360 -0
  851. package/src/coding/coding-namespace.ts +412 -0
  852. package/src/coding/coding-orchestrator.test.ts +249 -0
  853. package/src/coding/git-context.test.ts +507 -0
  854. package/src/coding/git-context.ts +336 -0
  855. package/src/coding/mcp-set-coding-context.test.ts +174 -0
  856. package/src/coding/review-context.test.ts +316 -0
  857. package/src/coding/review-context.ts +349 -0
  858. package/src/coding/wire-coding-context.test.ts +468 -0
  859. package/src/commitment-ledger.test.ts +78 -0
  860. package/src/commitment-ledger.ts +337 -0
  861. package/src/compat/checks.test.ts +206 -0
  862. package/src/compat/checks.ts +716 -0
  863. package/src/compat/types.ts +33 -0
  864. package/src/compounding/engine.ts +1686 -0
  865. package/src/compounding/preference-consolidator.ts +778 -0
  866. package/src/compression-optimizer.ts +312 -0
  867. package/src/config.test.ts +930 -0
  868. package/src/config.ts +3807 -0
  869. package/src/connectors/codex/instructions.md +160 -0
  870. package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  871. package/src/connectors/codex-marketplace.ts +500 -0
  872. package/src/connectors/codex-materialize-runner.ts +212 -0
  873. package/src/connectors/codex-materialize.ts +983 -0
  874. package/src/connectors/coerce.ts +62 -0
  875. package/src/connectors/index.test.ts +1570 -0
  876. package/src/connectors/index.ts +3222 -0
  877. package/src/connectors/live/framework.ts +164 -0
  878. package/src/connectors/live/github.test.ts +1218 -0
  879. package/src/connectors/live/github.ts +1068 -0
  880. package/src/connectors/live/gmail.test.ts +1706 -0
  881. package/src/connectors/live/gmail.ts +1293 -0
  882. package/src/connectors/live/google-drive.test.ts +696 -0
  883. package/src/connectors/live/google-drive.ts +724 -0
  884. package/src/connectors/live/index.ts +101 -0
  885. package/src/connectors/live/live-connectors.test.ts +689 -0
  886. package/src/connectors/live/notion.test.ts +1109 -0
  887. package/src/connectors/live/notion.ts +978 -0
  888. package/src/connectors/live/registry.ts +103 -0
  889. package/src/connectors/live/state-store.ts +399 -0
  890. package/src/connectors/live/transient-errors.ts +150 -0
  891. package/src/connectors/weclone-installer.test.ts +850 -0
  892. package/src/connectors-cli.ts +513 -0
  893. package/src/console/state.test.ts +224 -0
  894. package/src/console/state.ts +514 -0
  895. package/src/console/trace.test.ts +813 -0
  896. package/src/console/trace.ts +603 -0
  897. package/src/console/tui.test.ts +582 -0
  898. package/src/console/tui.ts +508 -0
  899. package/src/consolidation-operator.ts +182 -0
  900. package/src/consolidation-provenance-check.ts +551 -0
  901. package/src/consolidation-undo.ts +718 -0
  902. package/src/contradiction/contradiction-judge.test.ts +189 -0
  903. package/src/contradiction/contradiction-judge.ts +333 -0
  904. package/src/contradiction/contradiction-review.ts +574 -0
  905. package/src/contradiction/contradiction-scan.ts +504 -0
  906. package/src/contradiction/contradiction.test.ts +2230 -0
  907. package/src/contradiction/index.ts +37 -0
  908. package/src/contradiction/resolution.ts +383 -0
  909. package/src/conversation-index/backend.ts +323 -0
  910. package/src/conversation-index/chunker.ts +47 -0
  911. package/src/conversation-index/cleanup.ts +53 -0
  912. package/src/conversation-index/faiss-adapter.ts +384 -0
  913. package/src/conversation-index/indexer.test.ts +164 -0
  914. package/src/conversation-index/indexer.ts +192 -0
  915. package/src/conversation-index/search.ts +37 -0
  916. package/src/cross-namespace-budget.test.ts +275 -0
  917. package/src/cross-namespace-budget.ts +365 -0
  918. package/src/cue-anchors.ts +163 -0
  919. package/src/curation/index.ts +544 -0
  920. package/src/dashboard-runtime.ts +337 -0
  921. package/src/day-summary.ts +122 -0
  922. package/src/dedup/index.ts +330 -0
  923. package/src/dedup/semantic.test.ts +1577 -0
  924. package/src/dedup/semantic.ts +148 -0
  925. package/src/delinearize.ts +193 -0
  926. package/src/direct-answer-wiring.test.ts +473 -0
  927. package/src/direct-answer-wiring.ts +180 -0
  928. package/src/direct-answer.test.ts +484 -0
  929. package/src/direct-answer.ts +273 -0
  930. package/src/embedding-fallback.ts +565 -0
  931. package/src/enrichment/audit.ts +89 -0
  932. package/src/enrichment/index.ts +27 -0
  933. package/src/enrichment/pipeline.ts +197 -0
  934. package/src/enrichment/provider-registry.ts +85 -0
  935. package/src/enrichment/types.ts +100 -0
  936. package/src/enrichment/web-search-provider.ts +63 -0
  937. package/src/entity-retrieval.ts +774 -0
  938. package/src/entity-schema.ts +239 -0
  939. package/src/evals.ts +1312 -0
  940. package/src/event-order-recall.test.ts +4164 -0
  941. package/src/event-order-recall.ts +2802 -0
  942. package/src/evidence-pack.test.ts +89 -0
  943. package/src/evidence-pack.ts +388 -0
  944. package/src/explicit-capture.ts +530 -0
  945. package/src/explicit-cue-recall.test.ts +3019 -0
  946. package/src/explicit-cue-recall.ts +5545 -0
  947. package/src/extraction-judge-telemetry.ts +234 -0
  948. package/src/extraction-judge-training.ts +221 -0
  949. package/src/extraction-judge.ts +846 -0
  950. package/src/extraction-timeout.test.ts +265 -0
  951. package/src/extraction.ts +2719 -0
  952. package/src/fallback-llm.test.ts +1060 -0
  953. package/src/fallback-llm.ts +918 -0
  954. package/src/focused-list-recall.test.ts +734 -0
  955. package/src/focused-list-recall.ts +1160 -0
  956. package/src/graph-dashboard-diff.ts +35 -0
  957. package/src/graph-dashboard-key.ts +5 -0
  958. package/src/graph-dashboard-parser.ts +104 -0
  959. package/src/graph-edge-reinforcement.ts +192 -0
  960. package/src/graph-events.ts +151 -0
  961. package/src/graph-recall.test.ts +164 -0
  962. package/src/graph-recall.ts +189 -0
  963. package/src/graph-retrieval.test.ts +809 -0
  964. package/src/graph-retrieval.ts +823 -0
  965. package/src/graph-snapshot.ts +329 -0
  966. package/src/graph.ts +813 -0
  967. package/src/harmonic-retrieval.ts +223 -0
  968. package/src/himem.ts +154 -0
  969. package/src/hygiene.ts +87 -0
  970. package/src/identity-continuity.ts +333 -0
  971. package/src/importance.ts +328 -0
  972. package/src/importers/base.test.ts +294 -0
  973. package/src/importers/base.ts +436 -0
  974. package/src/importers/index.ts +21 -0
  975. package/src/index.ts +1204 -0
  976. package/src/intent.ts +154 -0
  977. package/src/json-extract.ts +85 -0
  978. package/src/json-store.ts +42 -0
  979. package/src/lcm/archive.ts +617 -0
  980. package/src/lcm/dag.ts +199 -0
  981. package/src/lcm/engine.ts +645 -0
  982. package/src/lcm/index.ts +7 -0
  983. package/src/lcm/queue.test.ts +178 -0
  984. package/src/lcm/queue.ts +200 -0
  985. package/src/lcm/recall.ts +117 -0
  986. package/src/lcm/schema.ts +154 -0
  987. package/src/lcm/summarizer.ts +235 -0
  988. package/src/lcm/tools.ts +191 -0
  989. package/src/lcm-engine.test.ts +660 -0
  990. package/src/legacy-hook-compat.test.ts +20 -0
  991. package/src/legacy-hook-compat.ts +45 -0
  992. package/src/lifecycle.ts +289 -0
  993. package/src/live-connectors-runner.ts +385 -0
  994. package/src/local-llm-qos.test.ts +303 -0
  995. package/src/local-llm-thinking.test.ts +292 -0
  996. package/src/local-llm.ts +1464 -0
  997. package/src/logger.ts +49 -0
  998. package/src/maintenance/archive-observations.ts +147 -0
  999. package/src/maintenance/backup-stamp.ts +3 -0
  1000. package/src/maintenance/dreams-ledger.ts +516 -0
  1001. package/src/maintenance/first-start-migration.ts +362 -0
  1002. package/src/maintenance/forget.test.ts +206 -0
  1003. package/src/maintenance/forget.ts +126 -0
  1004. package/src/maintenance/graph-edge-decay.test.ts +409 -0
  1005. package/src/maintenance/graph-edge-decay.ts +394 -0
  1006. package/src/maintenance/memory-governance-cron.ts +447 -0
  1007. package/src/maintenance/memory-governance.ts +1039 -0
  1008. package/src/maintenance/migrate-observations.ts +216 -0
  1009. package/src/maintenance/observation-ledger-utils.ts +54 -0
  1010. package/src/maintenance/pattern-reinforcement.test.ts +875 -0
  1011. package/src/maintenance/pattern-reinforcement.ts +369 -0
  1012. package/src/maintenance/purge.ts +334 -0
  1013. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
  1014. package/src/maintenance/rebuild-memory-projection.ts +1234 -0
  1015. package/src/maintenance/rebuild-observations.ts +178 -0
  1016. package/src/maintenance/tier-stats.test.ts +378 -0
  1017. package/src/maintenance/tier-stats.ts +222 -0
  1018. package/src/mcp-memory-inspector-app.ts +421 -0
  1019. package/src/memory-action-policy.ts +80 -0
  1020. package/src/memory-cache.ts +208 -0
  1021. package/src/memory-extension/claude-code-publisher.ts +51 -0
  1022. package/src/memory-extension/codex-publisher.ts +149 -0
  1023. package/src/memory-extension/hermes-publisher.ts +51 -0
  1024. package/src/memory-extension/index.ts +100 -0
  1025. package/src/memory-extension/shared-instructions.ts +133 -0
  1026. package/src/memory-extension/types.ts +86 -0
  1027. package/src/memory-extension-host/host-discovery.ts +276 -0
  1028. package/src/memory-extension-host/index.ts +14 -0
  1029. package/src/memory-extension-host/render-extensions-block.ts +73 -0
  1030. package/src/memory-extension-host/types.ts +21 -0
  1031. package/src/memory-lifecycle-ledger-utils.ts +116 -0
  1032. package/src/memory-projection-format.ts +11 -0
  1033. package/src/memory-projection-store.ts +951 -0
  1034. package/src/memory-provenance.test.ts +196 -0
  1035. package/src/memory-provenance.ts +484 -0
  1036. package/src/memory-worth-bench.test.ts +71 -0
  1037. package/src/memory-worth-bench.ts +265 -0
  1038. package/src/memory-worth-filter.test.ts +209 -0
  1039. package/src/memory-worth-filter.ts +204 -0
  1040. package/src/memory-worth-frontmatter.test.ts +311 -0
  1041. package/src/memory-worth-outcomes.test.ts +316 -0
  1042. package/src/memory-worth-outcomes.ts +286 -0
  1043. package/src/memory-worth.test.ts +317 -0
  1044. package/src/memory-worth.ts +215 -0
  1045. package/src/message-parts/index.ts +806 -0
  1046. package/src/message-parts/message-parts.test.ts +421 -0
  1047. package/src/migrate/from-engram.ts +789 -0
  1048. package/src/model-registry.ts +313 -0
  1049. package/src/models-json.ts +76 -0
  1050. package/src/namespaces/migrate.ts +187 -0
  1051. package/src/namespaces/path.ts +25 -0
  1052. package/src/namespaces/principal.test.ts +195 -0
  1053. package/src/namespaces/principal.ts +86 -0
  1054. package/src/namespaces/search.test.ts +105 -0
  1055. package/src/namespaces/search.ts +233 -0
  1056. package/src/namespaces/storage.ts +74 -0
  1057. package/src/native-knowledge.ts +1823 -0
  1058. package/src/negative.ts +72 -0
  1059. package/src/network/tailscale.ts +179 -0
  1060. package/src/network/webdav.ts +385 -0
  1061. package/src/objective-state-writers.ts +951 -0
  1062. package/src/objective-state.ts +320 -0
  1063. package/src/onboarding/index.ts +529 -0
  1064. package/src/openai-chat-compat.ts +56 -0
  1065. package/src/operator-toolkit.ts +2132 -0
  1066. package/src/opik-exporter.test.ts +72 -0
  1067. package/src/opik-exporter.ts +587 -0
  1068. package/src/orchestrator-extraction-queue.test.ts +197 -0
  1069. package/src/orchestrator-flush.test.ts +1171 -0
  1070. package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
  1071. package/src/orchestrator-source-attribution.test.ts +701 -0
  1072. package/src/orchestrator.ts +16368 -0
  1073. package/src/page-versioning.ts +450 -0
  1074. package/src/patterns-cli.ts +574 -0
  1075. package/src/peers/index.ts +54 -0
  1076. package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
  1077. package/src/peers/migrate-from-identity-anchor.ts +350 -0
  1078. package/src/peers/peers.test.ts +419 -0
  1079. package/src/peers/profile-reasoner.ts +694 -0
  1080. package/src/peers/storage.ts +1350 -0
  1081. package/src/peers/types.ts +138 -0
  1082. package/src/plugin-id.ts +84 -0
  1083. package/src/policy-runtime.ts +209 -0
  1084. package/src/procedural/procedure-miner.ts +150 -0
  1085. package/src/procedural/procedure-recall.ts +93 -0
  1086. package/src/procedural/procedure-stats.ts +213 -0
  1087. package/src/procedural/procedure-types.ts +132 -0
  1088. package/src/procedural/reinforcement-core.test.ts +132 -0
  1089. package/src/procedural/reinforcement-core.ts +73 -0
  1090. package/src/profiling.test.ts +263 -0
  1091. package/src/profiling.ts +435 -0
  1092. package/src/projection/index.ts +398 -0
  1093. package/src/qmd-recall-cache.test.ts +138 -0
  1094. package/src/qmd-recall-cache.ts +111 -0
  1095. package/src/qmd.test.ts +257 -0
  1096. package/src/qmd.ts +2614 -0
  1097. package/src/reasoning-trace-recall.ts +201 -0
  1098. package/src/reasoning-trace-types.ts +235 -0
  1099. package/src/recall-audit-anomaly.test.ts +246 -0
  1100. package/src/recall-audit-anomaly.ts +297 -0
  1101. package/src/recall-audit.test.ts +51 -0
  1102. package/src/recall-audit.ts +72 -0
  1103. package/src/recall-budget-config.test.ts +87 -0
  1104. package/src/recall-disclosure-escalation.test.ts +196 -0
  1105. package/src/recall-disclosure-escalation.ts +158 -0
  1106. package/src/recall-disclosure-shaping.test.ts +146 -0
  1107. package/src/recall-disclosure.test.ts +214 -0
  1108. package/src/recall-explain-renderer.test.ts +140 -0
  1109. package/src/recall-explain-renderer.ts +356 -0
  1110. package/src/recall-mmr.test.ts +808 -0
  1111. package/src/recall-mmr.ts +607 -0
  1112. package/src/recall-qos.test.ts +85 -0
  1113. package/src/recall-qos.ts +82 -0
  1114. package/src/recall-query-policy.ts +221 -0
  1115. package/src/recall-state.test.ts +233 -0
  1116. package/src/recall-state.ts +456 -0
  1117. package/src/recall-tag-filter.ts +143 -0
  1118. package/src/recall-tokenization.ts +35 -0
  1119. package/src/recall-xray-cli.test.ts +118 -0
  1120. package/src/recall-xray-cli.ts +100 -0
  1121. package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
  1122. package/src/recall-xray-renderer.test.ts +539 -0
  1123. package/src/recall-xray-renderer.ts +487 -0
  1124. package/src/recall-xray.test.ts +503 -0
  1125. package/src/recall-xray.ts +621 -0
  1126. package/src/reconstruct.ts +41 -0
  1127. package/src/release-changelog.ts +35 -0
  1128. package/src/relevance.ts +67 -0
  1129. package/src/replay/normalizers/chatgpt.ts +133 -0
  1130. package/src/replay/normalizers/claude.ts +102 -0
  1131. package/src/replay/normalizers/openclaw.ts +119 -0
  1132. package/src/replay/normalizers/shared.ts +69 -0
  1133. package/src/replay/runner.ts +197 -0
  1134. package/src/replay/types.ts +143 -0
  1135. package/src/rerank.test.ts +48 -0
  1136. package/src/rerank.ts +176 -0
  1137. package/src/resolve-auth-token.test.ts +226 -0
  1138. package/src/resolve-auth-token.ts +151 -0
  1139. package/src/resolve-provider-secret.test.ts +187 -0
  1140. package/src/resolve-provider-secret.ts +410 -0
  1141. package/src/response-guidance-recall.test.ts +3952 -0
  1142. package/src/response-guidance-recall.ts +4431 -0
  1143. package/src/resume-bundles.ts +415 -0
  1144. package/src/retrieval-agents.ts +623 -0
  1145. package/src/retrieval-tiers.ts +25 -0
  1146. package/src/retrieval.ts +104 -0
  1147. package/src/review/index.test.ts +201 -0
  1148. package/src/review/index.ts +536 -0
  1149. package/src/routing/engine.ts +162 -0
  1150. package/src/routing/store.ts +321 -0
  1151. package/src/runtime/better-sqlite.test.ts +32 -0
  1152. package/src/runtime/better-sqlite.ts +76 -0
  1153. package/src/runtime/child-process.ts +67 -0
  1154. package/src/runtime/env.ts +48 -0
  1155. package/src/sanitize.ts +58 -0
  1156. package/src/schemas.ts +449 -0
  1157. package/src/sdk-compat.ts +87 -0
  1158. package/src/search/document-scanner.ts +96 -0
  1159. package/src/search/embed-helper.ts +142 -0
  1160. package/src/search/factory.ts +189 -0
  1161. package/src/search/index.ts +10 -0
  1162. package/src/search/lancedb-backend.ts +342 -0
  1163. package/src/search/meilisearch-backend.ts +232 -0
  1164. package/src/search/noop-backend.ts +57 -0
  1165. package/src/search/orama-backend.ts +358 -0
  1166. package/src/search/port.ts +86 -0
  1167. package/src/search/remote-backend.ts +124 -0
  1168. package/src/secure-store/cipher.ts +271 -0
  1169. package/src/secure-store/cli-handlers.ts +355 -0
  1170. package/src/secure-store/cli-renderer.ts +131 -0
  1171. package/src/secure-store/header.ts +373 -0
  1172. package/src/secure-store/index.ts +137 -0
  1173. package/src/secure-store/kdf.ts +263 -0
  1174. package/src/secure-store/keyring.ts +106 -0
  1175. package/src/secure-store/metadata.ts +394 -0
  1176. package/src/secure-store/passphrase-reader.ts +252 -0
  1177. package/src/secure-store/secure-fs.ts +571 -0
  1178. package/src/secure-store/secure-store.test.ts +755 -0
  1179. package/src/semantic-chunking.ts +545 -0
  1180. package/src/semantic-consolidation.test.ts +182 -0
  1181. package/src/semantic-consolidation.ts +432 -0
  1182. package/src/semantic-rule-promotion.ts +183 -0
  1183. package/src/semantic-rule-verifier.ts +160 -0
  1184. package/src/session-integrity.ts +569 -0
  1185. package/src/session-observer-bands.ts +11 -0
  1186. package/src/session-observer-state.ts +346 -0
  1187. package/src/session-toggles.test.ts +96 -0
  1188. package/src/session-toggles.ts +159 -0
  1189. package/src/shared-context/manager.ts +810 -0
  1190. package/src/signal.ts +84 -0
  1191. package/src/skills-registry.test.ts +277 -0
  1192. package/src/skills-registry.ts +120 -0
  1193. package/src/source-attribution-roundtrip.test.ts +215 -0
  1194. package/src/source-attribution.test.ts +1425 -0
  1195. package/src/source-attribution.ts +639 -0
  1196. package/src/spaces/index.ts +627 -0
  1197. package/src/storage-paths.ts +117 -0
  1198. package/src/storage.ts +6657 -0
  1199. package/src/store-contract.ts +55 -0
  1200. package/src/summarizer.ts +844 -0
  1201. package/src/summary-snapshot.test.ts +681 -0
  1202. package/src/summary-snapshot.ts +238 -0
  1203. package/src/surfaces/dreams.test.ts +394 -0
  1204. package/src/surfaces/dreams.ts +346 -0
  1205. package/src/surfaces/heartbeat.test.ts +415 -0
  1206. package/src/surfaces/heartbeat.ts +325 -0
  1207. package/src/sync/index.ts +308 -0
  1208. package/src/targeted-fact-recall.test.ts +1694 -0
  1209. package/src/targeted-fact-recall.ts +2905 -0
  1210. package/src/taxonomy/default-taxonomy.ts +87 -0
  1211. package/src/taxonomy/index.ts +26 -0
  1212. package/src/taxonomy/resolver-doc-generator.ts +57 -0
  1213. package/src/taxonomy/resolver.ts +184 -0
  1214. package/src/taxonomy/taxonomy-loader.ts +186 -0
  1215. package/src/taxonomy/types.ts +48 -0
  1216. package/src/telemetry-transcript.ts +70 -0
  1217. package/src/temporal-index.ts +890 -0
  1218. package/src/temporal-supersession.test.ts +2703 -0
  1219. package/src/temporal-supersession.ts +493 -0
  1220. package/src/temporal-validity.test.ts +448 -0
  1221. package/src/temporal-validity.ts +123 -0
  1222. package/src/threading.ts +395 -0
  1223. package/src/tier-migration.ts +124 -0
  1224. package/src/tier-routing.ts +102 -0
  1225. package/src/tmt.ts +462 -0
  1226. package/src/tokens.test.ts +178 -0
  1227. package/src/tokens.ts +279 -0
  1228. package/src/topics.ts +147 -0
  1229. package/src/training-export/cli-date-validation.test.ts +258 -0
  1230. package/src/training-export/converter.test.ts +452 -0
  1231. package/src/training-export/converter.ts +319 -0
  1232. package/src/training-export/date-parse.ts +117 -0
  1233. package/src/training-export/index.ts +26 -0
  1234. package/src/training-export/registry.test.ts +85 -0
  1235. package/src/training-export/registry.ts +57 -0
  1236. package/src/training-export/types.ts +31 -0
  1237. package/src/transcript.ts +1179 -0
  1238. package/src/transfer/autodetect.ts +30 -0
  1239. package/src/transfer/backup.ts +138 -0
  1240. package/src/transfer/capsule-crypto.ts +485 -0
  1241. package/src/transfer/capsule-encrypt.test.ts +690 -0
  1242. package/src/transfer/capsule-export.ts +543 -0
  1243. package/src/transfer/capsule-fork.ts +375 -0
  1244. package/src/transfer/capsule-import.ts +564 -0
  1245. package/src/transfer/capsule-merge.ts +433 -0
  1246. package/src/transfer/conflict-policy.ts +16 -0
  1247. package/src/transfer/constants.ts +13 -0
  1248. package/src/transfer/exclusions.ts +37 -0
  1249. package/src/transfer/export-json.ts +65 -0
  1250. package/src/transfer/export-md.ts +59 -0
  1251. package/src/transfer/export-sqlite.ts +52 -0
  1252. package/src/transfer/fs-utils.ts +269 -0
  1253. package/src/transfer/import-json.ts +108 -0
  1254. package/src/transfer/import-md.ts +84 -0
  1255. package/src/transfer/import-sqlite.ts +100 -0
  1256. package/src/transfer/integrity.ts +71 -0
  1257. package/src/transfer/sqlite-schema.ts +16 -0
  1258. package/src/transfer/types.ts +297 -0
  1259. package/src/trust-zones.ts +1186 -0
  1260. package/src/types.ts +3074 -0
  1261. package/src/user-model.test.ts +124 -0
  1262. package/src/user-model.ts +162 -0
  1263. package/src/utility-learner.ts +353 -0
  1264. package/src/utility-runtime.ts +88 -0
  1265. package/src/utility-telemetry.ts +215 -0
  1266. package/src/utils/category-dir.ts +44 -0
  1267. package/src/utils/errno.ts +6 -0
  1268. package/src/utils/iso-timestamp.test.ts +37 -0
  1269. package/src/utils/iso-timestamp.ts +164 -0
  1270. package/src/utils/path.ts +26 -0
  1271. package/src/verified-recall.ts +138 -0
  1272. package/src/version-utils.test.ts +10 -0
  1273. package/src/version-utils.ts +9 -0
  1274. package/src/whitespace.ts +10 -0
  1275. package/src/work/board.ts +359 -0
  1276. package/src/work/boundary.ts +107 -0
  1277. package/src/work/storage.ts +436 -0
  1278. package/src/work/types.ts +82 -0
  1279. package/src/work-product-ledger.ts +265 -0
  1280. package/dist/access-service-BkXt3di1.d.ts +0 -2039
  1281. package/dist/capsule-crypto-SJS5VVAP.js +0 -18
  1282. package/dist/capsule-export-LLEVB2RG.js +0 -17
  1283. package/dist/capsule-import-UW45R2MZ.js +0 -16
  1284. package/dist/capsule-merge-DI7PNQ2H.js +0 -189
  1285. package/dist/chunk-2LGMW3DJ.js +0 -111
  1286. package/dist/chunk-2YMTO4ZJ.js +0 -265
  1287. package/dist/chunk-2YMTO4ZJ.js.map +0 -1
  1288. package/dist/chunk-363MWCD3.js +0 -9683
  1289. package/dist/chunk-363MWCD3.js.map +0 -1
  1290. package/dist/chunk-36CTNQY7.js +0 -1554
  1291. package/dist/chunk-36CTNQY7.js.map +0 -1
  1292. package/dist/chunk-457A4P3L.js +0 -119
  1293. package/dist/chunk-457A4P3L.js.map +0 -1
  1294. package/dist/chunk-4DXC6HQQ.js +0 -1837
  1295. package/dist/chunk-4DXC6HQQ.js.map +0 -1
  1296. package/dist/chunk-4IS4SXIQ.js +0 -2040
  1297. package/dist/chunk-57QNCUEZ.js +0 -1914
  1298. package/dist/chunk-57QNCUEZ.js.map +0 -1
  1299. package/dist/chunk-6AUUAZEX.js +0 -150
  1300. package/dist/chunk-6AUUAZEX.js.map +0 -1
  1301. package/dist/chunk-6TBWYBJ3.js +0 -236
  1302. package/dist/chunk-6XA7UN4Z.js +0 -135
  1303. package/dist/chunk-6Z6UH6TK.js +0 -2129
  1304. package/dist/chunk-6Z6UH6TK.js.map +0 -1
  1305. package/dist/chunk-74EMIVE4.js +0 -329
  1306. package/dist/chunk-74EMIVE4.js.map +0 -1
  1307. package/dist/chunk-74WWN7ZW.js +0 -82
  1308. package/dist/chunk-74WWN7ZW.js.map +0 -1
  1309. package/dist/chunk-767ODGE6.js +0 -183
  1310. package/dist/chunk-A4ACKWIW.js +0 -289
  1311. package/dist/chunk-A4ACKWIW.js.map +0 -1
  1312. package/dist/chunk-ASAITVLA.js +0 -64
  1313. package/dist/chunk-ASAITVLA.js.map +0 -1
  1314. package/dist/chunk-C5HUWVH2.js +0 -891
  1315. package/dist/chunk-C5HUWVH2.js.map +0 -1
  1316. package/dist/chunk-D54LZC5L.js +0 -147
  1317. package/dist/chunk-DF3RVK3X.js +0 -119
  1318. package/dist/chunk-DF3RVK3X.js.map +0 -1
  1319. package/dist/chunk-E6K4NIEU.js +0 -747
  1320. package/dist/chunk-E6K4NIEU.js.map +0 -1
  1321. package/dist/chunk-EEQLFRUM.js +0 -89
  1322. package/dist/chunk-EQINRHYR.js +0 -672
  1323. package/dist/chunk-EQINRHYR.js.map +0 -1
  1324. package/dist/chunk-ETOW6ACV.js +0 -158
  1325. package/dist/chunk-ETOW6ACV.js.map +0 -1
  1326. package/dist/chunk-EYNQTST2.js +0 -721
  1327. package/dist/chunk-FYIYMQ5N.js +0 -221
  1328. package/dist/chunk-FYIYMQ5N.js.map +0 -1
  1329. package/dist/chunk-G2WADRQ3.js +0 -219
  1330. package/dist/chunk-G4SK7DSQ.js +0 -121
  1331. package/dist/chunk-GGD5W7TB.js +0 -105
  1332. package/dist/chunk-GGD5W7TB.js.map +0 -1
  1333. package/dist/chunk-GVPWB7EY.js +0 -390
  1334. package/dist/chunk-GVPWB7EY.js.map +0 -1
  1335. package/dist/chunk-HJYHRE4S.js +0 -647
  1336. package/dist/chunk-I6BQZSML.js +0 -1451
  1337. package/dist/chunk-I6BQZSML.js.map +0 -1
  1338. package/dist/chunk-IBX3VFOM.js +0 -446
  1339. package/dist/chunk-IBX3VFOM.js.map +0 -1
  1340. package/dist/chunk-IXEJRKCZ.js +0 -18
  1341. package/dist/chunk-JBMSGZEQ.js +0 -441
  1342. package/dist/chunk-JBMSGZEQ.js.map +0 -1
  1343. package/dist/chunk-JRNQ3RNA.js +0 -284
  1344. package/dist/chunk-JRNQ3RNA.js.map +0 -1
  1345. package/dist/chunk-K6WK37A6.js +0 -865
  1346. package/dist/chunk-K6WK37A6.js.map +0 -1
  1347. package/dist/chunk-KBYWQWSB.js +0 -271
  1348. package/dist/chunk-KUHRUM6B.js +0 -14397
  1349. package/dist/chunk-KUHRUM6B.js.map +0 -1
  1350. package/dist/chunk-KWBPHZUU.js +0 -83
  1351. package/dist/chunk-KWBPHZUU.js.map +0 -1
  1352. package/dist/chunk-LIO5X3CM.js +0 -596
  1353. package/dist/chunk-MARWOCVP.js +0 -48
  1354. package/dist/chunk-MCC6KDQF.js +0 -5095
  1355. package/dist/chunk-MCC6KDQF.js.map +0 -1
  1356. package/dist/chunk-N5AKDXAI.js +0 -74
  1357. package/dist/chunk-NN3LPQ5D.js +0 -936
  1358. package/dist/chunk-NN3LPQ5D.js.map +0 -1
  1359. package/dist/chunk-O4XJUPSF.js +0 -533
  1360. package/dist/chunk-O4XJUPSF.js.map +0 -1
  1361. package/dist/chunk-OA3L7BFR.js +0 -183
  1362. package/dist/chunk-OA3L7BFR.js.map +0 -1
  1363. package/dist/chunk-OR64ZGRZ.js +0 -23
  1364. package/dist/chunk-P73JTV34.js +0 -275
  1365. package/dist/chunk-P73JTV34.js.map +0 -1
  1366. package/dist/chunk-P77UEOU2.js +0 -1521
  1367. package/dist/chunk-P77UEOU2.js.map +0 -1
  1368. package/dist/chunk-PB5KW5PL.js +0 -118
  1369. package/dist/chunk-PHNGXFQ6.js +0 -623
  1370. package/dist/chunk-PHNGXFQ6.js.map +0 -1
  1371. package/dist/chunk-QIGOEM65.js +0 -228
  1372. package/dist/chunk-RXTFCYQF.js +0 -108
  1373. package/dist/chunk-S2JJBLJG.js +0 -2101
  1374. package/dist/chunk-S2JJBLJG.js.map +0 -1
  1375. package/dist/chunk-S3IP6R6K.js +0 -219
  1376. package/dist/chunk-S3IP6R6K.js.map +0 -1
  1377. package/dist/chunk-SRBJUAMP.js +0 -403
  1378. package/dist/chunk-SRBJUAMP.js.map +0 -1
  1379. package/dist/chunk-URB2WSKZ.js +0 -350
  1380. package/dist/chunk-URB2WSKZ.js.map +0 -1
  1381. package/dist/chunk-VQXK37XA.js +0 -26
  1382. package/dist/chunk-VQXK37XA.js.map +0 -1
  1383. package/dist/chunk-VTU2B4VF.js +0 -146
  1384. package/dist/chunk-VTU2B4VF.js.map +0 -1
  1385. package/dist/chunk-VX2IUQFE.js +0 -613
  1386. package/dist/chunk-VX2IUQFE.js.map +0 -1
  1387. package/dist/chunk-WGK4VHGP.js +0 -4292
  1388. package/dist/chunk-WGK4VHGP.js.map +0 -1
  1389. package/dist/chunk-WTFWLUSX.js +0 -827
  1390. package/dist/chunk-WTFWLUSX.js.map +0 -1
  1391. package/dist/chunk-XJKFSSDW.js +0 -726
  1392. package/dist/chunk-XJKFSSDW.js.map +0 -1
  1393. package/dist/chunk-XMHBH5H6.js +0 -283
  1394. package/dist/chunk-XMHBH5H6.js.map +0 -1
  1395. package/dist/chunk-XMVFHBHT.js +0 -277
  1396. package/dist/chunk-Y5KDIOKF.js +0 -2403
  1397. package/dist/chunk-Y5KDIOKF.js.map +0 -1
  1398. package/dist/chunk-YNB73F22.js +0 -137
  1399. package/dist/chunk-YNB73F22.js.map +0 -1
  1400. package/dist/chunk-Z2E7VW55.js +0 -335
  1401. package/dist/chunk-Z2E7VW55.js.map +0 -1
  1402. package/dist/chunk-Z5S5HNGY.js +0 -2280
  1403. package/dist/chunk-Z5S5HNGY.js.map +0 -1
  1404. package/dist/chunk-ZL4S7ARC.js +0 -53
  1405. package/dist/chunk-ZTSE2ZJ6.js +0 -190
  1406. package/dist/chunk-ZTSE2ZJ6.js.map +0 -1
  1407. package/dist/cli-Cvy2SNhF.d.ts +0 -1259
  1408. package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
  1409. package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
  1410. package/dist/contradiction-review-5LTTVDQV.js +0 -22
  1411. package/dist/contradiction-scan-3Z6YW7YA.js +0 -413
  1412. package/dist/contradiction-scan-3Z6YW7YA.js.map +0 -1
  1413. package/dist/engine-FOC3IJLA.js +0 -28
  1414. package/dist/fs-utils-IRVUFB6G.js +0 -30
  1415. package/dist/graph-edge-decay-PWB63GRE.js +0 -207
  1416. package/dist/index-1qIcnbG1.d.ts +0 -34
  1417. package/dist/memory-governance-F3QOJGEY.js +0 -37
  1418. package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
  1419. package/dist/orchestrator-AOQMo7QI.d.ts +0 -1784
  1420. package/dist/path-RMTY5Y5A.js +0 -9
  1421. package/dist/port-B6VEDIkC.d.ts +0 -53
  1422. package/dist/resolution-YGIBORXI.js +0 -101
  1423. package/dist/resolution-YGIBORXI.js.map +0 -1
  1424. package/dist/secure-store-4R2GSO7S.js +0 -156
  1425. package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
  1426. package/dist/state-store-3EH7HYIN.js +0 -16
  1427. package/dist/types-V3FJ26TF.js +0 -30
  1428. /package/dist/{capsule-crypto-SJS5VVAP.js.map → action-confidence.js.map} +0 -0
  1429. /package/dist/{capsule-export-LLEVB2RG.js.map → adapters/claude-code.js.map} +0 -0
  1430. /package/dist/{capsule-import-UW45R2MZ.js.map → adapters/codex.js.map} +0 -0
  1431. /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/hermes.js.map} +0 -0
  1432. /package/dist/{engine-FOC3IJLA.js.map → adapters/index.js.map} +0 -0
  1433. /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/registry.js.map} +0 -0
  1434. /package/dist/{memory-governance-F3QOJGEY.js.map → adapters/replit.js.map} +0 -0
  1435. /package/dist/{path-RMTY5Y5A.js.map → adapters/types.js.map} +0 -0
  1436. /package/dist/{secure-store-4R2GSO7S.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  1437. /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
  1438. /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
  1439. /package/dist/{chunk-KBYWQWSB.js.map → chunk-4CRG46BG.js.map} +0 -0
  1440. /package/dist/{chunk-LIO5X3CM.js.map → chunk-7IASACLB.js.map} +0 -0
  1441. /package/dist/{chunk-EYNQTST2.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
  1442. /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
  1443. /package/dist/{chunk-QIGOEM65.js.map → chunk-GGKRUQOO.js.map} +0 -0
  1444. /package/dist/{chunk-HJYHRE4S.js.map → chunk-GL6I6MEQ.js.map} +0 -0
  1445. /package/dist/{state-store-3EH7HYIN.js.map → chunk-HHLLAQGZ.js.map} +0 -0
  1446. /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
  1447. /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
  1448. /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
  1449. /package/dist/{chunk-PB5KW5PL.js.map → chunk-MC26UJIM.js.map} +0 -0
  1450. /package/dist/{chunk-ZL4S7ARC.js.map → chunk-MT4HVDUZ.js.map} +0 -0
  1451. /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
  1452. /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
  1453. /package/dist/{chunk-RXTFCYQF.js.map → chunk-P4NEIHUT.js.map} +0 -0
  1454. /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
  1455. /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
  1456. /package/dist/{types-V3FJ26TF.js.map → chunk-SEDEKFYQ.js.map} +0 -0
  1457. /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
  1458. /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
  1459. /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
  1460. /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
  1461. /package/dist/{chunk-6XA7UN4Z.js.map → chunk-XVZ7B3HG.js.map} +0 -0
  1462. /package/dist/{graph-edge-decay-PWB63GRE.js.map → graph-edge-decay-5DI5GUNL.js.map} +0 -0
package/src/qmd.ts ADDED
@@ -0,0 +1,2614 @@
1
+ import { createHash } from "node:crypto";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { log } from "./logger.js";
5
+ import { getCachedQmdSearch, setCachedQmdSearch } from "./memory-cache.js";
6
+ import {
7
+ abortError,
8
+ isAbortError,
9
+ throwIfAborted,
10
+ } from "./abort-error.js";
11
+ import type { QmdSearchExplain, QmdSearchResult } from "./types.js";
12
+ import type { SearchBackend, SearchExecutionOptions, SearchQueryOptions } from "./search/port.js";
13
+ import { launchProcess, type CommandChildProcess } from "./runtime/child-process.js";
14
+ import { mergeEnv } from "./runtime/env.js";
15
+
16
+ export interface QmdClientOptions {
17
+ slowLog?: { enabled: boolean; thresholdMs: number };
18
+ updateTimeoutMs?: number;
19
+ updateMinIntervalMs?: number;
20
+ qmdPath?: string;
21
+ daemonUrl?: string;
22
+ daemonRecheckIntervalMs?: number;
23
+ qmdSupportedVersion?: string;
24
+ qmdAutoUpgradeEnabled?: boolean;
25
+ qmdAutoUpgradeCheckIntervalMs?: number;
26
+ qmdChunkStrategy?: QmdChunkStrategy;
27
+ qmdCandidateLimit?: number;
28
+ qmdQueryRerankEnabled?: boolean;
29
+ qmdIndexName?: string;
30
+ qmdForceCpu?: boolean;
31
+ qmdGpuBackend?: "auto" | "metal" | "cuda" | "vulkan" | "false";
32
+ qmdEmbedParallelism?: number;
33
+ qmdEmbedModel?: string;
34
+ qmdRerankModel?: string;
35
+ qmdGenerateModel?: string;
36
+ }
37
+
38
+ export type QmdVersionTuple = [number, number, number];
39
+ export type QmdChunkStrategy = "auto" | "regex";
40
+ export type QmdStructuredSearchType = "lex" | "vec" | "hyde";
41
+ export interface QmdStructuredSearch {
42
+ type: QmdStructuredSearchType;
43
+ query: string;
44
+ }
45
+
46
+ export interface QmdCapabilities {
47
+ version: string | null;
48
+ parsedVersion: QmdVersionTuple | null;
49
+ stableSdk: boolean;
50
+ unifiedSearch: boolean;
51
+ getDocumentBody: boolean;
52
+ maintenanceApi: boolean;
53
+ legacySkillInstall: boolean;
54
+ intentHints: boolean;
55
+ explainTraces: boolean;
56
+ candidateLimit: boolean;
57
+ v2McpQueryTool: boolean;
58
+ structuredSearches: boolean;
59
+ queryRerankToggle: boolean;
60
+ chunkStrategy: boolean;
61
+ qmdBench: boolean;
62
+ perCollectionModels: boolean;
63
+ jsonLineNumbers: boolean;
64
+ editorLinks: boolean;
65
+ doctor: boolean;
66
+ versionedSkills: boolean;
67
+ absoluteSnippetLines: boolean;
68
+ fullQueryOutput: boolean;
69
+ forceCpu: boolean;
70
+ gpuBackendOverride: boolean;
71
+ embedParallelism: boolean;
72
+ modelEnvConsistency: boolean;
73
+ scopedEmbed: boolean;
74
+ safeStatusDeviceProbe: boolean;
75
+ mcpIndexSelection: boolean;
76
+ }
77
+
78
+ export interface QmdVersionStatus {
79
+ installedVersion: string | null;
80
+ supportedVersion: string;
81
+ supported: boolean;
82
+ newerThanSupported: boolean;
83
+ upgradeAvailable: boolean;
84
+ capabilities: QmdCapabilities;
85
+ }
86
+
87
+ export interface QmdDoctorReport {
88
+ available: boolean;
89
+ skipped?: string;
90
+ report?: unknown;
91
+ raw?: string;
92
+ error?: string;
93
+ }
94
+
95
+ const QMD_TIMEOUT_MS = 30_000;
96
+ // Daemon timeout for individual search calls. Keep well under RECALL_TIMEOUT_MS (75s) so a
97
+ // slow/loading daemon fails fast and the caller can return early rather than hanging.
98
+ // After the daemon has loaded its index (~90s for 75K files), actual searches complete in <3s.
99
+ // During the loading window, searches will timeout/return [] quickly — this is preferable to
100
+ // blocking the full 75s on every recall request.
101
+ // Note: keep this ≥ 5s to allow normal searches (post-load) to complete reliably.
102
+ const QMD_DAEMON_TIMEOUT_MS = 8_000;
103
+ const QMD_PROBE_TIMEOUT_MS = 8_000;
104
+ const QMD_UPDATE_BACKOFF_MS = 15 * 60 * 1000; // 15m
105
+ const QMD_EMBED_BACKOFF_MS = 60 * 60 * 1000; // 60m
106
+ const QMD_CLI_WARN_THROTTLE_MS = 15 * 60 * 1000; // 15m
107
+ export const QMD_SUPPORTED_VERSION = "2.5.1";
108
+ const QMD_PACKAGE_NAME = "@tobilu/qmd";
109
+ const QMD_AUTO_UPGRADE_TIMEOUT_MS = 120_000;
110
+ const QMD_AUTO_UPGRADE_CHECK_INTERVAL_MS = 24 * 60 * 60_000;
111
+ const QMD_STRUCTURED_HYDE_MAX_CHARS = 320;
112
+ const QMD_FALLBACK_PATHS = [
113
+ path.join(os.homedir(), ".bun", "bin", "qmd"),
114
+ "/usr/local/bin/qmd",
115
+ "/opt/homebrew/bin/qmd",
116
+ ];
117
+ const QMD_GLOBAL_STATE_KEY = "__openclawEngramQmdGlobalState";
118
+
119
+ type QmdGlobalState = {
120
+ warnedGlobalUpdateBehavior: boolean;
121
+ lastGlobalUpdateRunAtMs: number | null;
122
+ lastGlobalUpdateFailAtMs: number | null;
123
+ lastGlobalEmbedRunAtMs: number | null;
124
+ lastGlobalEmbedFailAtMs: number | null;
125
+ lastCliWarnAtMs: number | null;
126
+ lastUpdateByCollectionMs: Record<string, number>;
127
+ lastUpdateFailByCollectionMs: Record<string, number>;
128
+ lastEmbedByCollectionMs: Record<string, number>;
129
+ lastEmbedFailByCollectionMs: Record<string, number>;
130
+ lastAutoUpgradeCheckAtMs: number | null;
131
+ lastAutoUpgradeStatus: string | null;
132
+ lastAutoUpgradeCheckByTargetMs: Record<string, number>;
133
+ lastAutoUpgradeStatusByTarget: Record<string, string>;
134
+ };
135
+
136
+ type QmdRuntimeEnv = Record<string, string | undefined>;
137
+
138
+ function getGlobalQmdState(): QmdGlobalState {
139
+ const g = globalThis as any;
140
+ if (!g[QMD_GLOBAL_STATE_KEY]) {
141
+ g[QMD_GLOBAL_STATE_KEY] = {
142
+ warnedGlobalUpdateBehavior: false,
143
+ lastGlobalUpdateRunAtMs: null,
144
+ lastGlobalUpdateFailAtMs: null,
145
+ lastGlobalEmbedRunAtMs: null,
146
+ lastGlobalEmbedFailAtMs: null,
147
+ lastCliWarnAtMs: null,
148
+ lastUpdateByCollectionMs: {},
149
+ lastUpdateFailByCollectionMs: {},
150
+ lastEmbedByCollectionMs: {},
151
+ lastEmbedFailByCollectionMs: {},
152
+ lastAutoUpgradeCheckAtMs: null,
153
+ lastAutoUpgradeStatus: null,
154
+ lastAutoUpgradeCheckByTargetMs: {},
155
+ lastAutoUpgradeStatusByTarget: {},
156
+ } satisfies QmdGlobalState;
157
+ }
158
+ const state = g[QMD_GLOBAL_STATE_KEY] as QmdGlobalState;
159
+ state.lastAutoUpgradeCheckByTargetMs ??= {};
160
+ state.lastAutoUpgradeStatusByTarget ??= {};
161
+ return state;
162
+ }
163
+
164
+ function sleep(ms: number): Promise<void> {
165
+ return new Promise((r) => setTimeout(r, ms));
166
+ }
167
+
168
+ function errorMessage(err: unknown): string {
169
+ if (typeof err === "string") return err;
170
+ if (err instanceof Error) return err.message;
171
+ if (err && typeof err === "object" && "message" in err && typeof (err as { message?: unknown }).message === "string") {
172
+ return (err as { message: string }).message;
173
+ }
174
+ return String(err);
175
+ }
176
+
177
+ function isCallerCancellation(err: unknown, signal?: AbortSignal): boolean {
178
+ if (signal?.aborted) return true;
179
+ if (isAbortError(err)) return true;
180
+ if (err && typeof err === "object") {
181
+ const code = "code" in err ? (err as { code?: unknown }).code : undefined;
182
+ if (code === "ABORT_ERR" || code === "ERR_CANCELED") return true;
183
+ }
184
+ return false;
185
+ }
186
+
187
+ function isDaemonTimeoutError(err: unknown): boolean {
188
+ return /timed out/i.test(errorMessage(err));
189
+ }
190
+
191
+ function sleepWithSignal(ms: number, signal?: AbortSignal): Promise<void> {
192
+ return new Promise((resolve, reject) => {
193
+ throwIfAborted(signal);
194
+ const timer = setTimeout(() => {
195
+ cleanup();
196
+ resolve();
197
+ }, ms);
198
+ const onAbort = () => {
199
+ clearTimeout(timer);
200
+ cleanup();
201
+ reject(abortError("operation aborted while waiting"));
202
+ };
203
+ const cleanup = () => {
204
+ signal?.removeEventListener("abort", onAbort);
205
+ };
206
+ signal?.addEventListener("abort", onAbort, { once: true });
207
+ });
208
+ }
209
+
210
+ function isSqliteBusyError(msg: string): boolean {
211
+ const lower = msg.toLowerCase();
212
+ return (
213
+ lower.includes("database is locked") ||
214
+ lower.includes("sqlite_busy") ||
215
+ lower.includes("sqlite_busy_recovery") ||
216
+ lower.includes("sqliterror: database is locked")
217
+ );
218
+ }
219
+
220
+ function stripControlChars(s: string): string {
221
+ // Remove ANSI escapes and other control characters that explode logs.
222
+ return s.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
223
+ }
224
+
225
+ function truncateForLog(s: string, max = 2000): string {
226
+ const cleaned = stripControlChars(s);
227
+ return cleaned.length > max ? cleaned.slice(0, max) + "…(truncated)" : cleaned;
228
+ }
229
+
230
+ function isVectorDimensionMismatchError(err: unknown): boolean {
231
+ const msg = err instanceof Error ? err.message : String(err);
232
+ return (
233
+ /dimension mismatch/i.test(msg) ||
234
+ (/vectors?_vec/i.test(msg) && /float\[\d+\]/i.test(msg)) ||
235
+ (/embedding/i.test(msg) && /dimensions?/i.test(msg))
236
+ );
237
+ }
238
+
239
+ export function parseQmdVersion(version: string | null): QmdVersionTuple | null {
240
+ if (!version) return null;
241
+ const match = version.match(/v?(\d{1,10})\.(\d{1,10})\.(\d{1,10})/i);
242
+ if (!match) return null;
243
+ return [
244
+ Number.parseInt(match[1] ?? "0", 10),
245
+ Number.parseInt(match[2] ?? "0", 10),
246
+ Number.parseInt(match[3] ?? "0", 10),
247
+ ];
248
+ }
249
+
250
+ export function parseQmdVersionOutput(stdout: string, stderr: string): string | null {
251
+ const lines = `${stdout}\n${stderr}`
252
+ .split("\n")
253
+ .map((s) => s.trim())
254
+ .filter((s) => s.length > 0);
255
+ if (lines.length === 0) return null;
256
+ const semanticLines = lines.filter((line) => parseQmdVersion(line) !== null);
257
+ if (semanticLines.length === 0) return lines[0] ?? null;
258
+ return semanticLines.find((line) => /\bqmd\b/i.test(line)) ?? semanticLines[0] ?? null;
259
+ }
260
+
261
+ export function compareQmdVersions(
262
+ left: QmdVersionTuple | null,
263
+ right: QmdVersionTuple | null,
264
+ ): number {
265
+ if (!left && !right) return 0;
266
+ if (!left) return -1;
267
+ if (!right) return 1;
268
+ for (let i = 0; i < 3; i += 1) {
269
+ if ((left[i] ?? 0) > (right[i] ?? 0)) return 1;
270
+ if ((left[i] ?? 0) < (right[i] ?? 0)) return -1;
271
+ }
272
+ return 0;
273
+ }
274
+
275
+ export function versionAtLeast(
276
+ current: QmdVersionTuple | null,
277
+ target: QmdVersionTuple,
278
+ ): boolean {
279
+ return compareQmdVersions(current, target) >= 0;
280
+ }
281
+
282
+ export function resolveQmdCapabilities(version: string | null): QmdCapabilities {
283
+ const parsedVersion = parseQmdVersion(version);
284
+ const atLeast = (target: QmdVersionTuple): boolean => versionAtLeast(parsedVersion, target);
285
+ return {
286
+ version,
287
+ parsedVersion,
288
+ stableSdk: atLeast([2, 0, 0]),
289
+ unifiedSearch: atLeast([2, 0, 0]),
290
+ getDocumentBody: atLeast([2, 0, 0]),
291
+ maintenanceApi: atLeast([2, 0, 0]),
292
+ legacySkillInstall: atLeast([2, 0, 1]),
293
+ intentHints: atLeast([1, 1, 5]),
294
+ explainTraces: atLeast([1, 1, 2]),
295
+ candidateLimit: atLeast([1, 1, 2]),
296
+ v2McpQueryTool: atLeast([2, 0, 0]),
297
+ structuredSearches: atLeast([2, 0, 0]),
298
+ queryRerankToggle: atLeast([2, 1, 0]),
299
+ chunkStrategy: atLeast([2, 1, 0]),
300
+ qmdBench: atLeast([2, 1, 0]),
301
+ perCollectionModels: atLeast([2, 1, 0]),
302
+ jsonLineNumbers: atLeast([2, 1, 0]),
303
+ editorLinks: atLeast([2, 1, 0]),
304
+ doctor: atLeast([2, 5, 0]),
305
+ versionedSkills: atLeast([2, 5, 0]),
306
+ absoluteSnippetLines: atLeast([2, 5, 0]),
307
+ fullQueryOutput: atLeast([2, 5, 0]),
308
+ forceCpu: atLeast([2, 5, 0]),
309
+ gpuBackendOverride: atLeast([2, 5, 0]),
310
+ embedParallelism: atLeast([2, 5, 0]),
311
+ modelEnvConsistency: atLeast([2, 5, 0]),
312
+ scopedEmbed: atLeast([2, 5, 0]),
313
+ safeStatusDeviceProbe: atLeast([2, 5, 0]),
314
+ mcpIndexSelection: atLeast([2, 5, 0]),
315
+ };
316
+ }
317
+
318
+ export function shouldAutoUpgradeQmd(
319
+ installedVersion: string | null,
320
+ supportedVersion: string = QMD_SUPPORTED_VERSION,
321
+ ): boolean {
322
+ const installed = parseQmdVersion(installedVersion);
323
+ const supported = parseQmdVersion(supportedVersion);
324
+ if (!installed || !supported) return false;
325
+ return compareQmdVersions(installed, supported) < 0;
326
+ }
327
+
328
+ export function getQmdPostInstallProbeTargets(
329
+ qmdPath: string,
330
+ qmdPathSource: "configured" | "auto-path" | "auto-fallback",
331
+ ): Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> {
332
+ const targets: Array<{ qmdPath: string; source: "auto-path" | "auto-fallback" }> = [
333
+ { qmdPath: "qmd", source: "auto-path" },
334
+ ];
335
+ const normalizedPath = qmdPath.trim();
336
+ if (
337
+ qmdPathSource === "auto-fallback" &&
338
+ normalizedPath.length > 0 &&
339
+ normalizedPath !== "qmd"
340
+ ) {
341
+ targets.push({ qmdPath: normalizedPath, source: "auto-fallback" });
342
+ }
343
+ return targets;
344
+ }
345
+
346
+ function qmdVersionToString(version: QmdVersionTuple): string {
347
+ return `${version[0]}.${version[1]}.${version[2]}`;
348
+ }
349
+
350
+ function normalizeSearchOptions(options?: SearchQueryOptions): SearchQueryOptions | undefined {
351
+ if (!options) return undefined;
352
+ const intent = typeof options.intent === "string" ? options.intent.trim() : "";
353
+ const normalized: SearchQueryOptions = {};
354
+ if (intent.length > 0) {
355
+ normalized.intent = intent;
356
+ }
357
+ if (options.explain === true) {
358
+ normalized.explain = true;
359
+ }
360
+ if (options.rerank === false) {
361
+ normalized.rerank = false;
362
+ }
363
+ if (options.chunkStrategy === "auto" || options.chunkStrategy === "regex") {
364
+ normalized.chunkStrategy = options.chunkStrategy;
365
+ }
366
+ if (
367
+ typeof options.candidateLimit === "number" &&
368
+ Number.isFinite(options.candidateLimit) &&
369
+ options.candidateLimit > 0
370
+ ) {
371
+ normalized.candidateLimit = Math.floor(options.candidateLimit);
372
+ }
373
+ const structuredSearches = normalizeStructuredSearches(options.structuredSearches);
374
+ if (structuredSearches.length > 0) {
375
+ normalized.structuredSearches = structuredSearches;
376
+ }
377
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
378
+ }
379
+
380
+ function normalizeStructuredSearches(value: unknown): QmdStructuredSearch[] {
381
+ if (!Array.isArray(value)) return [];
382
+ const normalized: QmdStructuredSearch[] = [];
383
+ for (const entry of value) {
384
+ if (!entry || typeof entry !== "object") continue;
385
+ const candidate = entry as { type?: unknown; query?: unknown };
386
+ const type = candidate.type;
387
+ const query = typeof candidate.query === "string" ? candidate.query.trim() : "";
388
+ if ((type === "lex" || type === "vec" || type === "hyde") && query.length > 0) {
389
+ normalized.push({ type, query });
390
+ }
391
+ if (normalized.length >= 10) break;
392
+ }
393
+ return normalized;
394
+ }
395
+
396
+ function buildSyntheticHydeQuery(query: string, intent?: string): string {
397
+ const base = intent && intent.trim().length > 0
398
+ ? `A relevant Remnic memory for ${intent.trim()} would answer: ${query.trim()}`
399
+ : `A relevant Remnic memory would answer: ${query.trim()}`;
400
+ return base.length > QMD_STRUCTURED_HYDE_MAX_CHARS
401
+ ? base.slice(0, QMD_STRUCTURED_HYDE_MAX_CHARS)
402
+ : base;
403
+ }
404
+
405
+ function buildDefaultStructuredSearches(
406
+ query: string,
407
+ options?: SearchQueryOptions,
408
+ ): QmdStructuredSearch[] {
409
+ const explicit = normalizeStructuredSearches(options?.structuredSearches);
410
+ if (explicit.length > 0) return explicit;
411
+ const trimmed = query.trim();
412
+ if (!trimmed) return [];
413
+ return [
414
+ { type: "lex", query: trimmed },
415
+ { type: "vec", query: trimmed },
416
+ { type: "hyde", query: buildSyntheticHydeQuery(trimmed, options?.intent) },
417
+ ];
418
+ }
419
+
420
+ function parseExplainScores(value: unknown): number[] | undefined {
421
+ if (!Array.isArray(value)) return undefined;
422
+ const scores = value.filter((entry): entry is number => typeof entry === "number");
423
+ return scores.length > 0 ? scores : undefined;
424
+ }
425
+
426
+ export function parseQmdExplain(value: unknown): QmdSearchExplain | undefined {
427
+ if (!value || typeof value !== "object") return undefined;
428
+ const candidate = value as Record<string, unknown>;
429
+ const rrf =
430
+ typeof candidate.rrf === "number"
431
+ ? candidate.rrf
432
+ : candidate.rrf && typeof candidate.rrf === "object" &&
433
+ typeof (candidate.rrf as Record<string, unknown>).totalScore === "number"
434
+ ? ((candidate.rrf as Record<string, unknown>).totalScore as number)
435
+ : undefined;
436
+ const rrfObj =
437
+ candidate.rrf && typeof candidate.rrf === "object"
438
+ ? (candidate.rrf as Record<string, unknown>)
439
+ : undefined;
440
+ const parsed: QmdSearchExplain = {
441
+ ftsScores: parseExplainScores(candidate.ftsScores),
442
+ vectorScores: parseExplainScores(candidate.vectorScores),
443
+ rrf,
444
+ rrfRank: typeof rrfObj?.rank === "number" ? rrfObj.rank : undefined,
445
+ rrfPositionScore:
446
+ typeof rrfObj?.positionScore === "number" ? rrfObj.positionScore : undefined,
447
+ rrfBaseScore: typeof rrfObj?.baseScore === "number" ? rrfObj.baseScore : undefined,
448
+ rrfTopRankBonus:
449
+ typeof rrfObj?.topRankBonus === "number" ? rrfObj.topRankBonus : undefined,
450
+ rerankScore: typeof candidate.rerankScore === "number" ? candidate.rerankScore : undefined,
451
+ blendedScore: typeof candidate.blendedScore === "number" ? candidate.blendedScore : undefined,
452
+ };
453
+ return Object.values(parsed).some((entry) => entry !== undefined) ? parsed : undefined;
454
+ }
455
+
456
+ class AsyncMutex {
457
+ private locked = false;
458
+ private queue: Array<{
459
+ resolve: (release: () => void) => void;
460
+ reject: (reason: Error) => void;
461
+ signal?: AbortSignal;
462
+ onAbort: () => void;
463
+ }> = [];
464
+
465
+ async runExclusive<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T> {
466
+ const release = await this.acquire(signal);
467
+ try {
468
+ throwIfAborted(signal);
469
+ return await fn();
470
+ } finally {
471
+ release();
472
+ }
473
+ }
474
+
475
+ private acquire(signal?: AbortSignal): Promise<() => void> {
476
+ throwIfAborted(signal);
477
+ if (!this.locked) {
478
+ this.locked = true;
479
+ return Promise.resolve(() => this.release());
480
+ }
481
+
482
+ return new Promise((resolve, reject) => {
483
+ const waiter = {
484
+ resolve: (release: () => void) => {
485
+ signal?.removeEventListener("abort", waiter.onAbort);
486
+ resolve(release);
487
+ },
488
+ reject: (reason: Error) => {
489
+ signal?.removeEventListener("abort", waiter.onAbort);
490
+ reject(reason);
491
+ },
492
+ signal,
493
+ onAbort: () => {
494
+ this.queue = this.queue.filter((entry) => entry !== waiter);
495
+ reject(abortError("operation aborted while waiting for qmd mutex"));
496
+ },
497
+ };
498
+ signal?.addEventListener("abort", waiter.onAbort, { once: true });
499
+ this.queue.push(waiter);
500
+ });
501
+ }
502
+
503
+ private release(): void {
504
+ while (this.queue.length > 0) {
505
+ const next = this.queue.shift();
506
+ if (!next) break;
507
+ if (next.signal?.aborted) {
508
+ next.reject(abortError("operation aborted while waiting for qmd mutex"));
509
+ continue;
510
+ }
511
+ this.locked = true;
512
+ next.resolve(() => this.release());
513
+ return;
514
+ }
515
+ this.locked = false;
516
+ }
517
+ }
518
+
519
+ const QMD_MUTEX = new AsyncMutex();
520
+
521
+ function runQmd(
522
+ args: string[],
523
+ timeoutMs: number = QMD_TIMEOUT_MS,
524
+ qmdPath: string = "qmd",
525
+ signal?: AbortSignal,
526
+ runtimeEnv?: QmdRuntimeEnv,
527
+ ): Promise<{ stdout: string; stderr: string }> {
528
+ // Serialize all qmd calls. This avoids SQLite lock contention when multiple
529
+ // channels/agents trigger QMD operations at nearly the same time.
530
+ return QMD_MUTEX.runExclusive(async () => {
531
+ throwIfAborted(signal, `qmd ${args.join(" ")} aborted before start`);
532
+ const maxAttempts = isLikelyWriteCommand(args) ? 3 : 1;
533
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
534
+ try {
535
+ return await runQmdOnce(args, timeoutMs, qmdPath, signal, runtimeEnv);
536
+ } catch (err) {
537
+ if (isAbortError(err)) throw err;
538
+ const msg = err instanceof Error ? err.message : String(err);
539
+ if (attempt < maxAttempts && isSqliteBusyError(msg)) {
540
+ // Another qmd call (or an external qmd process) currently holds the DB.
541
+ // Back off briefly and retry.
542
+ await sleepWithSignal(1500 * attempt, signal);
543
+ continue;
544
+ }
545
+ throw err;
546
+ }
547
+ }
548
+ // unreachable
549
+ throw new Error("qmd command failed");
550
+ }, signal);
551
+ }
552
+
553
+ function isLikelyWriteCommand(args: string[]): boolean {
554
+ const cmd = getQmdCommandName(args);
555
+ return cmd === "update" || cmd === "embed" || cmd === "cleanup" || cmd === "collection";
556
+ }
557
+
558
+ export function getQmdCommandName(args: string[]): string {
559
+ for (let i = 0; i < args.length; i += 1) {
560
+ const arg = args[i] ?? "";
561
+ if (arg === "--index") {
562
+ i += 1;
563
+ continue;
564
+ }
565
+ if (arg.startsWith("--index=")) {
566
+ continue;
567
+ }
568
+ if (arg.startsWith("-")) {
569
+ continue;
570
+ }
571
+ return arg;
572
+ }
573
+ return "";
574
+ }
575
+
576
+ function runQmdOnce(
577
+ args: string[],
578
+ timeoutMs: number,
579
+ qmdPath: string,
580
+ signal?: AbortSignal,
581
+ runtimeEnv?: QmdRuntimeEnv,
582
+ ): Promise<{ stdout: string; stderr: string }> {
583
+ const isVersionCheck = args.length === 1 && args[0] === "--version";
584
+ return runCommandWithTimeout(qmdPath, args, {
585
+ timeoutMs,
586
+ signal,
587
+ env: runtimeEnv,
588
+ label: `qmd ${args.join(" ")}`,
589
+ isSuccessExitCode: (code) => code === 0 || (isVersionCheck && code === 1),
590
+ });
591
+ }
592
+
593
+ function runCommandWithTimeout(
594
+ command: string,
595
+ args: string[],
596
+ options: {
597
+ timeoutMs: number;
598
+ signal?: AbortSignal;
599
+ env?: QmdRuntimeEnv;
600
+ label?: string;
601
+ isSuccessExitCode?: (code: number | null) => boolean;
602
+ },
603
+ ): Promise<{ stdout: string; stderr: string }> {
604
+ const label = options.label ?? `${command} ${args.join(" ")}`;
605
+ const isSuccessExitCode = options.isSuccessExitCode ?? ((code: number | null) => code === 0);
606
+ return new Promise((resolve, reject) => {
607
+ throwIfAborted(options.signal, `${label} aborted before spawn`);
608
+ const child = launchProcess(command, args, {
609
+ env: mergeEnv({ NO_COLOR: "1", ...options.env }),
610
+ stdio: ["ignore", "pipe", "pipe"],
611
+ });
612
+ if (!child.stdout || !child.stderr) {
613
+ reject(new Error(`${label} failed to open stdio pipes`));
614
+ return;
615
+ }
616
+
617
+ let stdout = "";
618
+ let stderr = "";
619
+ let settled = false;
620
+
621
+ const timer = setTimeout(() => {
622
+ settled = true;
623
+ cleanup();
624
+ child.kill("SIGKILL");
625
+ reject(new Error(`${label} timed out after ${options.timeoutMs}ms`));
626
+ }, options.timeoutMs);
627
+ const onAbort = () => {
628
+ if (settled) return;
629
+ settled = true;
630
+ clearTimeout(timer);
631
+ cleanup();
632
+ child.kill("SIGKILL");
633
+ reject(abortError(`${label} aborted`));
634
+ };
635
+ const cleanup = () => {
636
+ options.signal?.removeEventListener("abort", onAbort);
637
+ };
638
+ options.signal?.addEventListener("abort", onAbort, { once: true });
639
+
640
+ child.stdout.on("data", (data: Buffer) => {
641
+ stdout += data.toString();
642
+ });
643
+ child.stderr.on("data", (data: Buffer) => {
644
+ stderr += data.toString();
645
+ });
646
+ child.on("error", (err) => {
647
+ if (settled) return;
648
+ settled = true;
649
+ clearTimeout(timer);
650
+ cleanup();
651
+ reject(err);
652
+ });
653
+ child.on("close", (code) => {
654
+ if (settled) return;
655
+ settled = true;
656
+ clearTimeout(timer);
657
+ cleanup();
658
+ if (isSuccessExitCode(code)) {
659
+ resolve({ stdout, stderr });
660
+ } else {
661
+ reject(
662
+ new Error(
663
+ `${label} failed (code ${code}): ${truncateForLog(stderr || stdout)}`,
664
+ ),
665
+ );
666
+ }
667
+ });
668
+ });
669
+ }
670
+
671
+ function runProcessCommand(
672
+ command: string,
673
+ args: string[],
674
+ timeoutMs: number,
675
+ signal?: AbortSignal,
676
+ ): Promise<{ stdout: string; stderr: string }> {
677
+ return runCommandWithTimeout(command, args, {
678
+ timeoutMs,
679
+ signal,
680
+ });
681
+ }
682
+
683
+ // ---------------------------------------------------------------------------
684
+ // QMD Stdio Daemon Session (MCP over stdio child process)
685
+ // ---------------------------------------------------------------------------
686
+
687
+ let nextJsonRpcId = 1;
688
+
689
+ class QmdDaemonSession {
690
+ private child: CommandChildProcess | null = null;
691
+ private initialized = false;
692
+ private buffer = "";
693
+ private startPromise: Promise<boolean> | null = null;
694
+ private pendingRequests = new Map<
695
+ number,
696
+ {
697
+ resolve: (value: unknown) => void;
698
+ reject: (reason: Error) => void;
699
+ timer: ReturnType<typeof setTimeout>;
700
+ cleanup: () => void;
701
+ }
702
+ >();
703
+ private readonly qmdPath: string;
704
+ private readonly runtimeEnv: QmdRuntimeEnv;
705
+ private readonly indexName?: string;
706
+
707
+ constructor(qmdPath: string, runtimeEnv: QmdRuntimeEnv = {}, indexName?: string) {
708
+ this.qmdPath = qmdPath;
709
+ this.runtimeEnv = runtimeEnv;
710
+ this.indexName = indexName?.trim() || undefined;
711
+ }
712
+
713
+ /** Spawn the qmd mcp child process and perform MCP handshake. */
714
+ async start(): Promise<boolean> {
715
+ if (this.child && !this.child.killed && this.initialized) {
716
+ return true;
717
+ }
718
+ if (this.startPromise) {
719
+ return this.startPromise;
720
+ }
721
+ this.startPromise = (async () => {
722
+ // If the process is already running but not yet initialized (e.g. it is still
723
+ // loading its index after a previous handshake timeout), reuse it instead of
724
+ // killing and re-spawning. This prevents accumulating zombie qmd-mcp processes
725
+ // when the daemon takes >15s to load a large collection.
726
+ const processAlreadyRunning = this.child != null && !this.child.killed;
727
+ if (!processAlreadyRunning) {
728
+ if (this.child) {
729
+ this.cleanup({ killChild: true });
730
+ }
731
+ try {
732
+ const args = this.indexName ? ["--index", this.indexName, "mcp"] : ["mcp"];
733
+ const child = launchProcess(this.qmdPath, args, {
734
+ env: mergeEnv({ NO_COLOR: "1", ...this.runtimeEnv }),
735
+ stdio: ["pipe", "pipe", "pipe"],
736
+ });
737
+ this.child = child;
738
+ this.buffer = "";
739
+
740
+ child.stdout?.on("data", (data: Buffer) => {
741
+ if (this.child !== child) return;
742
+ this.handleStdoutData(data);
743
+ });
744
+ child.stderr?.on("data", (data: Buffer) => {
745
+ if (this.child !== child) return;
746
+ const msg = data.toString().trim();
747
+ if (msg) log.debug(`QMD mcp stderr: ${stripControlChars(msg)}`);
748
+ });
749
+ child.stdin?.on("error", (err) => {
750
+ // Swallow EPIPE/ERR_STREAM_DESTROYED — these happen when the child
751
+ // process is killed (e.g. due to recall timeout) and a write arrives
752
+ // after the pipe is broken. Without this handler Node.js would throw
753
+ // an uncaught exception and crash the process.
754
+ log.debug(`QMD mcp stdin error (suppressed): ${err.message}`);
755
+ });
756
+ child.on("error", (err) => {
757
+ if (this.child !== child) return;
758
+ log.debug(`QMD mcp process error: ${err.message}`);
759
+ this.cleanup({ child });
760
+ });
761
+ child.on("close", (code) => {
762
+ if (this.child !== child) return;
763
+ log.debug(`QMD mcp process exited (code ${code})`);
764
+ this.cleanup({ child });
765
+ });
766
+ } catch (err) {
767
+ log.debug(`QMD mcp: failed to spawn process: ${err}`);
768
+ this.cleanup({ killChild: true });
769
+ return false;
770
+ }
771
+ } else {
772
+ log.debug("QMD mcp: process already running, retrying handshake");
773
+ }
774
+
775
+ try {
776
+ // Use a generous timeout — large collections (75K+ files) can take 60-90s
777
+ // to load their vector index. We keep the process alive across retries so
778
+ // only one mcp instance is running at a time.
779
+ const result = await this.sendRequest(
780
+ "initialize",
781
+ {
782
+ protocolVersion: "2024-11-05",
783
+ capabilities: {},
784
+ clientInfo: { name: "openclaw-remnic", version: "1.0.0" },
785
+ },
786
+ 60_000,
787
+ );
788
+ if (!result) {
789
+ // Null result (non-timeout failure) — kill and let the next probe respawn.
790
+ this.cleanup({ killChild: true });
791
+ return false;
792
+ }
793
+ this.sendNotification("notifications/initialized");
794
+ this.initialized = true;
795
+ log.info("QMD mcp: stdio session initialized");
796
+ return true;
797
+ } catch (err) {
798
+ const msg = err instanceof Error ? err.message : String(err);
799
+ if (/timed out/i.test(msg)) {
800
+ // Handshake timeout — process is still loading. Keep it alive for the
801
+ // next retry (daemonRecheckIntervalMs). Do NOT kill and respawn.
802
+ log.debug(`QMD mcp: handshake timed out — process still loading, will retry later`);
803
+ // Reset initialized flag but leave child running.
804
+ this.initialized = false;
805
+ } else {
806
+ log.debug(`QMD mcp: failed to start stdio session: ${err}`);
807
+ this.cleanup({ killChild: true });
808
+ }
809
+ return false;
810
+ } finally {
811
+ this.startPromise = null;
812
+ }
813
+ })();
814
+ return this.startPromise;
815
+ }
816
+
817
+ /** Call an MCP tool and return the parsed result. */
818
+ async callTool(
819
+ name: string,
820
+ args: Record<string, unknown>,
821
+ timeoutMs: number = 30_000,
822
+ signal?: AbortSignal,
823
+ ): Promise<unknown> {
824
+ if (!this.child || this.child.killed || !this.initialized) {
825
+ throw new Error("QMD mcp process not running");
826
+ }
827
+ return this.sendRequest("tools/call", { name, arguments: args }, timeoutMs, signal);
828
+ }
829
+
830
+ /** Kill stdio process and clear state so the next probe can restart. */
831
+ invalidate(): void {
832
+ this.cleanup({ killChild: true });
833
+ }
834
+
835
+ /** Kill stdio process and wait briefly for the child handle to close. */
836
+ async close(timeoutMs = 1_000): Promise<void> {
837
+ const target = this.child;
838
+ if (!target) {
839
+ this.cleanup({ killChild: true });
840
+ return;
841
+ }
842
+
843
+ let closed = false;
844
+ const closedPromise = new Promise<void>((resolve) => {
845
+ target.once("close", () => {
846
+ closed = true;
847
+ resolve();
848
+ });
849
+ });
850
+
851
+ this.cleanup({ killChild: true });
852
+ await Promise.race([closedPromise, sleep(timeoutMs)]);
853
+ if (!closed) {
854
+ try {
855
+ target.kill("SIGKILL");
856
+ } catch {
857
+ // Ignore process-kill races during shutdown.
858
+ }
859
+ await Promise.race([closedPromise, sleep(250)]);
860
+ }
861
+ }
862
+
863
+ isActive(): boolean {
864
+ return this.child !== null && !this.child.killed && this.initialized;
865
+ }
866
+
867
+ /** True while the process is spawned but the MCP handshake has not yet completed. */
868
+ isLoading(): boolean {
869
+ return this.child !== null && !this.child.killed && !this.initialized;
870
+ }
871
+
872
+ private sendRequest(
873
+ method: string,
874
+ params: Record<string, unknown>,
875
+ timeoutMs: number,
876
+ signal?: AbortSignal,
877
+ ): Promise<unknown> {
878
+ return new Promise((resolve, reject) => {
879
+ throwIfAborted(signal, `QMD mcp ${method} aborted before request`);
880
+ if (!this.child || !this.child.stdin || this.child.killed) {
881
+ reject(new Error("QMD mcp process not available"));
882
+ return;
883
+ }
884
+
885
+ const id = nextJsonRpcId++;
886
+ const timer = setTimeout(() => {
887
+ this.pendingRequests.delete(id);
888
+ cleanup();
889
+ reject(new Error(`QMD mcp ${method} timed out after ${timeoutMs}ms`));
890
+ }, timeoutMs);
891
+ const onAbort = () => {
892
+ clearTimeout(timer);
893
+ this.pendingRequests.delete(id);
894
+ cleanup();
895
+ reject(abortError(`QMD mcp ${method} aborted`));
896
+ };
897
+ const cleanup = () => {
898
+ signal?.removeEventListener("abort", onAbort);
899
+ };
900
+
901
+ this.pendingRequests.set(id, { resolve, reject, timer, cleanup });
902
+ signal?.addEventListener("abort", onAbort, { once: true });
903
+ const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
904
+ this.child.stdin.write(message, (err) => {
905
+ if (err) {
906
+ clearTimeout(timer);
907
+ this.pendingRequests.delete(id);
908
+ cleanup();
909
+ reject(new Error(`Failed to write to QMD mcp stdin: ${err.message}`));
910
+ }
911
+ });
912
+ });
913
+ }
914
+
915
+ private sendNotification(method: string, params?: Record<string, unknown>): void {
916
+ if (!this.child || !this.child.stdin || this.child.killed) return;
917
+ if (this.child.stdin.destroyed) return;
918
+ const msg: Record<string, unknown> = { jsonrpc: "2.0", method };
919
+ if (params) msg.params = params;
920
+ try {
921
+ this.child.stdin.write(JSON.stringify(msg) + "\n");
922
+ } catch {
923
+ // Ignore EPIPE / write-after-close
924
+ }
925
+ }
926
+
927
+ private handleStdoutData(data: Buffer): void {
928
+ this.buffer += data.toString();
929
+ let newlineIdx: number;
930
+ while ((newlineIdx = this.buffer.indexOf("\n")) !== -1) {
931
+ const line = this.buffer.slice(0, newlineIdx).trim();
932
+ this.buffer = this.buffer.slice(newlineIdx + 1);
933
+ if (!line) continue;
934
+ try {
935
+ const msg = JSON.parse(line);
936
+ this.handleMessage(msg);
937
+ } catch {
938
+ log.debug(`QMD mcp: unparseable stdout: ${truncateForLog(line, 200)}`);
939
+ }
940
+ }
941
+ }
942
+
943
+ private handleMessage(msg: Record<string, unknown>): void {
944
+ if (msg.id !== undefined && msg.id !== null) {
945
+ const pending = this.pendingRequests.get(msg.id as number);
946
+ if (pending) {
947
+ clearTimeout(pending.timer);
948
+ this.pendingRequests.delete(msg.id as number);
949
+ pending.cleanup();
950
+ if (msg.error) {
951
+ pending.reject(new Error(JSON.stringify(msg.error)));
952
+ } else {
953
+ pending.resolve(msg.result);
954
+ }
955
+ }
956
+ return;
957
+ }
958
+ if (msg.method) {
959
+ log.debug(`QMD mcp notification: ${msg.method}`);
960
+ }
961
+ }
962
+
963
+ private cleanup(opts?: { killChild?: boolean; child?: CommandChildProcess | null }): void {
964
+ const target = opts?.child ?? this.child;
965
+ if (!target) return;
966
+ if (opts?.child && this.child !== opts.child) {
967
+ return;
968
+ }
969
+ if (opts?.killChild && !target.killed) {
970
+ target.kill("SIGTERM");
971
+ }
972
+ this.initialized = false;
973
+ for (const [, pending] of this.pendingRequests) {
974
+ clearTimeout(pending.timer);
975
+ pending.cleanup();
976
+ pending.reject(new Error("QMD mcp process terminated"));
977
+ }
978
+ this.pendingRequests.clear();
979
+ this.startPromise = null;
980
+ this.child = null;
981
+ this.buffer = "";
982
+ }
983
+ }
984
+
985
+ /** Matches `#<hex-docid> <score>% <rest-of-line>` — rest is split in a second pass. */
986
+ const QMD_RESULT_LINE_RE = /^#([0-9a-fA-F]+)\s+(\d+)%\s+(.+)/;
987
+
988
+ /**
989
+ * Splits `collection/path.ext - Title text` into path and title.
990
+ * Non-greedy `.+?` finds the FIRST dot-extension (2+ alphabetic chars)
991
+ * followed by ` - `, accepting any indexed file type while skipping
992
+ * version-like segments (e.g. `v1.2` where `.2` is a single digit).
993
+ */
994
+ const QMD_PATH_TITLE_RE = /^(.+?\.[a-zA-Z]{2,10})\s+-\s+(.*)$/;
995
+
996
+ function parseQmdMarkdownResultText(
997
+ text: string,
998
+ transport: QmdSearchResult["transport"],
999
+ ): QmdSearchResult[] {
1000
+ const results: QmdSearchResult[] = [];
1001
+ for (const line of text.split("\n")) {
1002
+ const m = QMD_RESULT_LINE_RE.exec(line.trim());
1003
+ if (!m) continue;
1004
+ const rest = m[3]; // "collection/path.md - Title with - dashes"
1005
+ // Find the path by looking for known file extensions followed by " - "
1006
+ const pathTitleSplit = QMD_PATH_TITLE_RE.exec(rest);
1007
+ if (!pathTitleSplit) continue;
1008
+ results.push({
1009
+ docid: m[1],
1010
+ path: pathTitleSplit[1] ?? "unknown",
1011
+ snippet: "",
1012
+ score: parseInt(m[2], 10) / 100,
1013
+ transport,
1014
+ });
1015
+ }
1016
+ return results;
1017
+ }
1018
+
1019
+ function parseMcpSearchResult(
1020
+ result: unknown,
1021
+ transport: QmdSearchResult["transport"] = "daemon",
1022
+ ): QmdSearchResult[] {
1023
+ const resultObj = result as Record<string, unknown> | null;
1024
+ if (!resultObj) return [];
1025
+ const results: QmdSearchResult[] = [];
1026
+ const pushDocs = (docs: unknown[]) => {
1027
+ for (const doc of docs) {
1028
+ const d = doc as Record<string, unknown>;
1029
+ results.push({
1030
+ docid: typeof d.docid === "string" ? d.docid.replace(/^#/, "") : "",
1031
+ path: typeof d.file === "string"
1032
+ ? d.file
1033
+ : typeof d.path === "string"
1034
+ ? d.path
1035
+ : (typeof d.docid === "string" ? d.docid.replace(/^#/, "") : "unknown"),
1036
+ snippet: typeof d.snippet === "string" ? d.snippet : "",
1037
+ score: typeof d.score === "number" ? d.score : 0,
1038
+ line: typeof d.line === "number" && Number.isFinite(d.line)
1039
+ ? Math.max(1, Math.floor(d.line))
1040
+ : undefined,
1041
+ explain: parseQmdExplain(d.explain),
1042
+ transport,
1043
+ });
1044
+ }
1045
+ };
1046
+ const topStructured = resultObj.structuredContent as Record<string, unknown> | undefined;
1047
+ const topDocs = topStructured?.results ?? topStructured?.documents;
1048
+ if (Array.isArray(topDocs)) pushDocs(topDocs);
1049
+ const content = resultObj.content;
1050
+ if (Array.isArray(content)) {
1051
+ for (const item of content) {
1052
+ const structured = item?.structuredContent;
1053
+ const docResults = structured?.results ?? structured?.documents;
1054
+ if (Array.isArray(docResults)) pushDocs(docResults);
1055
+ if (typeof item?.text === "string") {
1056
+ try {
1057
+ const parsed = JSON.parse(item.text);
1058
+ const textResults = parsed?.results ?? parsed?.documents;
1059
+ if (Array.isArray(textResults)) pushDocs(textResults);
1060
+ } catch {
1061
+ const existingKeys = new Set(results.map((r) => `${r.docid.toLowerCase()}|${r.path}`));
1062
+ const parsed = parseQmdMarkdownResultText(item.text, transport);
1063
+ for (const p of parsed) {
1064
+ const key = `${p.docid.toLowerCase()}|${p.path}`;
1065
+ if (!existingKeys.has(key)) {
1066
+ results.push(p);
1067
+ existingKeys.add(key);
1068
+ }
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+ return results;
1075
+ }
1076
+
1077
+ function parseQmdSearchStdout(
1078
+ stdout: string,
1079
+ transport: QmdSearchResult["transport"] = "subprocess",
1080
+ ): QmdSearchResult[] {
1081
+ const trimmedOut = stdout.trim();
1082
+ if (!trimmedOut || trimmedOut === "No results found.") return [];
1083
+ const parsed = JSON.parse(trimmedOut);
1084
+ if (!Array.isArray(parsed)) return [];
1085
+ return parsed.map(
1086
+ (entry: Record<string, unknown>): QmdSearchResult => ({
1087
+ docid: (entry.docid as string) ?? "",
1088
+ path:
1089
+ (entry.file as string) ??
1090
+ (entry.path as string) ??
1091
+ (entry.docid as string) ??
1092
+ "unknown",
1093
+ snippet: (entry.snippet as string) ?? "",
1094
+ score: typeof entry.score === "number" ? entry.score : 0,
1095
+ line: typeof entry.line === "number" && Number.isFinite(entry.line)
1096
+ ? Math.max(1, Math.floor(entry.line))
1097
+ : undefined,
1098
+ explain: parseQmdExplain(entry.explain),
1099
+ transport,
1100
+ }),
1101
+ );
1102
+ }
1103
+
1104
+ type SharedDaemonSessionEntry = {
1105
+ refs: number;
1106
+ session: QmdDaemonSession;
1107
+ };
1108
+
1109
+ const SHARED_DAEMON_SESSIONS = new Map<string, SharedDaemonSessionEntry>();
1110
+
1111
+ function stableRuntimeEnvKey(runtimeEnv: QmdRuntimeEnv): string {
1112
+ return JSON.stringify(
1113
+ Object.keys(runtimeEnv)
1114
+ .sort()
1115
+ .map((key) => [key, runtimeEnv[key]]),
1116
+ );
1117
+ }
1118
+
1119
+ function recordAutoUpgradeStatus(
1120
+ state: QmdGlobalState,
1121
+ targetKey: string,
1122
+ status: string,
1123
+ ): void {
1124
+ state.lastAutoUpgradeStatusByTarget[targetKey] = status;
1125
+ state.lastAutoUpgradeStatus = status;
1126
+ }
1127
+
1128
+ function retainSharedDaemonSession(
1129
+ qmdPath: string,
1130
+ runtimeEnv: QmdRuntimeEnv = {},
1131
+ indexName?: string,
1132
+ cliVersion?: string | null,
1133
+ ): QmdDaemonSession {
1134
+ const normalizedPath = qmdPath.trim() || "qmd";
1135
+ const normalizedIndex = indexName?.trim() || "";
1136
+ const normalizedVersion = cliVersion?.trim() || "";
1137
+ const sessionKey = `${normalizedPath}\0${normalizedIndex}\0${normalizedVersion}\0${stableRuntimeEnvKey(runtimeEnv)}`;
1138
+ const existing = SHARED_DAEMON_SESSIONS.get(sessionKey);
1139
+ if (existing) {
1140
+ existing.refs += 1;
1141
+ return existing.session;
1142
+ }
1143
+
1144
+ const session = new QmdDaemonSession(normalizedPath, runtimeEnv, normalizedIndex || undefined);
1145
+ SHARED_DAEMON_SESSIONS.set(sessionKey, {
1146
+ refs: 1,
1147
+ session,
1148
+ });
1149
+ return session;
1150
+ }
1151
+
1152
+ async function releaseSharedDaemonSession(session: QmdDaemonSession | null): Promise<void> {
1153
+ if (!session) return;
1154
+
1155
+ for (const [qmdPath, entry] of SHARED_DAEMON_SESSIONS.entries()) {
1156
+ if (entry.session !== session) continue;
1157
+ entry.refs = Math.max(0, entry.refs - 1);
1158
+ if (entry.refs === 0) {
1159
+ SHARED_DAEMON_SESSIONS.delete(qmdPath);
1160
+ await entry.session.close();
1161
+ }
1162
+ return;
1163
+ }
1164
+ }
1165
+
1166
+ // ---------------------------------------------------------------------------
1167
+ // QmdClient
1168
+ // ---------------------------------------------------------------------------
1169
+
1170
+ export class QmdClient implements SearchBackend {
1171
+ private available: boolean | null = null;
1172
+ private _lastUpdateFailAtMs: number | null = null;
1173
+ private lastEmbedFailAtMs: number | null = null;
1174
+ private lastUpdateRunAtMs: number | null = null;
1175
+
1176
+ get lastUpdateFailedAtMs(): number | null {
1177
+ return this._lastUpdateFailAtMs;
1178
+ }
1179
+
1180
+ get lastUpdateRanAtMs(): number | null {
1181
+ return this.lastUpdateRunAtMs;
1182
+ }
1183
+
1184
+ resetUpdateThrottles(): void {
1185
+ this._lastUpdateFailAtMs = null;
1186
+ this.lastUpdateRunAtMs = null;
1187
+ const gs = getGlobalQmdState();
1188
+ gs.lastGlobalUpdateRunAtMs = null;
1189
+ gs.lastGlobalUpdateFailAtMs = null;
1190
+ }
1191
+
1192
+ private readonly updateTimeoutMs: number;
1193
+ private readonly updateMinIntervalMs: number;
1194
+ private readonly slowLog?: { enabled: boolean; thresholdMs: number };
1195
+ private readonly configuredQmdPath?: string;
1196
+ private readonly qmdSupportedVersion: string;
1197
+ private readonly qmdAutoUpgradeEnabled: boolean;
1198
+ private readonly qmdAutoUpgradeCheckIntervalMs: number;
1199
+ private readonly qmdChunkStrategy?: QmdChunkStrategy;
1200
+ private readonly qmdCandidateLimit?: number;
1201
+ private readonly qmdQueryRerankEnabled: boolean;
1202
+ private readonly qmdIndexName?: string;
1203
+ private readonly qmdRuntimeEnv: QmdRuntimeEnv;
1204
+ private qmdPathSource: "auto-path" | "auto-fallback" | "configured" = "auto-path";
1205
+ private cliVersion: string | null = null;
1206
+ private lastCliProbeError: string | null = null;
1207
+ private qmdCapabilities: QmdCapabilities = resolveQmdCapabilities(null);
1208
+
1209
+ // Daemon mode fields
1210
+ private daemonSession: QmdDaemonSession | null = null;
1211
+ private daemonAvailable = false;
1212
+ private daemonSessionPath: string | null = null;
1213
+ private lastDaemonCheckAtMs = 0;
1214
+ private readonly daemonEnabled: boolean;
1215
+ private readonly daemonRecheckIntervalMs: number;
1216
+ /** Consecutive transient daemon failures before invalidating the session. */
1217
+ private daemonTransientFailures = 0;
1218
+ private static readonly DAEMON_MAX_TRANSIENT_FAILURES = 3;
1219
+
1220
+ constructor(
1221
+ private readonly collection: string,
1222
+ private readonly maxResults: number,
1223
+ opts?: QmdClientOptions,
1224
+ ) {
1225
+ this.slowLog = opts?.slowLog;
1226
+ this.updateTimeoutMs = opts?.updateTimeoutMs ?? 120_000;
1227
+ this.updateMinIntervalMs = Math.max(0, opts?.updateMinIntervalMs ?? 15 * 60_000);
1228
+ this.configuredQmdPath = opts?.qmdPath?.trim() ? opts.qmdPath.trim() : undefined;
1229
+ this.qmdSupportedVersion =
1230
+ parseQmdVersion(opts?.qmdSupportedVersion ?? null) !== null
1231
+ ? (opts?.qmdSupportedVersion ?? QMD_SUPPORTED_VERSION)
1232
+ : QMD_SUPPORTED_VERSION;
1233
+ this.qmdAutoUpgradeEnabled = opts?.qmdAutoUpgradeEnabled === true;
1234
+ this.qmdAutoUpgradeCheckIntervalMs = Math.max(
1235
+ 60_000,
1236
+ Math.floor(opts?.qmdAutoUpgradeCheckIntervalMs ?? QMD_AUTO_UPGRADE_CHECK_INTERVAL_MS),
1237
+ );
1238
+ this.qmdChunkStrategy =
1239
+ opts?.qmdChunkStrategy === "auto" || opts?.qmdChunkStrategy === "regex"
1240
+ ? opts.qmdChunkStrategy
1241
+ : undefined;
1242
+ this.qmdCandidateLimit =
1243
+ typeof opts?.qmdCandidateLimit === "number" &&
1244
+ Number.isFinite(opts.qmdCandidateLimit) &&
1245
+ opts.qmdCandidateLimit > 0
1246
+ ? Math.floor(opts.qmdCandidateLimit)
1247
+ : undefined;
1248
+ this.qmdQueryRerankEnabled = opts?.qmdQueryRerankEnabled !== false;
1249
+ this.qmdIndexName = opts?.qmdIndexName?.trim() || undefined;
1250
+ this.qmdRuntimeEnv = this.buildRuntimeEnv(opts);
1251
+ if (this.configuredQmdPath) {
1252
+ this.qmdPath = this.configuredQmdPath;
1253
+ this.qmdPathSource = "configured";
1254
+ }
1255
+ this.daemonEnabled = Boolean(opts?.daemonUrl);
1256
+ this.daemonRecheckIntervalMs = opts?.daemonRecheckIntervalMs ?? 15_000;
1257
+ }
1258
+
1259
+ private qmdPath: string = "qmd";
1260
+
1261
+ private buildRuntimeEnv(opts?: QmdClientOptions): QmdRuntimeEnv {
1262
+ const env: QmdRuntimeEnv = {};
1263
+ if (opts?.qmdForceCpu === true) {
1264
+ env.QMD_FORCE_CPU = "1";
1265
+ }
1266
+ if (opts?.qmdGpuBackend) {
1267
+ env.QMD_LLAMA_GPU = opts.qmdGpuBackend;
1268
+ }
1269
+ if (
1270
+ typeof opts?.qmdEmbedParallelism === "number" &&
1271
+ Number.isFinite(opts.qmdEmbedParallelism) &&
1272
+ opts.qmdEmbedParallelism > 0
1273
+ ) {
1274
+ env.QMD_EMBED_PARALLELISM = String(Math.min(8, Math.max(1, Math.floor(opts.qmdEmbedParallelism))));
1275
+ }
1276
+ if (opts?.qmdEmbedModel?.trim()) {
1277
+ env.QMD_EMBED_MODEL = opts.qmdEmbedModel.trim();
1278
+ }
1279
+ if (opts?.qmdRerankModel?.trim()) {
1280
+ env.QMD_RERANK_MODEL = opts.qmdRerankModel.trim();
1281
+ }
1282
+ if (opts?.qmdGenerateModel?.trim()) {
1283
+ env.QMD_GENERATE_MODEL = opts.qmdGenerateModel.trim();
1284
+ }
1285
+ return env;
1286
+ }
1287
+
1288
+ async probe(): Promise<boolean> {
1289
+ const cliOk = await this.probeCli();
1290
+ if (this.daemonEnabled) {
1291
+ await this.probeDaemon();
1292
+ }
1293
+ return cliOk || this.daemonAvailable;
1294
+ }
1295
+
1296
+ private async probeDaemon(): Promise<boolean> {
1297
+ this.lastDaemonCheckAtMs = Date.now();
1298
+ const normalizedPath = this.qmdPath.trim() || "qmd";
1299
+ const daemonIndexName =
1300
+ this.qmdIndexName && this.qmdCapabilities.mcpIndexSelection
1301
+ ? this.qmdIndexName
1302
+ : undefined;
1303
+ const daemonSessionPath = `${normalizedPath}\0${daemonIndexName ?? ""}\0${this.cliVersion ?? ""}`;
1304
+ if (!this.daemonSession || this.daemonSessionPath !== daemonSessionPath) {
1305
+ await releaseSharedDaemonSession(this.daemonSession);
1306
+ this.daemonSession = retainSharedDaemonSession(
1307
+ normalizedPath,
1308
+ this.qmdRuntimeEnv,
1309
+ daemonIndexName,
1310
+ this.cliVersion,
1311
+ );
1312
+ this.daemonSessionPath = daemonSessionPath;
1313
+ }
1314
+ try {
1315
+ // Race start() against a short window: if the session is already initialized
1316
+ // this returns instantly; if the process is still loading its index we fail
1317
+ // fast and let the caller fall back gracefully. The underlying start() promise
1318
+ // continues running in the background so the process is NOT killed. On the
1319
+ // next recheck cycle (daemonRecheckIntervalMs=15s) start() returns true
1320
+ // immediately once the handshake has completed.
1321
+ const PROBE_QUICK_TIMEOUT_MS = 3_000;
1322
+ const ok = await Promise.race([
1323
+ this.daemonSession.start(),
1324
+ new Promise<false>((resolve) => setTimeout(() => resolve(false), PROBE_QUICK_TIMEOUT_MS)),
1325
+ ]);
1326
+ if (!ok) {
1327
+ const loading = this.daemonSession.isLoading();
1328
+ log.debug(`QMD daemon: stdio session not ready within ${PROBE_QUICK_TIMEOUT_MS}ms probe window${loading ? " (still loading)" : ""}`);
1329
+ this.daemonAvailable = false;
1330
+ return false;
1331
+ }
1332
+ log.info(`QMD daemon: stdio session active (collection=${this.collection})`);
1333
+ this.daemonAvailable = true;
1334
+ this.daemonTransientFailures = 0;
1335
+ return true;
1336
+ } catch (err) {
1337
+ log.debug(`QMD daemon: probe failed: ${err}`);
1338
+ this.daemonAvailable = false;
1339
+ return false;
1340
+ }
1341
+ }
1342
+
1343
+ private async probeCli(): Promise<boolean> {
1344
+ const markProbeFailure = (err: unknown): void => {
1345
+ this.lastCliProbeError = err instanceof Error ? err.message : String(err);
1346
+ };
1347
+ const recordProbeSuccess = async (
1348
+ result: { stdout: string; stderr: string },
1349
+ qmdPath: string,
1350
+ source: typeof this.qmdPathSource,
1351
+ ): Promise<void> => {
1352
+ this.available = true;
1353
+ this.qmdPath = qmdPath;
1354
+ this.qmdPathSource = source;
1355
+ this.cliVersion = parseQmdVersionOutput(result.stdout, result.stderr);
1356
+ this.qmdCapabilities = resolveQmdCapabilities(this.cliVersion);
1357
+ this.lastCliProbeError = null;
1358
+ await this.maybeAutoUpgradeQmd();
1359
+ };
1360
+
1361
+ if (this.configuredQmdPath) {
1362
+ try {
1363
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, this.configuredQmdPath, undefined, this.qmdRuntimeEnv);
1364
+ await recordProbeSuccess(result, this.configuredQmdPath, "configured");
1365
+ return true;
1366
+ } catch (err) {
1367
+ markProbeFailure(err);
1368
+ // Do not hard-fail here: fall through to PATH/fallback probing.
1369
+ // This keeps recall healthy even when configured path is stale.
1370
+ this.logCliProbeWarning(
1371
+ `QMD: configured qmdPath failed (${this.configuredQmdPath}): ${this.lastCliProbeError}`,
1372
+ );
1373
+ }
1374
+ }
1375
+
1376
+ // Try PATH first
1377
+ try {
1378
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, "qmd", undefined, this.qmdRuntimeEnv);
1379
+ await recordProbeSuccess(result, "qmd", "auto-path");
1380
+ return true;
1381
+ } catch (err) {
1382
+ markProbeFailure(err);
1383
+ // Try fallback paths
1384
+ for (const fallbackPath of QMD_FALLBACK_PATHS) {
1385
+ try {
1386
+ const result = await runQmd(["--version"], QMD_PROBE_TIMEOUT_MS, fallbackPath, undefined, this.qmdRuntimeEnv);
1387
+ await recordProbeSuccess(result, fallbackPath, "auto-fallback");
1388
+ log.info(`QMD: found at ${fallbackPath}`);
1389
+ return true;
1390
+ } catch (fallbackErr) {
1391
+ markProbeFailure(fallbackErr);
1392
+ // Continue to next fallback
1393
+ }
1394
+ }
1395
+ this.available = false;
1396
+ return false;
1397
+ }
1398
+ }
1399
+
1400
+ private async maybeAutoUpgradeQmd(): Promise<void> {
1401
+ if (!this.qmdAutoUpgradeEnabled) return;
1402
+ const state = getGlobalQmdState();
1403
+ const targetKey = this.autoUpgradeTargetKey();
1404
+ const now = Date.now();
1405
+ const lastCheckAtMs = state.lastAutoUpgradeCheckByTargetMs[targetKey];
1406
+ if (
1407
+ Number.isFinite(lastCheckAtMs) &&
1408
+ now - lastCheckAtMs < this.qmdAutoUpgradeCheckIntervalMs
1409
+ ) {
1410
+ return;
1411
+ }
1412
+ state.lastAutoUpgradeCheckByTargetMs[targetKey] = now;
1413
+ state.lastAutoUpgradeCheckAtMs = now;
1414
+
1415
+ const installed = parseQmdVersion(this.cliVersion);
1416
+ const supported = parseQmdVersion(this.qmdSupportedVersion);
1417
+ if (!installed || !supported) {
1418
+ const status = `skipped: unable to parse installed=${this.cliVersion ?? "unknown"} supported=${this.qmdSupportedVersion}`;
1419
+ recordAutoUpgradeStatus(state, targetKey, status);
1420
+ log.warn(`QMD auto-upgrade skipped: ${status}`);
1421
+ return;
1422
+ }
1423
+ if (compareQmdVersions(installed, supported) >= 0) {
1424
+ recordAutoUpgradeStatus(
1425
+ state,
1426
+ targetKey,
1427
+ `current: installed=${qmdVersionToString(installed)} supported=${qmdVersionToString(supported)}`,
1428
+ );
1429
+ return;
1430
+ }
1431
+ if (this.qmdPathSource === "configured") {
1432
+ const status = `skipped: configured qmdPath=${this.qmdPath}`;
1433
+ recordAutoUpgradeStatus(state, targetKey, status);
1434
+ log.warn(
1435
+ `QMD auto-upgrade skipped because qmdPath is explicitly configured (${this.qmdPath}); install ${QMD_PACKAGE_NAME}@${this.qmdSupportedVersion} manually for that path.`,
1436
+ );
1437
+ return;
1438
+ }
1439
+
1440
+ const packageSpec = `${QMD_PACKAGE_NAME}@${this.qmdSupportedVersion}`;
1441
+ try {
1442
+ log.warn(
1443
+ `QMD auto-upgrade: installed=${qmdVersionToString(installed)} supported=${qmdVersionToString(supported)}; running npm install -g ${packageSpec}`,
1444
+ );
1445
+ await runProcessCommand(
1446
+ "npm",
1447
+ ["install", "-g", packageSpec],
1448
+ QMD_AUTO_UPGRADE_TIMEOUT_MS,
1449
+ );
1450
+ const postInstall = await this.probePostInstallQmdVersion(supported);
1451
+ this.qmdPath = postInstall.qmdPath;
1452
+ this.qmdPathSource = postInstall.source;
1453
+ this.cliVersion = postInstall.version;
1454
+ this.qmdCapabilities = resolveQmdCapabilities(this.cliVersion);
1455
+ await releaseSharedDaemonSession(this.daemonSession);
1456
+ this.daemonSession = null;
1457
+ this.daemonSessionPath = null;
1458
+ this.daemonAvailable = false;
1459
+ this.daemonTransientFailures = 0;
1460
+ const upgraded = parseQmdVersion(this.cliVersion);
1461
+ if (!upgraded || compareQmdVersions(upgraded, supported) < 0) {
1462
+ const status = `failed: post-install version=${this.cliVersion ?? "unknown"} target=${this.qmdSupportedVersion}`;
1463
+ recordAutoUpgradeStatus(state, targetKey, status);
1464
+ log.warn(`QMD auto-upgrade did not reach supported version: ${status}`);
1465
+ return;
1466
+ }
1467
+ recordAutoUpgradeStatus(
1468
+ state,
1469
+ targetKey,
1470
+ `upgraded: installed=${this.cliVersion ?? "unknown"} target=${this.qmdSupportedVersion}`,
1471
+ );
1472
+ } catch (err) {
1473
+ const msg = err instanceof Error ? err.message : String(err);
1474
+ recordAutoUpgradeStatus(state, targetKey, `failed: ${msg}`);
1475
+ log.warn(`QMD auto-upgrade failed: ${msg}`);
1476
+ }
1477
+ }
1478
+
1479
+ private async probePostInstallQmdVersion(supported: QmdVersionTuple): Promise<{
1480
+ qmdPath: string;
1481
+ source: "auto-path" | "auto-fallback";
1482
+ version: string | null;
1483
+ }> {
1484
+ let lastErr: unknown;
1485
+ let lastResult: { qmdPath: string; source: "auto-path" | "auto-fallback"; version: string | null } | null = null;
1486
+ for (const candidate of getQmdPostInstallProbeTargets(this.qmdPath, this.qmdPathSource)) {
1487
+ try {
1488
+ const result = await runQmd(
1489
+ ["--version"],
1490
+ QMD_PROBE_TIMEOUT_MS,
1491
+ candidate.qmdPath,
1492
+ undefined,
1493
+ this.qmdRuntimeEnv,
1494
+ );
1495
+ const postInstall = {
1496
+ ...candidate,
1497
+ version: parseQmdVersionOutput(result.stdout, result.stderr),
1498
+ };
1499
+ lastResult = postInstall;
1500
+ const parsed = parseQmdVersion(postInstall.version);
1501
+ if (parsed && compareQmdVersions(parsed, supported) >= 0) {
1502
+ return postInstall;
1503
+ }
1504
+ } catch (err) {
1505
+ lastErr = err;
1506
+ }
1507
+ }
1508
+ if (lastResult) return lastResult;
1509
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
1510
+ }
1511
+
1512
+ private autoUpgradeTargetKey(): string {
1513
+ return JSON.stringify({
1514
+ path: this.qmdPath.trim() || "qmd",
1515
+ source: this.qmdPathSource,
1516
+ index: this.qmdIndexName ?? "",
1517
+ supportedVersion: this.qmdSupportedVersion,
1518
+ runtimeEnv: stableRuntimeEnvKey(this.qmdRuntimeEnv),
1519
+ });
1520
+ }
1521
+
1522
+ private logCliProbeWarning(message: string): void {
1523
+ const state = getGlobalQmdState();
1524
+ const now = Date.now();
1525
+ const canWarn =
1526
+ state.lastCliWarnAtMs === null || now - state.lastCliWarnAtMs >= QMD_CLI_WARN_THROTTLE_MS;
1527
+ if (!canWarn) {
1528
+ log.debug(message);
1529
+ return;
1530
+ }
1531
+ state.lastCliWarnAtMs = now;
1532
+ if (this.daemonAvailable) {
1533
+ // Daemon mode is healthy; keep this as debug noise rather than warning.
1534
+ log.debug(message);
1535
+ return;
1536
+ }
1537
+ log.warn(message);
1538
+ }
1539
+
1540
+ /** Re-probe daemon if it was down and recheck interval has elapsed. */
1541
+ private async maybeProbeDaemon(): Promise<void> {
1542
+ if (!this.daemonEnabled) return;
1543
+ // If daemon is marked healthy and session is active, nothing to do.
1544
+ if (this.daemonAvailable && this.daemonSession?.isActive()) return;
1545
+ // If recently checked and failed, respect the recheck interval.
1546
+ if (this.daemonAvailable === false) {
1547
+ const elapsed = Date.now() - this.lastDaemonCheckAtMs;
1548
+ if (elapsed < this.daemonRecheckIntervalMs) return;
1549
+ }
1550
+ this.daemonAvailable = false;
1551
+ await this.probeDaemon();
1552
+ }
1553
+
1554
+ isAvailable(): boolean {
1555
+ return this.available === true || this.daemonAvailable;
1556
+ }
1557
+
1558
+ /** Debug string for troubleshooting availability issues. */
1559
+ debugStatus(): string {
1560
+ const cliPath = this.available ? this.qmdPath : (this.configuredQmdPath ?? "unavailable");
1561
+ const cliVersion = this.cliVersion ?? "unknown";
1562
+ const status = this.getVersionStatus();
1563
+ const enabledFeatures = Object.entries(status.capabilities)
1564
+ .filter(([key, value]) => key !== "version" && key !== "parsedVersion" && value === true)
1565
+ .map(([key]) => key)
1566
+ .join(",");
1567
+ const globalState = getGlobalQmdState();
1568
+ const autoUpgradeStatus =
1569
+ globalState.lastAutoUpgradeStatusByTarget[this.autoUpgradeTargetKey()] ??
1570
+ globalState.lastAutoUpgradeStatus;
1571
+ const probeError = this.lastCliProbeError ? ` cliProbeError=${this.lastCliProbeError}` : "";
1572
+ return `cli=${this.available} daemon=${this.daemonAvailable} session=${!!this.daemonSession} cliPath=${cliPath} cliPathSource=${this.qmdPathSource} cliVersion=${cliVersion} supportedVersion=${status.supportedVersion} upgradeAvailable=${status.upgradeAvailable} qmdFeatures=${enabledFeatures || "none"}${autoUpgradeStatus ? ` autoUpgrade=${autoUpgradeStatus}` : ""}${probeError}`;
1573
+ }
1574
+
1575
+ getVersionStatus(): QmdVersionStatus {
1576
+ const installed = parseQmdVersion(this.cliVersion);
1577
+ const supported = parseQmdVersion(this.qmdSupportedVersion) ?? parseQmdVersion(QMD_SUPPORTED_VERSION)!;
1578
+ const cmp = compareQmdVersions(installed, supported);
1579
+ return {
1580
+ installedVersion: this.cliVersion,
1581
+ supportedVersion: qmdVersionToString(supported),
1582
+ supported: installed !== null && cmp >= 0,
1583
+ newerThanSupported: installed !== null && cmp > 0,
1584
+ upgradeAvailable: installed !== null && cmp < 0,
1585
+ capabilities: this.qmdCapabilities,
1586
+ };
1587
+ }
1588
+
1589
+ async doctor(): Promise<QmdDoctorReport> {
1590
+ if (!this.isAvailable()) {
1591
+ return { available: false, skipped: "qmd unavailable" };
1592
+ }
1593
+ if (!this.qmdCapabilities.doctor) {
1594
+ return {
1595
+ available: false,
1596
+ skipped: `qmd doctor requires qmd >=2.5.0; installed ${this.cliVersion ?? "unknown"}`,
1597
+ };
1598
+ }
1599
+ try {
1600
+ const { stdout } = await this.runQmdCommand(["doctor", "--json"], QMD_TIMEOUT_MS);
1601
+ const trimmed = stdout.trim();
1602
+ if (!trimmed) return { available: true, report: null };
1603
+ try {
1604
+ return { available: true, report: JSON.parse(trimmed) };
1605
+ } catch {
1606
+ return { available: true, raw: trimmed };
1607
+ }
1608
+ } catch (err) {
1609
+ return {
1610
+ available: false,
1611
+ error: err instanceof Error ? err.message : String(err),
1612
+ };
1613
+ }
1614
+ }
1615
+
1616
+ isDaemonMode(): boolean {
1617
+ return this.daemonAvailable;
1618
+ }
1619
+
1620
+ async dispose(): Promise<void> {
1621
+ await releaseSharedDaemonSession(this.daemonSession);
1622
+ this.daemonSession = null;
1623
+ this.daemonSessionPath = null;
1624
+ this.daemonAvailable = false;
1625
+ this.daemonTransientFailures = 0;
1626
+ }
1627
+
1628
+ /**
1629
+ * Record a daemon search success — resets the transient failure counter.
1630
+ */
1631
+ private recordDaemonSuccess(): void {
1632
+ this.daemonTransientFailures = 0;
1633
+ }
1634
+
1635
+ /**
1636
+ * Handle a non-timeout, non-cancellation daemon error.
1637
+ * Tolerates up to DAEMON_MAX_TRANSIENT_FAILURES consecutive failures
1638
+ * before invalidating the session. This prevents a single transient
1639
+ * error from pushing all concurrent searches through the subprocess
1640
+ * mutex for the full recheck interval.
1641
+ */
1642
+ private handleDaemonTransientError(label: string, err: unknown, durationMs: number): void {
1643
+ // If daemon was already marked unavailable by a concurrent call, don't
1644
+ // increment further — the counter will reset on the next successful probe.
1645
+ if (!this.daemonAvailable) {
1646
+ log.debug(`QMD daemon ${label} failed after ${durationMs}ms (daemon already unavailable, ignoring): ${err}`);
1647
+ return;
1648
+ }
1649
+ this.daemonTransientFailures += 1;
1650
+ if (this.daemonTransientFailures >= QmdClient.DAEMON_MAX_TRANSIENT_FAILURES) {
1651
+ log.debug(`QMD daemon ${label} failed after ${durationMs}ms (${this.daemonTransientFailures} consecutive failures, invalidating): ${err}`);
1652
+ this.daemonSession?.invalidate();
1653
+ this.daemonAvailable = false;
1654
+ this.daemonTransientFailures = 0;
1655
+ } else {
1656
+ log.debug(`QMD daemon ${label} failed after ${durationMs}ms (transient ${this.daemonTransientFailures}/${QmdClient.DAEMON_MAX_TRANSIENT_FAILURES}): ${err}`);
1657
+ }
1658
+ }
1659
+
1660
+ private async runQmdCommand(
1661
+ args: string[],
1662
+ timeoutMs: number,
1663
+ signal?: AbortSignal,
1664
+ ): Promise<{ stdout: string; stderr: string }> {
1665
+ const commandArgs =
1666
+ this.qmdIndexName && this.qmdCapabilities.mcpIndexSelection
1667
+ ? ["--index", this.qmdIndexName, ...args]
1668
+ : args;
1669
+ return runQmd(commandArgs, timeoutMs, this.qmdPath, signal, this.qmdRuntimeEnv);
1670
+ }
1671
+
1672
+ private supportsIntentHints(): boolean {
1673
+ return this.qmdCapabilities.intentHints;
1674
+ }
1675
+
1676
+ private supportsExplainTraces(): boolean {
1677
+ return this.qmdCapabilities.explainTraces;
1678
+ }
1679
+
1680
+ private supportsCandidateLimit(): boolean {
1681
+ return this.qmdCapabilities.candidateLimit;
1682
+ }
1683
+
1684
+ private supportsRerankToggle(): boolean {
1685
+ return this.qmdCapabilities.queryRerankToggle;
1686
+ }
1687
+
1688
+ private supportsChunkStrategy(): boolean {
1689
+ return this.qmdCapabilities.chunkStrategy;
1690
+ }
1691
+
1692
+ /**
1693
+ * QMD v2 (>= 2.0.0) uses a new MCP tool API:
1694
+ * - `search` and `vsearch` tools removed; only `query` tool exists
1695
+ * - `query` accepts `{ searches: [{ type, query }], collections?: string[] }`
1696
+ * instead of `{ query: string, collection?: string }`
1697
+ * - `collection` (singular) → `collections` (plural array)
1698
+ */
1699
+ private isQmdV2(): boolean {
1700
+ return this.qmdCapabilities.v2McpQueryTool;
1701
+ }
1702
+
1703
+ private resolveSearchOptions(options?: SearchQueryOptions): SearchQueryOptions | undefined {
1704
+ const normalized = normalizeSearchOptions(options);
1705
+ const withDefaults: SearchQueryOptions = { ...(normalized ?? {}) };
1706
+ if (this.qmdCandidateLimit !== undefined && withDefaults.candidateLimit === undefined) {
1707
+ withDefaults.candidateLimit = this.qmdCandidateLimit;
1708
+ }
1709
+ if (!this.qmdQueryRerankEnabled && withDefaults.rerank === undefined) {
1710
+ withDefaults.rerank = false;
1711
+ }
1712
+ if (this.qmdChunkStrategy && withDefaults.chunkStrategy === undefined) {
1713
+ withDefaults.chunkStrategy = this.qmdChunkStrategy;
1714
+ }
1715
+ const resolved: SearchQueryOptions = {};
1716
+ if (withDefaults.intent && this.supportsIntentHints()) {
1717
+ resolved.intent = withDefaults.intent;
1718
+ }
1719
+ if (withDefaults.explain === true && this.supportsExplainTraces()) {
1720
+ resolved.explain = true;
1721
+ }
1722
+ if (
1723
+ typeof withDefaults.candidateLimit === "number" &&
1724
+ withDefaults.candidateLimit > 0 &&
1725
+ this.supportsCandidateLimit()
1726
+ ) {
1727
+ resolved.candidateLimit = Math.floor(withDefaults.candidateLimit);
1728
+ }
1729
+ if (withDefaults.rerank === false && this.supportsRerankToggle()) {
1730
+ resolved.rerank = false;
1731
+ }
1732
+ if (withDefaults.chunkStrategy && this.supportsChunkStrategy()) {
1733
+ resolved.chunkStrategy = withDefaults.chunkStrategy;
1734
+ }
1735
+ if (this.qmdCapabilities.structuredSearches) {
1736
+ const structuredSearches = normalizeStructuredSearches(withDefaults.structuredSearches);
1737
+ if (structuredSearches.length > 0) {
1738
+ resolved.structuredSearches = structuredSearches;
1739
+ }
1740
+ }
1741
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
1742
+ }
1743
+
1744
+ resolveSupportedSearchOptions(options?: SearchQueryOptions): SearchQueryOptions | undefined {
1745
+ return this.resolveSearchOptions(options);
1746
+ }
1747
+
1748
+ private addResolvedSearchOptionsToArgs(args: string[], options?: SearchQueryOptions): void {
1749
+ if (options?.intent) {
1750
+ args.push("--intent", options.intent);
1751
+ }
1752
+ if (options?.explain === true) {
1753
+ args.push("--explain");
1754
+ }
1755
+ if (typeof options?.candidateLimit === "number" && options.candidateLimit > 0) {
1756
+ args.push("--candidate-limit", String(Math.floor(options.candidateLimit)));
1757
+ }
1758
+ if (options?.rerank === false) {
1759
+ args.push("--no-rerank");
1760
+ }
1761
+ if (options?.chunkStrategy) {
1762
+ args.push("--chunk-strategy", options.chunkStrategy);
1763
+ }
1764
+ }
1765
+
1766
+ private addResolvedSearchOptionsToMcpArgs(
1767
+ args: Record<string, unknown>,
1768
+ options?: SearchQueryOptions,
1769
+ ): void {
1770
+ if (options?.intent) {
1771
+ args.intent = options.intent;
1772
+ }
1773
+ if (options?.explain === true) {
1774
+ args.explain = true;
1775
+ }
1776
+ if (typeof options?.candidateLimit === "number" && options.candidateLimit > 0) {
1777
+ args.candidateLimit = Math.floor(options.candidateLimit);
1778
+ }
1779
+ if (options?.rerank === false) {
1780
+ args.rerank = false;
1781
+ }
1782
+ // QMD 2.5.1 MCP query does not expose chunkStrategy even though CLI/SDK
1783
+ // search and embed do. Keep chunk strategy on CLI/embed paths only.
1784
+ }
1785
+
1786
+ private buildEmbedArgs(collection: string, force = false): string[] {
1787
+ const args = ["embed"];
1788
+ if (force) args.push("-f");
1789
+ args.push("-c", collection);
1790
+ if (this.qmdChunkStrategy && this.qmdCapabilities.chunkStrategy) {
1791
+ args.push("--chunk-strategy", this.qmdChunkStrategy);
1792
+ }
1793
+ return args;
1794
+ }
1795
+
1796
+ async search(
1797
+ query: string,
1798
+ collection?: string,
1799
+ maxResults?: number,
1800
+ options?: SearchQueryOptions,
1801
+ execution?: SearchExecutionOptions,
1802
+ ): Promise<QmdSearchResult[]> {
1803
+ if (!this.isAvailable()) return [];
1804
+ const trimmed = query.trim();
1805
+ if (!trimmed) return [];
1806
+
1807
+ const col = collection ?? this.collection;
1808
+ const n = maxResults ?? this.maxResults;
1809
+ const searchOptions = this.resolveSearchOptions(options);
1810
+
1811
+ // Short-lived search result cache — avoids redundant daemon calls for
1812
+ // repeated queries within the same recall cycle (e.g., primary + hybrid
1813
+ // top-up, or conversation recall using the same collection).
1814
+ const optionsFingerprint = searchOptions ? JSON.stringify(searchOptions) : "";
1815
+ const cacheKey = createHash("sha256").update(`${col}:${n}:${optionsFingerprint}:${trimmed}`).digest("hex");
1816
+ const cached = getCachedQmdSearch(cacheKey);
1817
+ if (cached) {
1818
+ log.debug(`QMD search cache hit (${cached.length} results)`);
1819
+ return cached as QmdSearchResult[];
1820
+ }
1821
+
1822
+ // Try daemon first (bypasses QMD_MUTEX — daemon handles its own concurrency)
1823
+ await this.maybeProbeDaemon();
1824
+ if (this.daemonAvailable) {
1825
+ let results: QmdSearchResult[] | null;
1826
+ try {
1827
+ results = await this.searchViaDaemon(trimmed, col, n, searchOptions, execution?.signal);
1828
+ } catch (err) {
1829
+ if (isCallerCancellation(err, execution?.signal)) {
1830
+ throw isAbortError(err) ? err : abortError("QMD daemon search aborted");
1831
+ }
1832
+ throw err;
1833
+ }
1834
+ // When the daemon is available, trust its outcome and skip the subprocess.
1835
+ // The subprocess runs `qmd query` (BM25 + LLM expansion) which hangs at
1836
+ // 99% CPU on large collections (75K+ files) making it strictly worse than
1837
+ // the daemon for this workload. Specifically:
1838
+ // results !== null → daemon succeeded (even with 0 hits) → return as-is
1839
+ // results === null → daemon timed-out or errored → still skip subprocess
1840
+ // because subprocess will also hang or timeout
1841
+ if (results !== null) {
1842
+ if (results.length === 0) {
1843
+ log.debug("QMD daemon search returned 0 results; skipping subprocess");
1844
+ }
1845
+ setCachedQmdSearch(cacheKey, results);
1846
+ return results;
1847
+ }
1848
+ // Daemon timed out or had a transient error — skip subprocess for large
1849
+ // collections. Return empty rather than hanging the caller.
1850
+ log.debug("QMD daemon search timed out/failed; skipping subprocess (daemon-only mode)");
1851
+ return [];
1852
+ }
1853
+
1854
+ // If the daemon process is spawned but still loading (handshake not yet complete),
1855
+ // skip subprocess — it would add load and block under QMD_MUTEX without helping.
1856
+ // Return empty and let the next recheck cycle pick up the daemon once ready.
1857
+ if (this.daemonSession?.isLoading()) {
1858
+ log.debug("QMD search: daemon loading, skipping subprocess");
1859
+ return [];
1860
+ }
1861
+
1862
+ // Subprocess fallback (only reached when daemon is unavailable and not loading)
1863
+ const subprocessResults = await this.searchViaSubprocess(trimmed, col, n, searchOptions, execution?.signal);
1864
+ setCachedQmdSearch(cacheKey, subprocessResults);
1865
+ return subprocessResults;
1866
+ }
1867
+
1868
+ async searchGlobal(
1869
+ query: string,
1870
+ maxResults?: number,
1871
+ execution?: SearchExecutionOptions,
1872
+ ): Promise<QmdSearchResult[]> {
1873
+ if (!this.isAvailable()) return [];
1874
+ const trimmed = query.trim();
1875
+ if (!trimmed) return [];
1876
+
1877
+ const n = maxResults ?? 6;
1878
+ const searchOptions = this.resolveSearchOptions();
1879
+
1880
+ // Try daemon first
1881
+ await this.maybeProbeDaemon();
1882
+ if (this.daemonAvailable) {
1883
+ // Global search: no collection filter
1884
+ let results: QmdSearchResult[] | null;
1885
+ try {
1886
+ results = await this.searchViaDaemon(trimmed, undefined, n, searchOptions, execution?.signal);
1887
+ } catch (err) {
1888
+ if (isCallerCancellation(err, execution?.signal)) {
1889
+ throw isAbortError(err) ? err : abortError("QMD daemon global search aborted");
1890
+ }
1891
+ throw err;
1892
+ }
1893
+ // Same rationale as search() — trust daemon outcome, skip subprocess.
1894
+ if (results !== null) {
1895
+ if (results.length === 0) {
1896
+ log.debug("QMD daemon global search returned 0 results; skipping subprocess");
1897
+ }
1898
+ return results;
1899
+ }
1900
+ log.debug("QMD daemon global search timed out/failed; skipping subprocess (daemon-only mode)");
1901
+ return [];
1902
+ }
1903
+
1904
+ // If the daemon is spawned but still loading, skip subprocess — same as search().
1905
+ if (this.daemonSession?.isLoading()) {
1906
+ log.debug("QMD searchGlobal: daemon loading, skipping subprocess");
1907
+ return [];
1908
+ }
1909
+
1910
+ // Subprocess fallback (only reached when daemon is unavailable and not loading)
1911
+ return this.searchGlobalViaSubprocess(trimmed, n, searchOptions, execution?.signal);
1912
+ }
1913
+
1914
+ /**
1915
+ * BM25 keyword search (fast, ~0.3s). Uses `qmd search`.
1916
+ */
1917
+ async bm25Search(
1918
+ query: string,
1919
+ collection?: string,
1920
+ maxResults?: number,
1921
+ execution?: SearchExecutionOptions,
1922
+ ): Promise<QmdSearchResult[]> {
1923
+ if (!this.isAvailable()) return [];
1924
+ const trimmed = query.trim();
1925
+ if (!trimmed) return [];
1926
+ const col = collection ?? this.collection;
1927
+ const n = maxResults ?? this.maxResults;
1928
+
1929
+ // Try daemon first — BM25 via daemon is much faster than subprocess.
1930
+ await this.maybeProbeDaemon();
1931
+ if (this.daemonAvailable && this.daemonSession) {
1932
+ let results: QmdSearchResult[] | null;
1933
+ try {
1934
+ results = await this.bm25SearchViaDaemon(trimmed, col, n, execution?.signal);
1935
+ } catch (err) {
1936
+ if (isCallerCancellation(err, execution?.signal)) {
1937
+ throw isAbortError(err) ? err : abortError("QMD daemon bm25 aborted");
1938
+ }
1939
+ throw err;
1940
+ }
1941
+ // When daemon is available, trust its outcome and skip subprocess (same
1942
+ // rationale as search() — subprocess hangs at 99% CPU on 75K+ files).
1943
+ if (results !== null) {
1944
+ if (results.length === 0) {
1945
+ log.debug("QMD daemon bm25 returned 0 results; skipping subprocess");
1946
+ }
1947
+ return results;
1948
+ }
1949
+ log.debug("QMD daemon bm25 timed out/failed; skipping subprocess (daemon-only mode)");
1950
+ return [];
1951
+ }
1952
+ if (this.daemonSession?.isLoading()) {
1953
+ log.debug("QMD bm25: daemon loading, skipping subprocess");
1954
+ return [];
1955
+ }
1956
+ return this.bm25SearchViaSubprocess(trimmed, col, n, execution?.signal);
1957
+ }
1958
+
1959
+ /**
1960
+ * Vector similarity search (~3-4s). Uses `qmd vsearch`.
1961
+ */
1962
+ async vectorSearch(
1963
+ query: string,
1964
+ collection?: string,
1965
+ maxResults?: number,
1966
+ execution?: SearchExecutionOptions,
1967
+ ): Promise<QmdSearchResult[]> {
1968
+ if (!this.isAvailable()) return [];
1969
+ const trimmed = query.trim();
1970
+ if (!trimmed) return [];
1971
+ const col = collection ?? this.collection;
1972
+ const n = maxResults ?? this.maxResults;
1973
+
1974
+ // Try daemon first — keeps models warm, avoids cold subprocess loads.
1975
+ await this.maybeProbeDaemon();
1976
+ if (this.daemonAvailable && this.daemonSession) {
1977
+ let results: QmdSearchResult[] | null;
1978
+ try {
1979
+ results = await this.vsearchViaDaemon(trimmed, col, n, execution?.signal);
1980
+ } catch (err) {
1981
+ if (isCallerCancellation(err, execution?.signal)) {
1982
+ throw isAbortError(err) ? err : abortError("QMD daemon vsearch aborted");
1983
+ }
1984
+ throw err;
1985
+ }
1986
+ // When daemon is available, trust its outcome and skip subprocess (same
1987
+ // rationale as search() — subprocess hangs at 99% CPU on 75K+ files).
1988
+ if (results !== null) {
1989
+ if (results.length === 0) {
1990
+ log.debug("QMD daemon vsearch returned 0 results; skipping subprocess");
1991
+ }
1992
+ return results;
1993
+ }
1994
+ log.debug("QMD daemon vsearch timed out/failed; skipping subprocess (daemon-only mode)");
1995
+ return [];
1996
+ }
1997
+ if (this.daemonSession?.isLoading()) {
1998
+ log.debug("QMD vsearch: daemon loading, skipping subprocess");
1999
+ return [];
2000
+ }
2001
+ return this.vsearchViaSubprocess(trimmed, col, n, execution?.signal);
2002
+ }
2003
+
2004
+ /**
2005
+ * Hybrid search: runs BM25 + vector in parallel, merges/dedupes by path
2006
+ * keeping the best score and first non-empty snippet.
2007
+ */
2008
+ async hybridSearch(
2009
+ query: string,
2010
+ collection?: string,
2011
+ maxResults?: number,
2012
+ execution?: SearchExecutionOptions,
2013
+ ): Promise<QmdSearchResult[]> {
2014
+ const n = maxResults ?? this.maxResults;
2015
+ const trimmed = query.trim();
2016
+ if (!trimmed) return [];
2017
+
2018
+ const [bm25Results, vectorResults] = await Promise.all([
2019
+ this.bm25Search(trimmed, collection, n, execution),
2020
+ this.vectorSearch(trimmed, collection, n, execution),
2021
+ ]);
2022
+
2023
+ // Merge by path, keeping best score
2024
+ const merged = new Map<string, QmdSearchResult>();
2025
+ for (const r of [...bm25Results, ...vectorResults]) {
2026
+ const key = r.path || r.docid;
2027
+ const existing = merged.get(key);
2028
+ if (!existing || r.score > existing.score) {
2029
+ merged.set(key, {
2030
+ ...r,
2031
+ snippet: r.snippet || existing?.snippet || "",
2032
+ });
2033
+ }
2034
+ }
2035
+
2036
+ // Sort by score descending, take top N
2037
+ return [...merged.values()]
2038
+ .sort((a, b) => b.score - a.score)
2039
+ .slice(0, n);
2040
+ }
2041
+
2042
+ private async searchViaDaemon(
2043
+ query: string,
2044
+ collection: string | undefined,
2045
+ maxResults: number,
2046
+ options?: SearchQueryOptions,
2047
+ signal?: AbortSignal,
2048
+ ): Promise<QmdSearchResult[] | null> {
2049
+ if (!this.daemonSession || !this.daemonAvailable) return null;
2050
+
2051
+ const startedAtMs = Date.now();
2052
+ const v2 = this.isQmdV2();
2053
+ try {
2054
+ let args: Record<string, unknown>;
2055
+ if (v2) {
2056
+ // QMD v2: query tool expects { searches: [...], collections?: [...] }
2057
+ // The MCP tool is structured-only in 2.x; use lex+vec+hyde by default
2058
+ // to exercise QMD's RRF + rerank path and let callers override when
2059
+ // they have stronger query-document structure.
2060
+ const searches = buildDefaultStructuredSearches(query, options);
2061
+ args = { searches, limit: maxResults };
2062
+ if (collection) {
2063
+ args.collections = [collection];
2064
+ }
2065
+ this.addResolvedSearchOptionsToMcpArgs(args, options);
2066
+ } else {
2067
+ // QMD v1: query tool accepts { query, collection?, limit }
2068
+ args = { query, limit: maxResults };
2069
+ if (collection) {
2070
+ args.collection = collection;
2071
+ }
2072
+ this.addResolvedSearchOptionsToMcpArgs(args, options);
2073
+ }
2074
+
2075
+ const result = await this.daemonSession.callTool("query", args, QMD_DAEMON_TIMEOUT_MS, signal);
2076
+ const durationMs = Date.now() - startedAtMs;
2077
+
2078
+ if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
2079
+ log.warn(
2080
+ `SLOW QMD daemon query: durationMs=${durationMs} collection=${collection ?? "global"} maxResults=${maxResults} queryChars=${query.length} v2=${v2}`,
2081
+ );
2082
+ }
2083
+
2084
+ const results = parseMcpSearchResult(result, "daemon");
2085
+
2086
+ log.debug(`QMD daemon search: ${results.length} results in ${durationMs}ms (v2=${v2})`);
2087
+ this.recordDaemonSuccess();
2088
+ return results;
2089
+ } catch (err) {
2090
+ const durationMs = Date.now() - startedAtMs;
2091
+ if (isCallerCancellation(err, signal)) {
2092
+ log.debug(`QMD daemon search aborted/cancelled after ${durationMs}ms`);
2093
+ throw isAbortError(err) ? err : abortError("QMD daemon search aborted");
2094
+ }
2095
+ // Timeout: don't invalidate session — daemon is still running, just slow.
2096
+ if (isDaemonTimeoutError(err)) {
2097
+ log.debug(`QMD daemon search timed out after ${durationMs}ms, falling back to subprocess`);
2098
+ return null;
2099
+ }
2100
+ // Transient error: tolerate a few before invalidating.
2101
+ this.handleDaemonTransientError("search", err, durationMs);
2102
+ return null;
2103
+ }
2104
+ }
2105
+
2106
+ private async bm25SearchViaDaemon(
2107
+ query: string,
2108
+ collection: string,
2109
+ maxResults: number,
2110
+ signal?: AbortSignal,
2111
+ ): Promise<QmdSearchResult[] | null> {
2112
+ if (!this.daemonSession || !this.daemonAvailable) return null;
2113
+
2114
+ const startedAtMs = Date.now();
2115
+ const v2 = this.isQmdV2();
2116
+ try {
2117
+ let result: unknown;
2118
+ if (v2) {
2119
+ // QMD v2: no `search` tool — use `query` with lex-only sub-query
2120
+ result = await this.daemonSession.callTool(
2121
+ "query",
2122
+ {
2123
+ searches: [{ type: "lex", query }],
2124
+ collections: [collection],
2125
+ limit: maxResults,
2126
+ },
2127
+ QMD_DAEMON_TIMEOUT_MS,
2128
+ signal,
2129
+ );
2130
+ } else {
2131
+ // QMD v1: dedicated `search` tool for BM25
2132
+ result = await this.daemonSession.callTool(
2133
+ "search",
2134
+ { query, limit: maxResults, collection },
2135
+ QMD_DAEMON_TIMEOUT_MS,
2136
+ signal,
2137
+ );
2138
+ }
2139
+ const durationMs = Date.now() - startedAtMs;
2140
+ const results = parseMcpSearchResult(result);
2141
+ log.debug(`QMD daemon bm25: ${results.length} results in ${durationMs}ms (v2=${v2})`);
2142
+ this.recordDaemonSuccess();
2143
+ return results;
2144
+ } catch (err) {
2145
+ const durationMs = Date.now() - startedAtMs;
2146
+ if (isCallerCancellation(err, signal)) {
2147
+ log.debug(`QMD daemon bm25 aborted/cancelled after ${durationMs}ms`);
2148
+ throw isAbortError(err) ? err : abortError("QMD daemon bm25 aborted");
2149
+ }
2150
+ if (isDaemonTimeoutError(err)) {
2151
+ log.debug(`QMD daemon bm25 timed out after ${durationMs}ms, falling back to subprocess`);
2152
+ return null;
2153
+ }
2154
+ this.handleDaemonTransientError("bm25", err, durationMs);
2155
+ return null;
2156
+ }
2157
+ }
2158
+
2159
+ private async vsearchViaDaemon(
2160
+ query: string,
2161
+ collection: string,
2162
+ maxResults: number,
2163
+ signal?: AbortSignal,
2164
+ ): Promise<QmdSearchResult[] | null> {
2165
+ if (!this.daemonSession || !this.daemonAvailable) return null;
2166
+
2167
+ const startedAtMs = Date.now();
2168
+ const v2 = this.isQmdV2();
2169
+ try {
2170
+ let result: unknown;
2171
+ if (v2) {
2172
+ // QMD v2: no `vsearch` tool — use `query` with vec-only sub-query
2173
+ result = await this.daemonSession.callTool(
2174
+ "query",
2175
+ {
2176
+ searches: [{ type: "vec", query }],
2177
+ collections: [collection],
2178
+ limit: maxResults,
2179
+ },
2180
+ QMD_DAEMON_TIMEOUT_MS,
2181
+ signal,
2182
+ );
2183
+ } else {
2184
+ // QMD v1: dedicated `vsearch` tool for vector search
2185
+ result = await this.daemonSession.callTool(
2186
+ "vsearch",
2187
+ { query, limit: maxResults, collection },
2188
+ QMD_DAEMON_TIMEOUT_MS,
2189
+ signal,
2190
+ );
2191
+ }
2192
+ const durationMs = Date.now() - startedAtMs;
2193
+ const results = parseMcpSearchResult(result);
2194
+ log.debug(`QMD daemon vsearch: ${results.length} results in ${durationMs}ms (v2=${v2})`);
2195
+ this.recordDaemonSuccess();
2196
+ return results;
2197
+ } catch (err) {
2198
+ const durationMs = Date.now() - startedAtMs;
2199
+ if (isCallerCancellation(err, signal)) {
2200
+ log.debug(`QMD daemon vsearch aborted/cancelled after ${durationMs}ms`);
2201
+ throw isAbortError(err) ? err : abortError("QMD daemon vsearch aborted");
2202
+ }
2203
+ if (isDaemonTimeoutError(err)) {
2204
+ log.debug(`QMD daemon vsearch timed out after ${durationMs}ms, falling back to subprocess`);
2205
+ return null;
2206
+ }
2207
+ this.handleDaemonTransientError("vsearch", err, durationMs);
2208
+ return null;
2209
+ }
2210
+ }
2211
+
2212
+ private async searchViaSubprocess(
2213
+ query: string,
2214
+ collection: string,
2215
+ maxResults: number,
2216
+ options?: SearchQueryOptions,
2217
+ signal?: AbortSignal,
2218
+ ): Promise<QmdSearchResult[]> {
2219
+ if (this.available === false) return [];
2220
+
2221
+ const startedAtMs = Date.now();
2222
+ try {
2223
+ const args = ["query", query, "-c", collection, "--json", "-n", String(maxResults)];
2224
+ this.addResolvedSearchOptionsToArgs(args, options);
2225
+ const { stdout } = await this.runQmdCommand(args, QMD_TIMEOUT_MS, signal);
2226
+ const durationMs = Date.now() - startedAtMs;
2227
+ if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
2228
+ log.warn(
2229
+ `SLOW QMD query: durationMs=${durationMs} collection=${collection} maxResults=${maxResults} queryChars=${query.length}`,
2230
+ );
2231
+ }
2232
+
2233
+ return parseQmdSearchStdout(stdout, "subprocess");
2234
+ } catch (err) {
2235
+ if (isCallerCancellation(err, signal)) {
2236
+ throw isAbortError(err) ? err : abortError("QMD subprocess search aborted");
2237
+ }
2238
+ log.debug(`QMD search failed: ${err}`);
2239
+ return [];
2240
+ }
2241
+ }
2242
+
2243
+ private async bm25SearchViaSubprocess(
2244
+ query: string,
2245
+ collection: string,
2246
+ maxResults: number,
2247
+ signal?: AbortSignal,
2248
+ ): Promise<QmdSearchResult[]> {
2249
+ if (this.available === false) return [];
2250
+ const startedAtMs = Date.now();
2251
+ try {
2252
+ const { stdout } = await this.runQmdCommand(
2253
+ ["search", query, "-c", collection, "--json", "-n", String(maxResults)],
2254
+ QMD_TIMEOUT_MS,
2255
+ signal,
2256
+ );
2257
+ log.debug(`QMD bm25: ${Date.now() - startedAtMs}ms`);
2258
+ return parseQmdSearchStdout(stdout);
2259
+ } catch (err) {
2260
+ if (isCallerCancellation(err, signal)) {
2261
+ throw isAbortError(err) ? err : abortError("QMD subprocess bm25 aborted");
2262
+ }
2263
+ log.debug(`QMD bm25 search failed: ${err}`);
2264
+ return [];
2265
+ }
2266
+ }
2267
+
2268
+ private async vsearchViaSubprocess(
2269
+ query: string,
2270
+ collection: string,
2271
+ maxResults: number,
2272
+ signal?: AbortSignal,
2273
+ ): Promise<QmdSearchResult[]> {
2274
+ if (this.available === false) return [];
2275
+ const startedAtMs = Date.now();
2276
+ try {
2277
+ const { stdout } = await this.runQmdCommand(
2278
+ ["vsearch", query, "-c", collection, "--json", "-n", String(maxResults)],
2279
+ QMD_TIMEOUT_MS,
2280
+ signal,
2281
+ );
2282
+ log.debug(`QMD vsearch: ${Date.now() - startedAtMs}ms`);
2283
+ return parseQmdSearchStdout(stdout);
2284
+ } catch (err) {
2285
+ if (isCallerCancellation(err, signal)) {
2286
+ throw isAbortError(err) ? err : abortError("QMD subprocess vsearch aborted");
2287
+ }
2288
+ log.debug(`QMD vsearch failed: ${err}`);
2289
+ return [];
2290
+ }
2291
+ }
2292
+
2293
+ private async searchGlobalViaSubprocess(
2294
+ query: string,
2295
+ maxResults: number,
2296
+ options?: SearchQueryOptions,
2297
+ signal?: AbortSignal,
2298
+ ): Promise<QmdSearchResult[]> {
2299
+ if (this.available === false) return [];
2300
+
2301
+ const startedAtMs = Date.now();
2302
+ try {
2303
+ const args = ["query", query, "--json", "-n", String(maxResults)];
2304
+ this.addResolvedSearchOptionsToArgs(args, options);
2305
+ const { stdout } = await this.runQmdCommand(args, QMD_TIMEOUT_MS, signal);
2306
+ const durationMs = Date.now() - startedAtMs;
2307
+ if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
2308
+ log.warn(
2309
+ `SLOW QMD global query: durationMs=${durationMs} maxResults=${maxResults} queryChars=${query.length}`,
2310
+ );
2311
+ }
2312
+
2313
+ return parseQmdSearchStdout(stdout);
2314
+ } catch (err) {
2315
+ if (isCallerCancellation(err, signal)) {
2316
+ throw isAbortError(err) ? err : abortError("QMD subprocess global search aborted");
2317
+ }
2318
+ log.debug(`QMD global search failed: ${err}`);
2319
+ return [];
2320
+ }
2321
+ }
2322
+
2323
+ async update(execution?: SearchExecutionOptions): Promise<void> {
2324
+ await this.runUpdateForCollection(
2325
+ this.collection,
2326
+ { perCollectionThrottle: false },
2327
+ execution?.signal,
2328
+ );
2329
+ }
2330
+
2331
+ async updateCollection(
2332
+ collection: string,
2333
+ execution?: SearchExecutionOptions,
2334
+ ): Promise<void> {
2335
+ await this.runUpdateForCollection(
2336
+ collection,
2337
+ { perCollectionThrottle: true },
2338
+ execution?.signal,
2339
+ );
2340
+ }
2341
+
2342
+ async updateCollectionStrict(
2343
+ collection: string,
2344
+ execution?: SearchExecutionOptions,
2345
+ ): Promise<void> {
2346
+ await this.runUpdateForCollection(
2347
+ collection,
2348
+ { perCollectionThrottle: true, strict: true },
2349
+ execution?.signal,
2350
+ );
2351
+ }
2352
+
2353
+ updatesAllCollections(): boolean {
2354
+ return true;
2355
+ }
2356
+
2357
+ private async runUpdateForCollection(
2358
+ collection: string,
2359
+ options: { perCollectionThrottle: boolean; strict?: boolean },
2360
+ signal?: AbortSignal,
2361
+ ): Promise<void> {
2362
+ if (this.available === false) {
2363
+ if (options.strict) {
2364
+ throw new Error("QMD unavailable");
2365
+ }
2366
+ return;
2367
+ }
2368
+ const name = collection.trim();
2369
+ if (!name) {
2370
+ if (options.strict) {
2371
+ throw new Error("QMD collection name is required");
2372
+ }
2373
+ return;
2374
+ }
2375
+ const globalState = getGlobalQmdState();
2376
+ const now = Date.now();
2377
+ if (!options.strict && options.perCollectionThrottle) {
2378
+ if (
2379
+ globalState.lastGlobalUpdateFailAtMs &&
2380
+ now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS
2381
+ ) {
2382
+ log.debug("QMD update: suppressed by global failure backoff");
2383
+ return;
2384
+ }
2385
+ const lastCollectionRun = globalState.lastUpdateByCollectionMs[name];
2386
+ if (
2387
+ Number.isFinite(lastCollectionRun) &&
2388
+ now - lastCollectionRun < this.updateMinIntervalMs
2389
+ ) {
2390
+ log.debug(`QMD update: suppressed by per-collection min-interval gate (${name})`);
2391
+ return;
2392
+ }
2393
+ const lastCollectionFail = globalState.lastUpdateFailByCollectionMs[name];
2394
+ if (
2395
+ Number.isFinite(lastCollectionFail) &&
2396
+ now - lastCollectionFail < QMD_UPDATE_BACKOFF_MS
2397
+ ) {
2398
+ log.debug(`QMD update: suppressed by per-collection failure backoff (${name})`);
2399
+ return;
2400
+ }
2401
+ } else if (!options.strict) {
2402
+ if (
2403
+ this.lastUpdateRunAtMs &&
2404
+ now - this.lastUpdateRunAtMs < this.updateMinIntervalMs
2405
+ ) {
2406
+ log.debug("QMD update: suppressed due to min-interval gate");
2407
+ return;
2408
+ }
2409
+ if (
2410
+ this._lastUpdateFailAtMs &&
2411
+ now - this._lastUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS
2412
+ ) {
2413
+ log.debug("QMD update: suppressed due to recent failures (backoff)");
2414
+ return;
2415
+ }
2416
+ if (
2417
+ globalState.lastGlobalUpdateRunAtMs &&
2418
+ now - globalState.lastGlobalUpdateRunAtMs < this.updateMinIntervalMs
2419
+ ) {
2420
+ log.debug("QMD update: suppressed by global min-interval gate");
2421
+ return;
2422
+ }
2423
+ if (
2424
+ globalState.lastGlobalUpdateFailAtMs &&
2425
+ now - globalState.lastGlobalUpdateFailAtMs < QMD_UPDATE_BACKOFF_MS
2426
+ ) {
2427
+ log.debug("QMD update: suppressed by global failure backoff");
2428
+ return;
2429
+ }
2430
+ }
2431
+ try {
2432
+ if (!globalState.warnedGlobalUpdateBehavior) {
2433
+ globalState.warnedGlobalUpdateBehavior = true;
2434
+ log.warn(
2435
+ "QMD update runs globally across collections in current CLI versions; Engram now rate-limits update calls to reduce gateway load.",
2436
+ );
2437
+ }
2438
+ const startedAtMs = Date.now();
2439
+ await this.runQmdCommand(["update", "-c", name], this.updateTimeoutMs, signal);
2440
+ const durationMs = Date.now() - startedAtMs;
2441
+ if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
2442
+ log.warn(`SLOW QMD update: durationMs=${durationMs}`);
2443
+ }
2444
+ const at = Date.now();
2445
+ if (options.perCollectionThrottle) {
2446
+ globalState.lastUpdateByCollectionMs[name] = at;
2447
+ globalState.lastGlobalUpdateRunAtMs = at;
2448
+ } else {
2449
+ this.lastUpdateRunAtMs = at;
2450
+ globalState.lastGlobalUpdateRunAtMs = at;
2451
+ }
2452
+ log.debug(`QMD update completed for collection=${name}`);
2453
+ } catch (err) {
2454
+ const at = Date.now();
2455
+ if (options.perCollectionThrottle) {
2456
+ globalState.lastUpdateFailByCollectionMs[name] = at;
2457
+ globalState.lastGlobalUpdateFailAtMs = at;
2458
+ } else {
2459
+ this._lastUpdateFailAtMs = at;
2460
+ globalState.lastGlobalUpdateFailAtMs = at;
2461
+ }
2462
+ const msg = err instanceof Error ? err.message : String(err);
2463
+ log.warn(`QMD update failed for collection ${name}: ${msg}`);
2464
+ if (options.strict) {
2465
+ throw err;
2466
+ }
2467
+ }
2468
+ }
2469
+
2470
+ async embed(): Promise<void> {
2471
+ if (this.available === false) return;
2472
+ const globalState = getGlobalQmdState();
2473
+ if (
2474
+ this.lastEmbedFailAtMs &&
2475
+ Date.now() - this.lastEmbedFailAtMs < QMD_EMBED_BACKOFF_MS
2476
+ ) {
2477
+ log.debug("QMD embed: suppressed due to recent failures (backoff)");
2478
+ return;
2479
+ }
2480
+ if (
2481
+ globalState.lastGlobalEmbedRunAtMs &&
2482
+ Date.now() - globalState.lastGlobalEmbedRunAtMs < this.updateMinIntervalMs
2483
+ ) {
2484
+ log.debug("QMD embed: suppressed by global min-interval gate");
2485
+ return;
2486
+ }
2487
+ if (
2488
+ globalState.lastGlobalEmbedFailAtMs &&
2489
+ Date.now() - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS
2490
+ ) {
2491
+ log.debug("QMD embed: suppressed by global failure backoff");
2492
+ return;
2493
+ }
2494
+ try {
2495
+ const startedAtMs = Date.now();
2496
+ await this.runQmdCommand(this.buildEmbedArgs(this.collection), 300_000);
2497
+ const durationMs = Date.now() - startedAtMs;
2498
+ if (this.slowLog?.enabled && durationMs >= this.slowLog.thresholdMs) {
2499
+ log.warn(`SLOW QMD embed: durationMs=${durationMs}`);
2500
+ }
2501
+ globalState.lastGlobalEmbedRunAtMs = Date.now();
2502
+ log.debug("QMD embed completed");
2503
+ } catch (err) {
2504
+ if (isVectorDimensionMismatchError(err)) {
2505
+ try {
2506
+ log.warn("QMD embed hit a vector dimension mismatch; retrying with force re-embed");
2507
+ await this.runQmdCommand(this.buildEmbedArgs(this.collection, true), 300_000);
2508
+ globalState.lastGlobalEmbedRunAtMs = Date.now();
2509
+ this.lastEmbedFailAtMs = null;
2510
+ globalState.lastGlobalEmbedFailAtMs = null;
2511
+ log.warn("QMD embed recovered by forcing a full vector rebuild");
2512
+ return;
2513
+ } catch (retryErr) {
2514
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
2515
+ log.warn(`QMD force re-embed failed after dimension mismatch: ${retryMsg}`);
2516
+ }
2517
+ }
2518
+ const now = Date.now();
2519
+ this.lastEmbedFailAtMs = now;
2520
+ globalState.lastGlobalEmbedFailAtMs = now;
2521
+ const msg = err instanceof Error ? err.message : String(err);
2522
+ log.warn(`QMD embed failed: ${msg}`);
2523
+ }
2524
+ }
2525
+
2526
+ async embedCollection(collection: string): Promise<void> {
2527
+ if (this.available === false) return;
2528
+ const name = collection.trim();
2529
+ if (!name) return;
2530
+ const globalState = getGlobalQmdState();
2531
+ const now = Date.now();
2532
+ if (
2533
+ globalState.lastGlobalEmbedFailAtMs &&
2534
+ now - globalState.lastGlobalEmbedFailAtMs < QMD_EMBED_BACKOFF_MS
2535
+ ) {
2536
+ log.debug(`QMD embed: suppressed by global failure backoff (${name})`);
2537
+ return;
2538
+ }
2539
+ const lastCollectionRun = globalState.lastEmbedByCollectionMs[name];
2540
+ if (
2541
+ Number.isFinite(lastCollectionRun) &&
2542
+ now - lastCollectionRun < this.updateMinIntervalMs
2543
+ ) {
2544
+ log.debug(`QMD embed: suppressed by per-collection min-interval gate (${name})`);
2545
+ return;
2546
+ }
2547
+ const lastCollectionFail = globalState.lastEmbedFailByCollectionMs[name];
2548
+ if (
2549
+ Number.isFinite(lastCollectionFail) &&
2550
+ now - lastCollectionFail < QMD_EMBED_BACKOFF_MS
2551
+ ) {
2552
+ log.debug(`QMD embed: suppressed by per-collection failure backoff (${name})`);
2553
+ return;
2554
+ }
2555
+ try {
2556
+ await this.runQmdCommand(this.buildEmbedArgs(name), 300_000);
2557
+ const at = Date.now();
2558
+ globalState.lastEmbedByCollectionMs[name] = at;
2559
+ globalState.lastGlobalEmbedRunAtMs = at;
2560
+ } catch (err) {
2561
+ if (isVectorDimensionMismatchError(err)) {
2562
+ try {
2563
+ log.warn(`QMD embed for collection ${name} hit a vector dimension mismatch; retrying with force re-embed`);
2564
+ await this.runQmdCommand(this.buildEmbedArgs(name, true), 300_000);
2565
+ const recoveredAt = Date.now();
2566
+ globalState.lastEmbedByCollectionMs[name] = recoveredAt;
2567
+ globalState.lastGlobalEmbedRunAtMs = recoveredAt;
2568
+ delete globalState.lastEmbedFailByCollectionMs[name];
2569
+ globalState.lastGlobalEmbedFailAtMs = null;
2570
+ log.warn(`QMD embed for collection ${name} recovered by forcing a full vector rebuild`);
2571
+ return;
2572
+ } catch (retryErr) {
2573
+ const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
2574
+ log.warn(`QMD force re-embed failed for collection ${name}: ${retryMsg}`);
2575
+ }
2576
+ }
2577
+ const at = Date.now();
2578
+ globalState.lastEmbedFailByCollectionMs[name] = at;
2579
+ globalState.lastGlobalEmbedFailAtMs = at;
2580
+ const msg = err instanceof Error ? err.message : String(err);
2581
+ log.warn(`QMD embed failed for collection ${name}: ${msg}`);
2582
+ }
2583
+ }
2584
+
2585
+ async ensureCollection(memoryDir: string): Promise<"present" | "missing" | "unknown" | "skipped"> {
2586
+ if (this.available === false && !this.daemonAvailable) return "unknown";
2587
+ // If only daemon is available (no CLI), skip collection check
2588
+ if (this.available === false) return "skipped";
2589
+ try {
2590
+ const { stdout } = await this.runQmdCommand(["collection", "list"], QMD_TIMEOUT_MS);
2591
+ // Parse text output: "openclaw-engram (qmd://openclaw-engram/)"
2592
+ const collectionRegex = new RegExp(
2593
+ `^${this.collection}\\s+\\(qmd://`,
2594
+ "m",
2595
+ );
2596
+ if (collectionRegex.test(stdout)) {
2597
+ return "present";
2598
+ }
2599
+ } catch (err) {
2600
+ // Treat command/probe failures as unknown so callers do not disable features
2601
+ // permanently after a transient CLI or daemon hiccup.
2602
+ log.debug(
2603
+ `QMD collection check unavailable for "${this.collection}" (will not disable features): ${err instanceof Error ? err.message : String(err)}`,
2604
+ );
2605
+ return "unknown";
2606
+ }
2607
+
2608
+ log.info(
2609
+ `QMD collection "${this.collection}" not found. ` +
2610
+ `Add it to ~/.config/qmd/index.yml pointing at ${memoryDir}`,
2611
+ );
2612
+ return "missing";
2613
+ }
2614
+ }