@remnic/core 1.0.1 → 1.0.3

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 (347) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/dist/access-cli.d.ts +13 -3
  4. package/dist/access-cli.js +90 -75
  5. package/dist/access-cli.js.map +1 -1
  6. package/dist/access-http.d.ts +10 -3
  7. package/dist/access-http.js +25 -18
  8. package/dist/access-mcp.d.ts +30 -3
  9. package/dist/access-mcp.js +16 -1
  10. package/dist/access-schema.d.ts +46 -46
  11. package/dist/access-schema.js +1 -1
  12. package/dist/access-service.d.ts +65 -4
  13. package/dist/access-service.js +21 -15
  14. package/dist/active-memory-bridge.d.ts +66 -0
  15. package/dist/active-memory-bridge.js +11 -0
  16. package/dist/active-recall.d.ts +96 -0
  17. package/dist/active-recall.js +308 -0
  18. package/dist/active-recall.js.map +1 -0
  19. package/dist/behavior-learner.js +1 -1
  20. package/dist/bootstrap.d.ts +6 -3
  21. package/dist/bootstrap.js +2 -2
  22. package/dist/boxes.js +2 -2
  23. package/dist/briefing.d.ts +169 -0
  24. package/dist/briefing.js +52 -0
  25. package/dist/briefing.js.map +1 -0
  26. package/dist/buffer.d.ts +19 -5
  27. package/dist/buffer.js +2 -2
  28. package/dist/calibration.js +6 -6
  29. package/dist/causal-behavior.js +5 -5
  30. package/dist/causal-chain.js +3 -3
  31. package/dist/causal-consolidation.d.ts +22 -2
  32. package/dist/causal-consolidation.js +36 -9
  33. package/dist/causal-consolidation.js.map +1 -1
  34. package/dist/causal-retrieval.js +6 -6
  35. package/dist/causal-trajectory-graph.js +1 -1
  36. package/dist/causal-trajectory.d.ts +14 -1
  37. package/dist/causal-trajectory.js +5 -1
  38. package/dist/{chunk-KWBU5S5U.js → chunk-2ODBA7MQ.js} +9 -3
  39. package/dist/chunk-2ODBA7MQ.js.map +1 -0
  40. package/dist/{chunk-6UJQNRIO.js → chunk-2VFW5K5U.js} +93 -36
  41. package/dist/chunk-2VFW5K5U.js.map +1 -0
  42. package/dist/chunk-3PG3H5TD.js +7 -0
  43. package/dist/chunk-3PG3H5TD.js.map +1 -0
  44. package/dist/{chunk-NTTLPF7F.js → chunk-3QFQGRHO.js} +5 -5
  45. package/dist/chunk-4DJQYKMN.js +187 -0
  46. package/dist/chunk-4DJQYKMN.js.map +1 -0
  47. package/dist/chunk-4KAN3GZ3.js +225 -0
  48. package/dist/chunk-4KAN3GZ3.js.map +1 -0
  49. package/dist/{chunk-ORZMT74A.js → chunk-4NRAJUDS.js} +11 -1
  50. package/dist/chunk-4NRAJUDS.js.map +1 -0
  51. package/dist/{chunk-B7LOFDVE.js → chunk-4WMCPJWX.js} +8 -3
  52. package/dist/chunk-4WMCPJWX.js.map +1 -0
  53. package/dist/{chunk-G3AG3KZN.js → chunk-5IZL4DCV.js} +2 -2
  54. package/dist/{chunk-BRK4ODMI.js → chunk-5NPGSAVB.js} +2 -2
  55. package/dist/chunk-6MKAMLQL.js +16 -0
  56. package/dist/chunk-6MKAMLQL.js.map +1 -0
  57. package/dist/{chunk-ESSMF2FR.js → chunk-6PFRXT4K.js} +15 -6
  58. package/dist/chunk-6PFRXT4K.js.map +1 -0
  59. package/dist/chunk-6ZH4TU6I.js +245 -0
  60. package/dist/chunk-6ZH4TU6I.js.map +1 -0
  61. package/dist/{chunk-V4YC4LUK.js → chunk-74JR4N5J.js} +175 -63
  62. package/dist/chunk-74JR4N5J.js.map +1 -0
  63. package/dist/{chunk-L5RPWGFK.js → chunk-7DHTMOND.js} +2 -2
  64. package/dist/{chunk-TVVVQQAK.js → chunk-7PA4OZEU.js} +53 -11
  65. package/dist/chunk-7PA4OZEU.js.map +1 -0
  66. package/dist/{chunk-Q6FETXJA.js → chunk-7SEAZFFB.js} +2 -2
  67. package/dist/chunk-ALXMCZEU.js +332 -0
  68. package/dist/chunk-ALXMCZEU.js.map +1 -0
  69. package/dist/{chunk-QANCTXQF.js → chunk-AYPYCLR7.js} +3 -3
  70. package/dist/{chunk-WWIQTB2Y.js → chunk-BKQJBXXX.js} +9 -2
  71. package/dist/chunk-BKQJBXXX.js.map +1 -0
  72. package/dist/{chunk-LP47L3ZX.js → chunk-BTY5RRRF.js} +7 -7
  73. package/dist/{chunk-SCHEKPYH.js → chunk-C2EFFULQ.js} +1 -1
  74. package/dist/{chunk-GJR6D6KC.js → chunk-D654IBA6.js} +2 -2
  75. package/dist/{chunk-UV2FO7J4.js → chunk-E6K4NIEU.js} +2 -2
  76. package/dist/{chunk-T4WRIV2C.js → chunk-EABGC2TL.js} +2 -2
  77. package/dist/chunk-ECKDIK5F.js +813 -0
  78. package/dist/chunk-ECKDIK5F.js.map +1 -0
  79. package/dist/chunk-EJI5XIBB.js +232 -0
  80. package/dist/chunk-EJI5XIBB.js.map +1 -0
  81. package/dist/{chunk-ONRU4L2N.js → chunk-FEMOX5AD.js} +2 -2
  82. package/dist/{chunk-IFFFR3MR.js → chunk-FSFEQI74.js} +3 -3
  83. package/dist/chunk-G4SK7DSQ.js +121 -0
  84. package/dist/chunk-G4SK7DSQ.js.map +1 -0
  85. package/dist/{chunk-UIYZ5T3I.js → chunk-GJQPH5G3.js} +8 -8
  86. package/dist/{chunk-2PO5ZRKV.js → chunk-GZCUW5IC.js} +16 -3
  87. package/dist/chunk-GZCUW5IC.js.map +1 -0
  88. package/dist/{chunk-IZME7KW2.js → chunk-HITJFT7E.js} +24 -10
  89. package/dist/{chunk-IZME7KW2.js.map → chunk-HITJFT7E.js.map} +1 -1
  90. package/dist/chunk-IQT3XTKW.js +121 -0
  91. package/dist/chunk-IQT3XTKW.js.map +1 -0
  92. package/dist/{chunk-BDFZXRSO.js → chunk-J4IYOZZ5.js} +15 -2
  93. package/dist/chunk-J4IYOZZ5.js.map +1 -0
  94. package/dist/{chunk-ZKYI7UVO.js → chunk-JR4ZC3G4.js} +2 -2
  95. package/dist/{chunk-UCYSTFZR.js → chunk-JRNQ3RNA.js} +2 -2
  96. package/dist/{chunk-UYSKNO6E.js → chunk-JROGC36Y.js} +15 -4
  97. package/dist/chunk-JROGC36Y.js.map +1 -0
  98. package/dist/{chunk-GPGBSNKM.js → chunk-K4FLSOR5.js} +2 -2
  99. package/dist/{chunk-M5ZBBBJI.js → chunk-KEG4GNGI.js} +2 -2
  100. package/dist/chunk-KVE7R4CG.js +320 -0
  101. package/dist/chunk-KVE7R4CG.js.map +1 -0
  102. package/dist/{chunk-L7WO3MZ4.js → chunk-KWP7T3DP.js} +2 -2
  103. package/dist/chunk-LAYN4LDC.js +267 -0
  104. package/dist/chunk-LAYN4LDC.js.map +1 -0
  105. package/dist/{chunk-PGK3VUHN.js → chunk-MTLYEMJB.js} +3 -2
  106. package/dist/chunk-MTLYEMJB.js.map +1 -0
  107. package/dist/{chunk-J47FNDR7.js → chunk-MYQWXITD.js} +7 -7
  108. package/dist/{chunk-YNI4S5WT.js → chunk-N53K2EXC.js} +2 -2
  109. package/dist/{chunk-763GUIOU.js → chunk-NBNN5GOB.js} +2 -2
  110. package/dist/{chunk-CXWFUJR2.js → chunk-NSB3WSYS.js} +125 -6
  111. package/dist/chunk-NSB3WSYS.js.map +1 -0
  112. package/dist/{chunk-KL4CP4SB.js → chunk-O5ETUNBT.js} +17 -5
  113. package/dist/chunk-O5ETUNBT.js.map +1 -0
  114. package/dist/{chunk-OOSWAUYB.js → chunk-ODWDQNRE.js} +2 -2
  115. package/dist/{chunk-ISY75RLM.js → chunk-OJFGVJS6.js} +288 -7
  116. package/dist/chunk-OJFGVJS6.js.map +1 -0
  117. package/dist/{chunk-HLBYLYRD.js → chunk-PAORGQRI.js} +70 -13
  118. package/dist/chunk-PAORGQRI.js.map +1 -0
  119. package/dist/{chunk-ZJLY4QSU.js → chunk-PMB3WGDL.js} +69 -6
  120. package/dist/chunk-PMB3WGDL.js.map +1 -0
  121. package/dist/{chunk-J3BT33K7.js → chunk-POBPGDWI.js} +5 -5
  122. package/dist/{chunk-QWUUMMIK.js → chunk-POMSFKTB.js} +1351 -76
  123. package/dist/chunk-POMSFKTB.js.map +1 -0
  124. package/dist/{chunk-OTAVQCSF.js → chunk-PYXS46O7.js} +2 -2
  125. package/dist/chunk-QDW3E4RD.js +108 -0
  126. package/dist/chunk-QDW3E4RD.js.map +1 -0
  127. package/dist/{chunk-YNCQ7E4M.js → chunk-QDYXG4CS.js} +4 -3
  128. package/dist/chunk-QDYXG4CS.js.map +1 -0
  129. package/dist/{chunk-XUHI52HK.js → chunk-QKAH5B6E.js} +4 -4
  130. package/dist/{chunk-HLXVTBF3.js → chunk-QNJMBKFK.js} +3 -2
  131. package/dist/chunk-QNJMBKFK.js.map +1 -0
  132. package/dist/chunk-RCICHSHL.js +789 -0
  133. package/dist/chunk-RCICHSHL.js.map +1 -0
  134. package/dist/{chunk-HG2NKWR2.js → chunk-S4LX5EBI.js} +2 -2
  135. package/dist/{chunk-4A24LIM2.js → chunk-S75M5ZRK.js} +2 -2
  136. package/dist/{chunk-QCCCQT3O.js → chunk-TBBDFYXW.js} +2 -2
  137. package/dist/chunk-TBBDFYXW.js.map +1 -0
  138. package/dist/{chunk-U4PV25RD.js → chunk-U2IQTSBY.js} +1 -1
  139. package/dist/chunk-U2IQTSBY.js.map +1 -0
  140. package/dist/chunk-U66YHYC7.js +31 -0
  141. package/dist/chunk-U66YHYC7.js.map +1 -0
  142. package/dist/{chunk-MWGVGUIS.js → chunk-UEYA6UC7.js} +36 -4
  143. package/dist/chunk-UEYA6UC7.js.map +1 -0
  144. package/dist/{chunk-MDDAA2AO.js → chunk-UPMD5XND.js} +2 -2
  145. package/dist/{chunk-M5KEYE5E.js → chunk-URB2WSKZ.js} +2 -2
  146. package/dist/chunk-UVJFDP7P.js +202 -0
  147. package/dist/chunk-UVJFDP7P.js.map +1 -0
  148. package/dist/{chunk-QY2BHY5O.js → chunk-V7XCAHIB.js} +265 -25
  149. package/dist/chunk-V7XCAHIB.js.map +1 -0
  150. package/dist/chunk-W6SL7OFG.js +180 -0
  151. package/dist/chunk-W6SL7OFG.js.map +1 -0
  152. package/dist/{chunk-QDOSNLB4.js → chunk-X4WESCKA.js} +17 -15
  153. package/dist/chunk-X4WESCKA.js.map +1 -0
  154. package/dist/{chunk-OTFNI3OO.js → chunk-XMGSSBFX.js} +1738 -383
  155. package/dist/chunk-XMGSSBFX.js.map +1 -0
  156. package/dist/chunk-YDBIWGNI.js +298 -0
  157. package/dist/chunk-YDBIWGNI.js.map +1 -0
  158. package/dist/chunk-YFYL2SIJ.js +7857 -0
  159. package/dist/chunk-YFYL2SIJ.js.map +1 -0
  160. package/dist/chunking.js +1 -1
  161. package/dist/citations.d.ts +67 -0
  162. package/dist/citations.js +13 -0
  163. package/dist/citations.js.map +1 -0
  164. package/dist/cli-DwIBnp2g.d.ts +1240 -0
  165. package/dist/cli.d.ts +31 -1147
  166. package/dist/cli.js +149 -7092
  167. package/dist/cli.js.map +1 -1
  168. package/dist/codex-materialize-CQlLTzke.d.ts +139 -0
  169. package/dist/codex-thread-key.d.ts +3 -0
  170. package/dist/codex-thread-key.js +7 -0
  171. package/dist/codex-thread-key.js.map +1 -0
  172. package/dist/config.js +3 -2
  173. package/dist/connectors/codex/instructions.md +160 -0
  174. package/dist/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  175. package/dist/day-summary.d.ts +7 -2
  176. package/dist/day-summary.js +5 -2
  177. package/dist/embedding-fallback.d.ts +96 -2
  178. package/dist/embedding-fallback.js +6 -4
  179. package/dist/{engine-2A6J4XEX.js → engine-X7X3AAG3.js} +10 -7
  180. package/dist/engine-X7X3AAG3.js.map +1 -0
  181. package/dist/entity-retrieval.d.ts +3 -2
  182. package/dist/entity-retrieval.js +10 -7
  183. package/dist/entity-schema.d.ts +11 -0
  184. package/dist/entity-schema.js +19 -0
  185. package/dist/entity-schema.js.map +1 -0
  186. package/dist/explicit-capture.d.ts +6 -3
  187. package/dist/explicit-capture.js +2 -2
  188. package/dist/extraction-judge.d.ts +66 -0
  189. package/dist/extraction-judge.js +18 -0
  190. package/dist/extraction-judge.js.map +1 -0
  191. package/dist/extraction.d.ts +1 -0
  192. package/dist/extraction.js +12 -10
  193. package/dist/fallback-llm.js +4 -4
  194. package/dist/graph.js +1 -1
  195. package/dist/importance.d.ts +11 -1
  196. package/dist/importance.js +3 -1
  197. package/dist/index.d.ts +1140 -8
  198. package/dist/index.js +3350 -333
  199. package/dist/index.js.map +1 -1
  200. package/dist/intent.d.ts +2 -1
  201. package/dist/intent.js +3 -1
  202. package/dist/lifecycle.js +1 -1
  203. package/dist/local-llm.js +2 -2
  204. package/dist/logger.d.ts +1 -1
  205. package/dist/logger.js +1 -1
  206. package/dist/memory-cache.d.ts +2 -2
  207. package/dist/memory-cache.js +1 -1
  208. package/dist/{memory-projection-store-NxMkbocT.d.ts → memory-projection-store-DeSXPh1j.d.ts} +1 -1
  209. package/dist/memory-projection-store.d.ts +1 -1
  210. package/dist/model-registry.js +2 -2
  211. package/dist/models-json.js +2 -2
  212. package/dist/native-knowledge.js +2 -2
  213. package/dist/negative.js +2 -2
  214. package/dist/operator-toolkit.js +20 -16
  215. package/dist/{orchestrator-CIvLFHx3.d.ts → orchestrator-B9kwlCep.d.ts} +254 -9
  216. package/dist/orchestrator.d.ts +6 -3
  217. package/dist/orchestrator.js +70 -58
  218. package/dist/page-versioning.d.ts +77 -0
  219. package/dist/page-versioning.js +15 -0
  220. package/dist/page-versioning.js.map +1 -0
  221. package/dist/plugin-id.d.ts +37 -0
  222. package/dist/plugin-id.js +11 -0
  223. package/dist/plugin-id.js.map +1 -0
  224. package/dist/policy-runtime.js +2 -2
  225. package/dist/profiling.js +2 -2
  226. package/dist/qmd.d.ts +5 -2
  227. package/dist/qmd.js +3 -3
  228. package/dist/recall-audit.d.ts +20 -0
  229. package/dist/recall-audit.js +50 -0
  230. package/dist/recall-audit.js.map +1 -0
  231. package/dist/recall-mmr.d.ts +152 -0
  232. package/dist/recall-mmr.js +17 -0
  233. package/dist/recall-mmr.js.map +1 -0
  234. package/dist/recall-qos.js +2 -2
  235. package/dist/recall-state.js +2 -2
  236. package/dist/relevance.js +2 -2
  237. package/dist/resolve-provider-secret.js +2 -2
  238. package/dist/resume-bundles.js +5 -4
  239. package/dist/retrieval-agents.js +2 -2
  240. package/dist/retrieval.js +2 -2
  241. package/dist/schemas.d.ts +422 -64
  242. package/dist/schemas.js +3 -1
  243. package/dist/sdk-compat.d.ts +2 -0
  244. package/dist/sdk-compat.js +6 -3
  245. package/dist/sdk-compat.js.map +1 -1
  246. package/dist/semantic-chunking.d.ts +87 -0
  247. package/dist/semantic-chunking.js +20 -0
  248. package/dist/semantic-chunking.js.map +1 -0
  249. package/dist/semantic-consolidation-DrvSYRdB.d.ts +119 -0
  250. package/dist/semantic-consolidation.d.ts +4 -42
  251. package/dist/semantic-consolidation.js +23 -2
  252. package/dist/semantic-rule-promotion.js +9 -6
  253. package/dist/semantic-rule-verifier.js +10 -7
  254. package/dist/session-observer-state.js +2 -2
  255. package/dist/session-toggles.d.ts +22 -0
  256. package/dist/session-toggles.js +116 -0
  257. package/dist/session-toggles.js.map +1 -0
  258. package/dist/skills-registry.d.ts +47 -0
  259. package/dist/skills-registry.js +48 -0
  260. package/dist/skills-registry.js.map +1 -0
  261. package/dist/source-attribution.d.ts +169 -0
  262. package/dist/source-attribution.js +27 -0
  263. package/dist/source-attribution.js.map +1 -0
  264. package/dist/storage.d.ts +171 -10
  265. package/dist/storage.js +16 -5
  266. package/dist/summarizer.js +7 -7
  267. package/dist/temporal-supersession.d.ts +127 -0
  268. package/dist/temporal-supersession.js +20 -0
  269. package/dist/temporal-supersession.js.map +1 -0
  270. package/dist/threading.js +2 -2
  271. package/dist/tier-migration.d.ts +2 -1
  272. package/dist/tier-routing.js +2 -2
  273. package/dist/tokens.d.ts +21 -1
  274. package/dist/tokens.js +5 -1
  275. package/dist/transcript.js +2 -2
  276. package/dist/types.d.ts +497 -3
  277. package/dist/types.js +1 -1
  278. package/dist/utility-learner.js +2 -2
  279. package/dist/utility-runtime.js +3 -3
  280. package/dist/verified-recall.js +11 -8
  281. package/dist/whitespace.d.ts +4 -0
  282. package/dist/whitespace.js +9 -0
  283. package/dist/whitespace.js.map +1 -0
  284. package/package.json +14 -8
  285. package/dist/chunk-2CJCWDMR.js +0 -87
  286. package/dist/chunk-2CJCWDMR.js.map +0 -1
  287. package/dist/chunk-2PO5ZRKV.js.map +0 -1
  288. package/dist/chunk-6UJQNRIO.js.map +0 -1
  289. package/dist/chunk-B7LOFDVE.js.map +0 -1
  290. package/dist/chunk-BDFZXRSO.js.map +0 -1
  291. package/dist/chunk-CXWFUJR2.js.map +0 -1
  292. package/dist/chunk-DORBM6OB.js +0 -81
  293. package/dist/chunk-DORBM6OB.js.map +0 -1
  294. package/dist/chunk-ESSMF2FR.js.map +0 -1
  295. package/dist/chunk-HLBYLYRD.js.map +0 -1
  296. package/dist/chunk-HLXVTBF3.js.map +0 -1
  297. package/dist/chunk-ISY75RLM.js.map +0 -1
  298. package/dist/chunk-KL4CP4SB.js.map +0 -1
  299. package/dist/chunk-KWBU5S5U.js.map +0 -1
  300. package/dist/chunk-MWGVGUIS.js.map +0 -1
  301. package/dist/chunk-ORZMT74A.js.map +0 -1
  302. package/dist/chunk-OTFNI3OO.js.map +0 -1
  303. package/dist/chunk-PGK3VUHN.js.map +0 -1
  304. package/dist/chunk-QCCCQT3O.js.map +0 -1
  305. package/dist/chunk-QDOSNLB4.js.map +0 -1
  306. package/dist/chunk-QPKFPHOO.js +0 -178
  307. package/dist/chunk-QPKFPHOO.js.map +0 -1
  308. package/dist/chunk-QWUUMMIK.js.map +0 -1
  309. package/dist/chunk-QY2BHY5O.js.map +0 -1
  310. package/dist/chunk-TVVVQQAK.js.map +0 -1
  311. package/dist/chunk-U4PV25RD.js.map +0 -1
  312. package/dist/chunk-UYSKNO6E.js.map +0 -1
  313. package/dist/chunk-V4YC4LUK.js.map +0 -1
  314. package/dist/chunk-WWIQTB2Y.js.map +0 -1
  315. package/dist/chunk-YNCQ7E4M.js.map +0 -1
  316. package/dist/chunk-ZJLY4QSU.js.map +0 -1
  317. /package/dist/{engine-2A6J4XEX.js.map → active-memory-bridge.js.map} +0 -0
  318. /package/dist/{chunk-NTTLPF7F.js.map → chunk-3QFQGRHO.js.map} +0 -0
  319. /package/dist/{chunk-G3AG3KZN.js.map → chunk-5IZL4DCV.js.map} +0 -0
  320. /package/dist/{chunk-BRK4ODMI.js.map → chunk-5NPGSAVB.js.map} +0 -0
  321. /package/dist/{chunk-L5RPWGFK.js.map → chunk-7DHTMOND.js.map} +0 -0
  322. /package/dist/{chunk-Q6FETXJA.js.map → chunk-7SEAZFFB.js.map} +0 -0
  323. /package/dist/{chunk-QANCTXQF.js.map → chunk-AYPYCLR7.js.map} +0 -0
  324. /package/dist/{chunk-LP47L3ZX.js.map → chunk-BTY5RRRF.js.map} +0 -0
  325. /package/dist/{chunk-SCHEKPYH.js.map → chunk-C2EFFULQ.js.map} +0 -0
  326. /package/dist/{chunk-GJR6D6KC.js.map → chunk-D654IBA6.js.map} +0 -0
  327. /package/dist/{chunk-UV2FO7J4.js.map → chunk-E6K4NIEU.js.map} +0 -0
  328. /package/dist/{chunk-T4WRIV2C.js.map → chunk-EABGC2TL.js.map} +0 -0
  329. /package/dist/{chunk-ONRU4L2N.js.map → chunk-FEMOX5AD.js.map} +0 -0
  330. /package/dist/{chunk-IFFFR3MR.js.map → chunk-FSFEQI74.js.map} +0 -0
  331. /package/dist/{chunk-UIYZ5T3I.js.map → chunk-GJQPH5G3.js.map} +0 -0
  332. /package/dist/{chunk-ZKYI7UVO.js.map → chunk-JR4ZC3G4.js.map} +0 -0
  333. /package/dist/{chunk-UCYSTFZR.js.map → chunk-JRNQ3RNA.js.map} +0 -0
  334. /package/dist/{chunk-GPGBSNKM.js.map → chunk-K4FLSOR5.js.map} +0 -0
  335. /package/dist/{chunk-M5ZBBBJI.js.map → chunk-KEG4GNGI.js.map} +0 -0
  336. /package/dist/{chunk-L7WO3MZ4.js.map → chunk-KWP7T3DP.js.map} +0 -0
  337. /package/dist/{chunk-J47FNDR7.js.map → chunk-MYQWXITD.js.map} +0 -0
  338. /package/dist/{chunk-YNI4S5WT.js.map → chunk-N53K2EXC.js.map} +0 -0
  339. /package/dist/{chunk-763GUIOU.js.map → chunk-NBNN5GOB.js.map} +0 -0
  340. /package/dist/{chunk-OOSWAUYB.js.map → chunk-ODWDQNRE.js.map} +0 -0
  341. /package/dist/{chunk-J3BT33K7.js.map → chunk-POBPGDWI.js.map} +0 -0
  342. /package/dist/{chunk-OTAVQCSF.js.map → chunk-PYXS46O7.js.map} +0 -0
  343. /package/dist/{chunk-XUHI52HK.js.map → chunk-QKAH5B6E.js.map} +0 -0
  344. /package/dist/{chunk-HG2NKWR2.js.map → chunk-S4LX5EBI.js.map} +0 -0
  345. /package/dist/{chunk-4A24LIM2.js.map → chunk-S75M5ZRK.js.map} +0 -0
  346. /package/dist/{chunk-MDDAA2AO.js.map → chunk-UPMD5XND.js.map} +0 -0
  347. /package/dist/{chunk-M5KEYE5E.js.map → chunk-URB2WSKZ.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import {
2
+ buildTokenEntry,
3
+ commitTokenEntry,
2
4
  generateToken,
3
5
  getAllValidTokens,
4
6
  getAllValidTokensCached,
@@ -7,105 +9,180 @@ import {
7
9
  resolveConnectorFromToken,
8
10
  revokeToken,
9
11
  saveTokenStore
10
- } from "./chunk-KL4CP4SB.js";
12
+ } from "./chunk-O5ETUNBT.js";
13
+ import {
14
+ clearBulkImportSources,
15
+ clearTrainingExportAdapters,
16
+ convertMemoriesToRecords,
17
+ formatBatchTranscript,
18
+ getBulkImportSource,
19
+ getTrainingExportAdapter,
20
+ isImportRole,
21
+ listBulkImportSources,
22
+ listTrainingExportAdapters,
23
+ parseIsoTimestamp,
24
+ parseStrictCliDate,
25
+ registerBulkImportSource,
26
+ registerTrainingExportAdapter,
27
+ runBulkImportCliCommand,
28
+ runBulkImportPipeline,
29
+ validateImportTurn
30
+ } from "./chunk-YFYL2SIJ.js";
31
+ import "./chunk-KWP7T3DP.js";
32
+ import "./chunk-AYPYCLR7.js";
33
+ import "./chunk-X4WESCKA.js";
34
+ import "./chunk-HL4DB7TO.js";
35
+ import "./chunk-ZPKBYX2F.js";
36
+ import "./chunk-3SLRNYNG.js";
37
+ import "./chunk-LIRZNNUP.js";
38
+ import "./chunk-Y4Z4I6WK.js";
11
39
  import {
12
40
  Orchestrator,
41
+ buildProcedureRecallSection,
42
+ decideSemanticDedup,
13
43
  defaultWorkspaceDir,
14
44
  migrateFromEngram,
15
45
  rollbackFromEngramMigration,
16
46
  sanitizeSessionKeyForFilename
17
- } from "./chunk-OTFNI3OO.js";
18
- import "./chunk-UYSKNO6E.js";
19
- import "./chunk-IFFFR3MR.js";
47
+ } from "./chunk-XMGSSBFX.js";
48
+ import "./chunk-JROGC36Y.js";
49
+ import "./chunk-FSFEQI74.js";
50
+ import "./chunk-W6SL7OFG.js";
20
51
  import "./chunk-Z5AAYHUC.js";
21
- import "./chunk-4A24LIM2.js";
52
+ import "./chunk-S75M5ZRK.js";
22
53
  import "./chunk-TPB3I2AC.js";
23
54
  import "./chunk-UHGBNIOS.js";
24
- import "./chunk-ZKYI7UVO.js";
25
- import "./chunk-LP47L3ZX.js";
55
+ import "./chunk-BTY5RRRF.js";
26
56
  import "./chunk-ETOW6ACV.js";
27
- import "./chunk-GPGBSNKM.js";
57
+ import "./chunk-KVE7R4CG.js";
58
+ import "./chunk-JR4ZC3G4.js";
59
+ import "./chunk-C7VW7C3F.js";
60
+ import "./chunk-K4FLSOR5.js";
28
61
  import "./chunk-V3RXWQIE.js";
29
- import "./chunk-G3AG3KZN.js";
30
- import "./chunk-2CJCWDMR.js";
31
- import "./chunk-HG2NKWR2.js";
62
+ import "./chunk-5IZL4DCV.js";
63
+ import "./chunk-YDBIWGNI.js";
64
+ import "./chunk-7DHTMOND.js";
65
+ import "./chunk-S4LX5EBI.js";
32
66
  import "./chunk-X7XN6YU4.js";
33
- import "./chunk-BRK4ODMI.js";
34
- import "./chunk-C7VW7C3F.js";
67
+ import "./chunk-5NPGSAVB.js";
35
68
  import "./chunk-YCN4BVDK.js";
36
- import "./chunk-L5RPWGFK.js";
37
- import "./chunk-GJR6D6KC.js";
69
+ import "./chunk-D654IBA6.js";
38
70
  import "./chunk-H63EDPFJ.js";
39
- import "./chunk-WWIQTB2Y.js";
40
71
  import {
41
- ExtractionEngine
42
- } from "./chunk-6UJQNRIO.js";
43
- import "./chunk-MWGVGUIS.js";
44
- import "./chunk-763GUIOU.js";
45
- import "./chunk-ONRU4L2N.js";
46
- import "./chunk-MDDAA2AO.js";
72
+ hasBroadGraphIntent,
73
+ inferIntentFromText,
74
+ intentCompatibilityScore,
75
+ isTaskInitiationIntent,
76
+ planRecallMode
77
+ } from "./chunk-BKQJBXXX.js";
47
78
  import "./chunk-YAZNBMNF.js";
48
- import {
49
- loadDaySummaryPrompt
50
- } from "./chunk-2PO5ZRKV.js";
51
- import "./chunk-VEWZZM3H.js";
52
- import "./chunk-QPKFPHOO.js";
79
+ import "./chunk-ALXMCZEU.js";
53
80
  import {
54
81
  buildEntityRecallSection
55
- } from "./chunk-V4YC4LUK.js";
56
- import "./chunk-HLBYLYRD.js";
57
- import "./chunk-OTAVQCSF.js";
82
+ } from "./chunk-74JR4N5J.js";
83
+ import {
84
+ clearVerdictCache,
85
+ createVerdictCache,
86
+ judgeFactDurability,
87
+ verdictCacheSize
88
+ } from "./chunk-LAYN4LDC.js";
89
+ import {
90
+ ExtractionEngine
91
+ } from "./chunk-2VFW5K5U.js";
92
+ import "./chunk-UEYA6UC7.js";
93
+ import "./chunk-NBNN5GOB.js";
94
+ import "./chunk-UPMD5XND.js";
95
+ import "./chunk-FEMOX5AD.js";
96
+ import "./chunk-VEWZZM3H.js";
97
+ import "./chunk-PAORGQRI.js";
98
+ import "./chunk-PYXS46O7.js";
58
99
  import "./chunk-3QKK7QOS.js";
59
- import "./chunk-UIYZ5T3I.js";
60
- import "./chunk-UCYSTFZR.js";
61
- import "./chunk-J47FNDR7.js";
100
+ import "./chunk-GJQPH5G3.js";
101
+ import "./chunk-JRNQ3RNA.js";
102
+ import "./chunk-MYQWXITD.js";
62
103
  import "./chunk-CULXMQJH.js";
63
- import "./chunk-UV2FO7J4.js";
64
- import "./chunk-T4WRIV2C.js";
104
+ import "./chunk-E6K4NIEU.js";
105
+ import "./chunk-EABGC2TL.js";
65
106
  import "./chunk-LOBRX7VD.js";
66
107
  import {
67
108
  LanceDbBackend,
68
109
  MeilisearchBackend,
69
110
  OramaBackend
70
- } from "./chunk-IZME7KW2.js";
111
+ } from "./chunk-HITJFT7E.js";
71
112
  import "./chunk-YRMVARQP.js";
113
+ import {
114
+ LEGACY_PLUGIN_ID,
115
+ PLUGIN_ID,
116
+ resolveRemnicPluginEntry
117
+ } from "./chunk-U66YHYC7.js";
72
118
  import {
73
119
  QmdClient
74
- } from "./chunk-TVVVQQAK.js";
75
- import "./chunk-BDFZXRSO.js";
76
- import "./chunk-LK6SGL53.js";
120
+ } from "./chunk-7PA4OZEU.js";
121
+ import "./chunk-J4IYOZZ5.js";
77
122
  import "./chunk-AAI7JARD.js";
78
- import "./chunk-C6QPK5GG.js";
79
- import "./chunk-Q6FETXJA.js";
123
+ import "./chunk-7SEAZFFB.js";
80
124
  import "./chunk-K6WK37A6.js";
81
- import "./chunk-SCHEKPYH.js";
82
- import "./chunk-ORZMT74A.js";
83
- import "./chunk-B7LOFDVE.js";
125
+ import "./chunk-LK6SGL53.js";
126
+ import {
127
+ CODEX_THREAD_KEY_PREFIX
128
+ } from "./chunk-3PG3H5TD.js";
84
129
  import "./chunk-FYIYMQ5N.js";
85
130
  import "./chunk-2NMMFZ5T.js";
86
131
  import {
132
+ coerceInstallExtension,
87
133
  parseConfig
88
- } from "./chunk-ISY75RLM.js";
134
+ } from "./chunk-OJFGVJS6.js";
89
135
  import "./chunk-Z5LAYHGJ.js";
136
+ import "./chunk-C6QPK5GG.js";
137
+ import {
138
+ buildExtensionsFooterForSummary,
139
+ loadDaySummaryPrompt
140
+ } from "./chunk-GZCUW5IC.js";
141
+ import {
142
+ MATERIALIZE_VERSION,
143
+ SENTINEL_FILE,
144
+ buildExtensionsBlockForConsolidation,
145
+ describeMemoriesDir,
146
+ ensureSentinel,
147
+ materializeForNamespace,
148
+ runCodexMaterialize,
149
+ runPostConsolidationMaterialize
150
+ } from "./chunk-RCICHSHL.js";
151
+ import {
152
+ REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT,
153
+ discoverMemoryExtensions,
154
+ renderExtensionsBlock,
155
+ renderExtensionsFooter,
156
+ resolveExtensionsRoot
157
+ } from "./chunk-EJI5XIBB.js";
158
+ import "./chunk-C2EFFULQ.js";
159
+ import "./chunk-4WMCPJWX.js";
160
+ import "./chunk-6HZ6AO2P.js";
90
161
  import "./chunk-JWPLJLDU.js";
91
162
  import {
92
163
  BootstrapEngine
93
- } from "./chunk-YNI4S5WT.js";
94
- import "./chunk-DORBM6OB.js";
164
+ } from "./chunk-N53K2EXC.js";
165
+ import "./chunk-URB2WSKZ.js";
166
+ import "./chunk-UVJFDP7P.js";
95
167
  import "./chunk-XYIK4LF6.js";
96
- import "./chunk-XUHI52HK.js";
97
- import "./chunk-M5ZBBBJI.js";
98
- import "./chunk-OOSWAUYB.js";
168
+ import "./chunk-QKAH5B6E.js";
169
+ import "./chunk-KEG4GNGI.js";
170
+ import "./chunk-ODWDQNRE.js";
99
171
  import "./chunk-Y27UJK6V.js";
100
172
  import "./chunk-UZB5KHKX.js";
101
- import "./chunk-M5KEYE5E.js";
102
173
  import "./chunk-NGAVDO7E.js";
103
174
  import {
104
175
  EngramAccessHttpServer
105
- } from "./chunk-ZJLY4QSU.js";
176
+ } from "./chunk-PMB3WGDL.js";
106
177
  import {
107
178
  EngramMcpServer
108
- } from "./chunk-CXWFUJR2.js";
179
+ } from "./chunk-NSB3WSYS.js";
180
+ import {
181
+ buildCitationGuidance,
182
+ formatOaiMemCitation,
183
+ parseOaiMemCitation,
184
+ sanitizeNoteForCitation
185
+ } from "./chunk-IQT3XTKW.js";
109
186
  import "./chunk-MARWOCVP.js";
110
187
  import {
111
188
  formatZodError,
@@ -114,47 +191,484 @@ import {
114
191
  recallRequestSchema,
115
192
  suggestionSubmitRequestSchema,
116
193
  validateRequest
117
- } from "./chunk-PGK3VUHN.js";
194
+ } from "./chunk-MTLYEMJB.js";
118
195
  import {
119
196
  EngramAccessInputError,
120
197
  EngramAccessService
121
- } from "./chunk-QY2BHY5O.js";
122
- import "./chunk-N5AKDXAI.js";
198
+ } from "./chunk-V7XCAHIB.js";
123
199
  import {
124
200
  isTrustZoneName
125
201
  } from "./chunk-EQINRHYR.js";
126
- import "./chunk-J3BT33K7.js";
202
+ import "./chunk-POBPGDWI.js";
203
+ import "./chunk-QDYXG4CS.js";
204
+ import "./chunk-QNJMBKFK.js";
127
205
  import "./chunk-EEQLFRUM.js";
206
+ import {
207
+ buildProcedureMarkdownBody,
208
+ parseProcedureStepsFromBody
209
+ } from "./chunk-QDW3E4RD.js";
210
+ import "./chunk-4NRAJUDS.js";
211
+ import "./chunk-DT5TVLJE.js";
212
+ import "./chunk-TBBDFYXW.js";
213
+ import "./chunk-DGXUHMOV.js";
214
+ import "./chunk-LPSF4OQH.js";
215
+ import "./chunk-XKECPATV.js";
216
+ import {
217
+ BRIEFING_FORMAT_ALLOWED,
218
+ FileCalendarSource,
219
+ briefingFilename,
220
+ buildBriefing,
221
+ focusMatchesEntity,
222
+ focusMatchesMemory,
223
+ parseBriefingFocus,
224
+ parseBriefingWindow,
225
+ renderBriefingMarkdown,
226
+ resolveBriefingSaveDir,
227
+ validateBriefingFormat
228
+ } from "./chunk-ECKDIK5F.js";
128
229
  import {
129
230
  StorageManager
130
- } from "./chunk-QWUUMMIK.js";
131
- import "./chunk-U4PV25RD.js";
132
- import "./chunk-ESSMF2FR.js";
231
+ } from "./chunk-POMSFKTB.js";
232
+ import "./chunk-U2IQTSBY.js";
233
+ import {
234
+ CITATION_UNKNOWN,
235
+ DEFAULT_CITATION_FORMAT,
236
+ attachCitation,
237
+ deriveSessionId,
238
+ formatCitation,
239
+ hasCitation,
240
+ parseAllCitations,
241
+ parseCitation,
242
+ stripCitation
243
+ } from "./chunk-4KAN3GZ3.js";
244
+ import {
245
+ createVersion,
246
+ diffVersions,
247
+ getVersion,
248
+ listVersions,
249
+ revertToVersion
250
+ } from "./chunk-6ZH4TU6I.js";
251
+ import "./chunk-6PFRXT4K.js";
133
252
  import "./chunk-TP4FZJIZ.js";
134
253
  import "./chunk-SCU65EZI.js";
135
254
  import "./chunk-BOUYNNYD.js";
136
- import "./chunk-DM2T26WE.js";
137
255
  import "./chunk-QSVPYQPG.js";
138
- import "./chunk-YNCQ7E4M.js";
139
- import "./chunk-HLXVTBF3.js";
256
+ import "./chunk-DM2T26WE.js";
140
257
  import "./chunk-M62O4P4T.js";
141
- import "./chunk-DT5TVLJE.js";
258
+ import "./chunk-4DJQYKMN.js";
142
259
  import {
143
260
  initLogger,
144
261
  log
145
- } from "./chunk-KWBU5S5U.js";
146
- import "./chunk-DGXUHMOV.js";
147
- import "./chunk-LPSF4OQH.js";
148
- import "./chunk-XKECPATV.js";
149
- import "./chunk-6HZ6AO2P.js";
150
- import "./chunk-QCCCQT3O.js";
262
+ } from "./chunk-2ODBA7MQ.js";
263
+ import {
264
+ getMemoryForActiveMemory,
265
+ recallForActiveMemory
266
+ } from "./chunk-G4SK7DSQ.js";
267
+ import "./chunk-6MKAMLQL.js";
268
+ import {
269
+ resolvePrincipal
270
+ } from "./chunk-N5AKDXAI.js";
151
271
 
152
- // src/projection/index.ts
272
+ // src/binary-lifecycle/types.ts
273
+ var DEFAULT_SCAN_PATTERNS = [
274
+ "*.png",
275
+ "*.jpg",
276
+ "*.jpeg",
277
+ "*.gif",
278
+ "*.pdf",
279
+ "*.mp3",
280
+ "*.mp4",
281
+ "*.wav"
282
+ ];
283
+ var DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024;
284
+ var DEFAULT_GRACE_PERIOD_DAYS = 7;
285
+
286
+ // src/binary-lifecycle/backend.ts
153
287
  import fs from "fs";
288
+ import fsp from "fs/promises";
289
+ import path from "path";
290
+ var FilesystemBackend = class {
291
+ type = "filesystem";
292
+ basePath;
293
+ constructor(basePath) {
294
+ if (!basePath || basePath.trim().length === 0) {
295
+ throw new Error("FilesystemBackend requires a non-empty basePath");
296
+ }
297
+ this.basePath = basePath;
298
+ }
299
+ async upload(localPath, remotePath) {
300
+ const dest = path.join(this.basePath, remotePath);
301
+ const destDir = path.dirname(dest);
302
+ await fsp.mkdir(destDir, { recursive: true });
303
+ await fsp.copyFile(localPath, dest);
304
+ return dest;
305
+ }
306
+ async exists(remotePath) {
307
+ const dest = path.join(this.basePath, remotePath);
308
+ try {
309
+ await fsp.access(dest, fs.constants.F_OK);
310
+ return true;
311
+ } catch {
312
+ return false;
313
+ }
314
+ }
315
+ async delete(remotePath) {
316
+ const dest = path.join(this.basePath, remotePath);
317
+ try {
318
+ await fsp.unlink(dest);
319
+ } catch (err) {
320
+ if (err.code !== "ENOENT") throw err;
321
+ }
322
+ }
323
+ };
324
+ var NoneBackend = class {
325
+ type = "none";
326
+ async upload(_localPath, remotePath) {
327
+ return remotePath;
328
+ }
329
+ async exists(_remotePath) {
330
+ return false;
331
+ }
332
+ async delete(_remotePath) {
333
+ }
334
+ };
335
+ function createBackend(cfg) {
336
+ switch (cfg.type) {
337
+ case "filesystem": {
338
+ if (!cfg.basePath) {
339
+ throw new Error(
340
+ 'BinaryStorageBackendConfig.basePath is required when type is "filesystem"'
341
+ );
342
+ }
343
+ return new FilesystemBackend(cfg.basePath);
344
+ }
345
+ case "s3":
346
+ throw new Error("S3 binary storage backend is not yet implemented");
347
+ case "none":
348
+ return new NoneBackend();
349
+ default:
350
+ throw new Error(`Unknown binary storage backend type: ${String(cfg.type)}`);
351
+ }
352
+ }
353
+
354
+ // src/binary-lifecycle/scanner.ts
355
+ import fsp2 from "fs/promises";
154
356
  import path2 from "path";
357
+ function matchesPatterns(filename, patterns) {
358
+ const lower = filename.toLowerCase();
359
+ for (const pattern of patterns) {
360
+ if (pattern.startsWith("*.")) {
361
+ const ext = pattern.slice(1).toLowerCase();
362
+ if (lower.endsWith(ext)) return true;
363
+ } else if (lower === pattern.toLowerCase()) {
364
+ return true;
365
+ }
366
+ }
367
+ return false;
368
+ }
369
+ async function scanForBinaries(memoryDir, config, manifest) {
370
+ const tracked = new Set(manifest.assets.map((a) => a.originalPath));
371
+ const results = [];
372
+ async function walk(dir) {
373
+ let entries;
374
+ try {
375
+ entries = await fsp2.readdir(dir, { withFileTypes: true });
376
+ } catch {
377
+ return;
378
+ }
379
+ for (const entry of entries) {
380
+ const fullPath = path2.join(dir, entry.name);
381
+ const relativePath = path2.relative(memoryDir, fullPath).split(path2.sep).join("/");
382
+ if (entry.isDirectory()) {
383
+ if (entry.name === ".binary-lifecycle") continue;
384
+ await walk(fullPath);
385
+ continue;
386
+ }
387
+ if (!entry.isFile()) continue;
388
+ if (!matchesPatterns(entry.name, config.scanPatterns)) continue;
389
+ if (tracked.has(relativePath)) continue;
390
+ try {
391
+ const stat = await fsp2.stat(fullPath);
392
+ if (stat.size > config.maxBinarySizeBytes) continue;
393
+ if (stat.size === 0) continue;
394
+ } catch {
395
+ continue;
396
+ }
397
+ results.push(relativePath);
398
+ }
399
+ }
400
+ await walk(memoryDir);
401
+ return results;
402
+ }
403
+
404
+ // src/binary-lifecycle/manifest.ts
405
+ import fsp3 from "fs/promises";
406
+ import path3 from "path";
407
+ import crypto from "crypto";
408
+ var MANIFEST_DIR = ".binary-lifecycle";
409
+ var MANIFEST_FILE = "manifest.json";
410
+ function manifestDir(memoryDir) {
411
+ return path3.join(memoryDir, MANIFEST_DIR);
412
+ }
413
+ function manifestPath(memoryDir) {
414
+ return path3.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);
415
+ }
416
+ async function readManifest(memoryDir) {
417
+ const filePath = manifestPath(memoryDir);
418
+ try {
419
+ const raw = await fsp3.readFile(filePath, "utf-8");
420
+ const parsed = JSON.parse(raw);
421
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
422
+ return emptyManifest();
423
+ }
424
+ const obj = parsed;
425
+ if (obj.version !== 1 || !Array.isArray(obj.assets)) {
426
+ return emptyManifest();
427
+ }
428
+ return parsed;
429
+ } catch {
430
+ return emptyManifest();
431
+ }
432
+ }
433
+ async function writeManifest(memoryDir, manifest) {
434
+ const dir = manifestDir(memoryDir);
435
+ await fsp3.mkdir(dir, { recursive: true });
436
+ const dest = manifestPath(memoryDir);
437
+ const tmpSuffix = crypto.randomBytes(8).toString("hex");
438
+ const tmpPath = `${dest}.${tmpSuffix}.tmp`;
439
+ const content = JSON.stringify(manifest, null, 2) + "\n";
440
+ await fsp3.writeFile(tmpPath, content, "utf-8");
441
+ try {
442
+ await fsp3.rename(tmpPath, dest);
443
+ } catch (renameErr) {
444
+ try {
445
+ await fsp3.unlink(tmpPath);
446
+ } catch {
447
+ }
448
+ throw renameErr;
449
+ }
450
+ }
451
+ function emptyManifest() {
452
+ return { version: 1, assets: [] };
453
+ }
454
+
455
+ // src/binary-lifecycle/pipeline.ts
456
+ import fsp4 from "fs/promises";
457
+ import path4 from "path";
458
+ import crypto2 from "crypto";
459
+ async function hashFile(filePath) {
460
+ const content = await fsp4.readFile(filePath);
461
+ return crypto2.createHash("sha256").update(content).digest("hex");
462
+ }
463
+ function guessMimeType(ext) {
464
+ const map = {
465
+ ".png": "image/png",
466
+ ".jpg": "image/jpeg",
467
+ ".jpeg": "image/jpeg",
468
+ ".gif": "image/gif",
469
+ ".pdf": "application/pdf",
470
+ ".mp3": "audio/mpeg",
471
+ ".mp4": "video/mp4",
472
+ ".wav": "audio/wav"
473
+ };
474
+ return map[ext.toLowerCase()] ?? "application/octet-stream";
475
+ }
476
+ function escapeRegex(s) {
477
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
478
+ }
479
+ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
480
+ let mirrored = 0;
481
+ const errors = [];
482
+ for (const relPath of newPaths) {
483
+ const fullPath = path4.join(memoryDir, relPath);
484
+ try {
485
+ const stat = await fsp4.stat(fullPath);
486
+ const contentHash = await hashFile(fullPath);
487
+ const ext = path4.extname(relPath);
488
+ const mimeType = guessMimeType(ext);
489
+ const remotePath = relPath;
490
+ let backendLocation = remotePath;
491
+ if (!dryRun) {
492
+ backendLocation = await backend.upload(fullPath, remotePath);
493
+ }
494
+ const record = {
495
+ originalPath: relPath,
496
+ mirroredPath: backendLocation,
497
+ contentHash,
498
+ sizeBytes: stat.size,
499
+ mimeType,
500
+ mirroredAt: (/* @__PURE__ */ new Date()).toISOString(),
501
+ status: "mirrored"
502
+ };
503
+ assets.push(record);
504
+ mirrored++;
505
+ log2.info(`[binary-lifecycle] mirrored: ${relPath} (${stat.size} bytes)${dryRun ? " [dry-run]" : ""}`);
506
+ } catch (err) {
507
+ const msg = `mirror failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}`;
508
+ log2.error(`[binary-lifecycle] ${msg}`);
509
+ errors.push(msg);
510
+ }
511
+ }
512
+ return { mirrored, errors };
513
+ }
514
+ async function stageRedirect(memoryDir, assets, log2, dryRun) {
515
+ let redirected = 0;
516
+ const errors = [];
517
+ const candidates = assets.filter((a) => a.status === "mirrored");
518
+ if (candidates.length === 0) return { redirected, errors };
519
+ const mdFiles = await findMarkdownFiles(memoryDir);
520
+ for (const asset of candidates) {
521
+ let matchCount = 0;
522
+ let writeFailCount = 0;
523
+ for (const mdPath of mdFiles) {
524
+ try {
525
+ const content = await fsp4.readFile(mdPath, "utf-8");
526
+ const mdDir = path4.dirname(mdPath);
527
+ const assetAbsolute = path4.join(memoryDir, asset.originalPath);
528
+ const relativeToMd = path4.relative(mdDir, assetAbsolute);
529
+ const relativeForward = relativeToMd.split(path4.sep).join("/");
530
+ const escaped = escapeRegex(relativeForward);
531
+ const pattern = new RegExp(
532
+ `(!?\\[[^\\]]*\\]\\()(\\.\\/)?(${escaped})(\\))`,
533
+ "g"
534
+ );
535
+ if (!pattern.test(content)) continue;
536
+ matchCount++;
537
+ if (!dryRun) {
538
+ pattern.lastIndex = 0;
539
+ const updated = content.replace(pattern, (_match, open, _dotSlash, _file, close) => {
540
+ return `${open}${asset.mirroredPath}${close}`;
541
+ });
542
+ await fsp4.writeFile(mdPath, updated, "utf-8");
543
+ }
544
+ } catch (err) {
545
+ writeFailCount++;
546
+ const msg = `redirect scan failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`;
547
+ log2.error(`[binary-lifecycle] ${msg}`);
548
+ errors.push(msg);
549
+ }
550
+ }
551
+ if (matchCount > 0 && writeFailCount === 0) {
552
+ asset.status = "redirected";
553
+ asset.redirectedAt = (/* @__PURE__ */ new Date()).toISOString();
554
+ redirected++;
555
+ log2.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
556
+ } else if (matchCount > 0 && writeFailCount > 0) {
557
+ asset.status = "error";
558
+ log2.warn(
559
+ `[binary-lifecycle] redirect partial failure for ${asset.originalPath}: ${matchCount} match(es), ${writeFailCount} write failure(s) \u2014 status set to error`
560
+ );
561
+ }
562
+ }
563
+ return { redirected, errors };
564
+ }
565
+ async function stageClean(memoryDir, assets, gracePeriodDays, log2, dryRun, forceClean) {
566
+ let cleaned = 0;
567
+ const errors = [];
568
+ const now = Date.now();
569
+ const graceMs = gracePeriodDays * 24 * 60 * 60 * 1e3;
570
+ const candidates = assets.filter(
571
+ (a) => a.status === "redirected"
572
+ );
573
+ for (const asset of candidates) {
574
+ const mirroredMs = new Date(asset.mirroredAt).getTime();
575
+ const ageMs = now - mirroredMs;
576
+ if (!forceClean && ageMs < graceMs) {
577
+ continue;
578
+ }
579
+ const fullPath = path4.join(memoryDir, asset.originalPath);
580
+ try {
581
+ if (!dryRun) {
582
+ await fsp4.unlink(fullPath);
583
+ }
584
+ asset.status = "cleaned";
585
+ asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
586
+ cleaned++;
587
+ log2.info(`[binary-lifecycle] cleaned: ${asset.originalPath}${dryRun ? " [dry-run]" : ""}`);
588
+ } catch (err) {
589
+ if (err.code === "ENOENT") {
590
+ asset.status = "cleaned";
591
+ asset.cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
592
+ cleaned++;
593
+ } else {
594
+ const msg = `clean failed for ${asset.originalPath}: ${err instanceof Error ? err.message : String(err)}`;
595
+ log2.error(`[binary-lifecycle] ${msg}`);
596
+ errors.push(msg);
597
+ }
598
+ }
599
+ }
600
+ return { cleaned, errors };
601
+ }
602
+ async function findMarkdownFiles(dir) {
603
+ const results = [];
604
+ async function walk(current) {
605
+ let entries;
606
+ try {
607
+ entries = await fsp4.readdir(current, { withFileTypes: true });
608
+ } catch {
609
+ return;
610
+ }
611
+ for (const entry of entries) {
612
+ const full = path4.join(current, entry.name);
613
+ if (entry.isDirectory()) {
614
+ if (entry.name === ".binary-lifecycle") continue;
615
+ await walk(full);
616
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
617
+ results.push(full);
618
+ }
619
+ }
620
+ }
621
+ await walk(dir);
622
+ return results;
623
+ }
624
+ async function runBinaryLifecyclePipeline(memoryDir, config, backend, log2, opts) {
625
+ const dryRun = opts?.dryRun ?? false;
626
+ const forceClean = opts?.forceClean ?? false;
627
+ const manifest = await readManifest(memoryDir);
628
+ const newPaths = await scanForBinaries(memoryDir, config, manifest);
629
+ const scanned = newPaths.length;
630
+ const mirrorResult = await stageMirror(
631
+ memoryDir,
632
+ newPaths,
633
+ backend,
634
+ manifest.assets,
635
+ log2,
636
+ dryRun
637
+ );
638
+ const redirectResult = await stageRedirect(memoryDir, manifest.assets, log2, dryRun);
639
+ const cleanResult = await stageClean(
640
+ memoryDir,
641
+ manifest.assets,
642
+ config.gracePeriodDays,
643
+ log2,
644
+ dryRun,
645
+ forceClean
646
+ );
647
+ manifest.lastScanAt = (/* @__PURE__ */ new Date()).toISOString();
648
+ if (!dryRun) {
649
+ await writeManifest(memoryDir, manifest);
650
+ }
651
+ const allErrors = [
652
+ ...mirrorResult.errors,
653
+ ...redirectResult.errors,
654
+ ...cleanResult.errors
655
+ ];
656
+ return {
657
+ scanned,
658
+ mirrored: mirrorResult.mirrored,
659
+ redirected: redirectResult.redirected,
660
+ cleaned: cleanResult.cleaned,
661
+ errors: allErrors,
662
+ dryRun
663
+ };
664
+ }
665
+
666
+ // src/projection/index.ts
667
+ import fs2 from "fs";
668
+ import path6 from "path";
155
669
 
156
670
  // src/utils/category-dir.ts
157
- import path from "path";
671
+ import path5 from "path";
158
672
  var CATEGORY_DIR_MAP = {
159
673
  correction: "corrections",
160
674
  question: "questions",
@@ -165,7 +679,8 @@ var CATEGORY_DIR_MAP = {
165
679
  principle: "principles",
166
680
  rule: "rules",
167
681
  skill: "skills",
168
- relationship: "relationships"
682
+ relationship: "relationships",
683
+ procedure: "procedures"
169
684
  };
170
685
  var ALL_CATEGORY_DIRS = [
171
686
  "facts",
@@ -177,7 +692,7 @@ var ALL_CATEGORY_KEYS = [
177
692
  ];
178
693
  function getCategoryDir(memoryDir, category) {
179
694
  const dir = CATEGORY_DIR_MAP[category];
180
- return dir ? path.join(memoryDir, dir) : path.join(memoryDir, "facts");
695
+ return dir ? path5.join(memoryDir, dir) : path5.join(memoryDir, "facts");
181
696
  }
182
697
 
183
698
  // src/projection/index.ts
@@ -194,11 +709,11 @@ async function generateContextTree(options) {
194
709
  let nodesGenerated = 0;
195
710
  let nodesSkipped = 0;
196
711
  const categoryCounts = {};
197
- fs.mkdirSync(outputDir, { recursive: true });
712
+ fs2.mkdirSync(outputDir, { recursive: true });
198
713
  const allCategories = filterCategories ?? ALL_CATEGORY_KEYS.filter((c) => c !== "question");
199
714
  for (const category of allCategories) {
200
715
  const categoryDir = getCategoryDir(memoryDir, category);
201
- if (!fs.existsSync(categoryDir)) continue;
716
+ if (!fs2.existsSync(categoryDir)) continue;
202
717
  categoryCounts[category] = 0;
203
718
  const files = walkR(categoryDir);
204
719
  let count = 0;
@@ -207,7 +722,7 @@ async function generateContextTree(options) {
207
722
  nodesSkipped++;
208
723
  continue;
209
724
  }
210
- const content = fs.readFileSync(filePath, "utf8");
725
+ const content = fs2.readFileSync(filePath, "utf8");
211
726
  const fm = parseFrontmatter(content);
212
727
  if (!fm) {
213
728
  nodesSkipped++;
@@ -218,17 +733,17 @@ async function generateContextTree(options) {
218
733
  nodesSkipped++;
219
734
  continue;
220
735
  }
221
- const outputPath = path2.join(outputDir, node.path);
222
- fs.mkdirSync(path2.dirname(outputPath), { recursive: true });
223
- fs.writeFileSync(outputPath, node.content);
736
+ const outputPath = path6.join(outputDir, node.path);
737
+ fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
738
+ fs2.writeFileSync(outputPath, node.content);
224
739
  nodesGenerated++;
225
740
  categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;
226
741
  count++;
227
742
  }
228
743
  }
229
744
  if (includeEntities) {
230
- const entitiesDir = path2.join(memoryDir, "entities");
231
- if (fs.existsSync(entitiesDir)) {
745
+ const entitiesDir = path6.join(memoryDir, "entities");
746
+ if (fs2.existsSync(entitiesDir)) {
232
747
  categoryCounts["entity"] = 0;
233
748
  const entityFiles = walkR(entitiesDir);
234
749
  let count = 0;
@@ -237,12 +752,12 @@ async function generateContextTree(options) {
237
752
  nodesSkipped++;
238
753
  continue;
239
754
  }
240
- const content = fs.readFileSync(filePath, "utf8");
241
- const fileName = path2.basename(filePath, ".md");
755
+ const content = fs2.readFileSync(filePath, "utf8");
756
+ const fileName = path6.basename(filePath, ".md");
242
757
  const node = projectEntityNode(fileName, content);
243
- const outputPath = path2.join(outputDir, "entities", `${fileName}.md`);
244
- fs.mkdirSync(path2.dirname(outputPath), { recursive: true });
245
- fs.writeFileSync(outputPath, node.content);
758
+ const outputPath = path6.join(outputDir, "entities", `${fileName}.md`);
759
+ fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
760
+ fs2.writeFileSync(outputPath, node.content);
246
761
  nodesGenerated++;
247
762
  categoryCounts["entity"] = (categoryCounts["entity"] ?? 0) + 1;
248
763
  count++;
@@ -250,8 +765,8 @@ async function generateContextTree(options) {
250
765
  }
251
766
  }
252
767
  if (includeQuestions) {
253
- const questionsDir = path2.join(memoryDir, "questions");
254
- if (fs.existsSync(questionsDir)) {
768
+ const questionsDir = path6.join(memoryDir, "questions");
769
+ if (fs2.existsSync(questionsDir)) {
255
770
  categoryCounts["question"] = 0;
256
771
  const qFiles = walkR(questionsDir);
257
772
  let count = 0;
@@ -260,7 +775,7 @@ async function generateContextTree(options) {
260
775
  nodesSkipped++;
261
776
  continue;
262
777
  }
263
- const content = fs.readFileSync(filePath, "utf8");
778
+ const content = fs2.readFileSync(filePath, "utf8");
264
779
  const fm = parseFrontmatter(content);
265
780
  if (!fm) {
266
781
  nodesSkipped++;
@@ -271,9 +786,9 @@ async function generateContextTree(options) {
271
786
  nodesSkipped++;
272
787
  continue;
273
788
  }
274
- const outputPath = path2.join(outputDir, node.path);
275
- fs.mkdirSync(path2.dirname(outputPath), { recursive: true });
276
- fs.writeFileSync(outputPath, node.content);
789
+ const outputPath = path6.join(outputDir, node.path);
790
+ fs2.mkdirSync(path6.dirname(outputPath), { recursive: true });
791
+ fs2.writeFileSync(outputPath, node.content);
277
792
  nodesGenerated++;
278
793
  categoryCounts["question"] = (categoryCounts["question"] ?? 0) + 1;
279
794
  count++;
@@ -281,7 +796,7 @@ async function generateContextTree(options) {
281
796
  }
282
797
  }
283
798
  const index = generateIndex(categoryCounts, outputDir);
284
- fs.writeFileSync(path2.join(outputDir, "INDEX.md"), index);
799
+ fs2.writeFileSync(path6.join(outputDir, "INDEX.md"), index);
285
800
  return {
286
801
  nodesGenerated,
287
802
  nodesSkipped,
@@ -293,8 +808,8 @@ async function generateContextTree(options) {
293
808
  function walkR(dir) {
294
809
  const results = [];
295
810
  function walk(directory) {
296
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
297
- const fullPath = path2.join(directory, entry.name);
811
+ for (const entry of fs2.readdirSync(directory, { withFileTypes: true })) {
812
+ const fullPath = path6.join(directory, entry.name);
298
813
  if (entry.isDirectory()) {
299
814
  walk(fullPath);
300
815
  } else if (entry.name.endsWith(".md")) {
@@ -336,13 +851,13 @@ function extractBody(content) {
336
851
  }
337
852
  function projectNode(filePath, category, fm, rawContent) {
338
853
  const body = extractBody(rawContent);
339
- const fileName = path2.basename(filePath, ".md");
340
- const dateDir = path2.basename(path2.dirname(filePath));
854
+ const fileName = path6.basename(filePath, ".md");
855
+ const dateDir = path6.basename(path6.dirname(filePath));
341
856
  let relPath;
342
857
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateDir)) {
343
- relPath = path2.join(category, dateDir, `${fileName}.md`);
858
+ relPath = path6.join(category, dateDir, `${fileName}.md`);
344
859
  } else {
345
- relPath = path2.join(category, `${fileName}.md`);
860
+ relPath = path6.join(category, `${fileName}.md`);
346
861
  }
347
862
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
348
863
  const md = `# ${fm.id}
@@ -388,7 +903,7 @@ function projectEntityNode(fileName, content) {
388
903
  ${content}
389
904
  `;
390
905
  return {
391
- path: path2.join("entities", `${fileName}.md`),
906
+ path: path6.join("entities", `${fileName}.md`),
392
907
  category: "entity",
393
908
  title: fileName,
394
909
  content: md,
@@ -440,8 +955,8 @@ function generateIndex(categoryCounts, outputDir) {
440
955
  }
441
956
 
442
957
  // src/onboarding/index.ts
443
- import fs2 from "fs";
444
- import path3 from "path";
958
+ import fs3 from "fs";
959
+ import path7 from "path";
445
960
  var LANGUAGE_RULES = [
446
961
  {
447
962
  language: "TypeScript",
@@ -567,13 +1082,13 @@ function walkDir(root, exclude, maxDepth) {
567
1082
  if (depth > maxDepth) return;
568
1083
  let entries;
569
1084
  try {
570
- entries = fs2.readdirSync(dir, { withFileTypes: true });
1085
+ entries = fs3.readdirSync(dir, { withFileTypes: true });
571
1086
  } catch {
572
1087
  return;
573
1088
  }
574
1089
  for (const entry of entries) {
575
1090
  if (exclude.has(entry.name)) continue;
576
- const fullPath = path3.join(dir, entry.name);
1091
+ const fullPath = path7.join(dir, entry.name);
577
1092
  if (entry.isDirectory()) {
578
1093
  walk(fullPath, depth + 1);
579
1094
  } else if (entry.isFile()) {
@@ -588,11 +1103,11 @@ function detectLanguages(files, root) {
588
1103
  const results = [];
589
1104
  const extCounts = /* @__PURE__ */ new Map();
590
1105
  for (const f of files) {
591
- const ext = path3.extname(f).toLowerCase();
1106
+ const ext = path7.extname(f).toLowerCase();
592
1107
  if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);
593
1108
  }
594
1109
  const rootFiles = new Set(
595
- files.filter((f) => path3.dirname(f) === root).map((f) => path3.basename(f))
1110
+ files.filter((f) => path7.dirname(f) === root).map((f) => path7.basename(f))
596
1111
  );
597
1112
  for (const rule of LANGUAGE_RULES) {
598
1113
  const evidence = [];
@@ -636,18 +1151,18 @@ function detectLanguages(files, root) {
636
1151
  }
637
1152
  function detectShape(files, root) {
638
1153
  const rootFiles = new Set(
639
- files.filter((f) => path3.dirname(f) === root).map((f) => path3.basename(f))
1154
+ files.filter((f) => path7.dirname(f) === root).map((f) => path7.basename(f))
640
1155
  );
641
1156
  const rootDirs = /* @__PURE__ */ new Set();
642
1157
  try {
643
- for (const entry of fs2.readdirSync(root, { withFileTypes: true })) {
1158
+ for (const entry of fs3.readdirSync(root, { withFileTypes: true })) {
644
1159
  if (entry.isDirectory()) rootDirs.add(entry.name);
645
1160
  }
646
1161
  } catch {
647
1162
  }
648
1163
  const evidence = [];
649
1164
  if (rootFiles.has("package.json")) {
650
- const pkg = readJsonSafe(path3.join(root, "package.json"));
1165
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
651
1166
  if (pkg?.workspaces) {
652
1167
  evidence.push("package.json has workspaces");
653
1168
  return { shape: "monorepo", evidence };
@@ -661,13 +1176,13 @@ function detectShape(files, root) {
661
1176
  evidence.push("workspace manifest found");
662
1177
  return { shape: "workspace", evidence };
663
1178
  }
664
- const cargoToml = readTomlWorkspace(path3.join(root, "Cargo.toml"));
1179
+ const cargoToml = readTomlWorkspace(path7.join(root, "Cargo.toml"));
665
1180
  if (cargoToml) {
666
1181
  evidence.push("Cargo.toml has workspace");
667
1182
  return { shape: "workspace", evidence };
668
1183
  }
669
1184
  if (rootFiles.has("package.json")) {
670
- const pkg = readJsonSafe(path3.join(root, "package.json"));
1185
+ const pkg = readJsonSafe(path7.join(root, "package.json"));
671
1186
  if (pkg?.exports || pkg?.main) {
672
1187
  if (pkg?.bin) {
673
1188
  evidence.push("package.json has bin");
@@ -701,8 +1216,8 @@ function discoverDocs(files, root) {
701
1216
  { pattern: /^\.editorconfig$/i, kind: "config" }
702
1217
  ];
703
1218
  for (const filePath of files) {
704
- const basename = path3.basename(filePath).toLowerCase();
705
- const relPath = path3.relative(root, filePath);
1219
+ const basename = path7.basename(filePath).toLowerCase();
1220
+ const relPath = path7.relative(root, filePath);
706
1221
  let kind;
707
1222
  for (const { pattern, kind: k } of docPatterns) {
708
1223
  if (pattern.test(basename)) {
@@ -714,14 +1229,14 @@ function discoverDocs(files, root) {
714
1229
  kind = "docs";
715
1230
  }
716
1231
  if (!kind && (basename.endsWith(".md") || basename.endsWith(".mdx"))) {
717
- if (path3.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
1232
+ if (path7.dirname(relPath) === "." || isUnderDocsDir(relPath)) {
718
1233
  kind = "docs";
719
1234
  }
720
1235
  }
721
1236
  if (kind) {
722
1237
  let size = 0;
723
1238
  try {
724
- size = fs2.statSync(filePath).size;
1239
+ size = fs3.statSync(filePath).size;
725
1240
  } catch {
726
1241
  }
727
1242
  docs.push({
@@ -735,7 +1250,7 @@ function discoverDocs(files, root) {
735
1250
  return docs;
736
1251
  }
737
1252
  function isUnderDocsDir(relPath) {
738
- const parts = relPath.split(path3.sep);
1253
+ const parts = relPath.split(path7.sep);
739
1254
  return parts[0] === "docs" || parts[0] === "doc" || parts[0] === "documentation";
740
1255
  }
741
1256
  function buildPlan(languages, shape, docs, _root) {
@@ -763,14 +1278,14 @@ function buildPlan(languages, shape, docs, _root) {
763
1278
  }
764
1279
  function readJsonSafe(filePath) {
765
1280
  try {
766
- return JSON.parse(fs2.readFileSync(filePath, "utf8"));
1281
+ return JSON.parse(fs3.readFileSync(filePath, "utf8"));
767
1282
  } catch {
768
1283
  return null;
769
1284
  }
770
1285
  }
771
1286
  function readTomlWorkspace(filePath) {
772
1287
  try {
773
- const content = fs2.readFileSync(filePath, "utf8");
1288
+ const content = fs3.readFileSync(filePath, "utf8");
774
1289
  return content.includes("[workspace]");
775
1290
  } catch {
776
1291
  return false;
@@ -778,9 +1293,9 @@ function readTomlWorkspace(filePath) {
778
1293
  }
779
1294
 
780
1295
  // src/curation/index.ts
781
- import fs3 from "fs";
782
- import path4 from "path";
783
- import crypto from "crypto";
1296
+ import fs4 from "fs";
1297
+ import path8 from "path";
1298
+ import crypto3 from "crypto";
784
1299
  async function curate(options) {
785
1300
  const startTime = Date.now();
786
1301
  const {
@@ -865,18 +1380,18 @@ async function curate(options) {
865
1380
  };
866
1381
  }
867
1382
  function resolveTargets(targetPath) {
868
- const stat = fs3.statSync(targetPath);
1383
+ const stat = fs4.statSync(targetPath);
869
1384
  if (stat.isFile()) return [targetPath];
870
1385
  const results = [];
871
1386
  const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
872
1387
  function walk(dir) {
873
- for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
874
- const fullPath = path4.join(dir, entry.name);
1388
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1389
+ const fullPath = path8.join(dir, entry.name);
875
1390
  if (entry.isDirectory()) {
876
1391
  if (entry.name !== "node_modules" && entry.name !== ".git") {
877
1392
  walk(fullPath);
878
1393
  }
879
- } else if (extensions.has(path4.extname(entry.name).toLowerCase())) {
1394
+ } else if (extensions.has(path8.extname(entry.name).toLowerCase())) {
880
1395
  results.push(fullPath);
881
1396
  }
882
1397
  }
@@ -885,7 +1400,7 @@ function resolveTargets(targetPath) {
885
1400
  return results;
886
1401
  }
887
1402
  function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
888
- const relativePath = path4.relative(projectRoot, filePath);
1403
+ const relativePath = path8.relative(projectRoot, filePath);
889
1404
  const statements = [];
890
1405
  const now = (/* @__PURE__ */ new Date()).toISOString();
891
1406
  const paragraphs = content.split(/\n{2,}/).map((p) => p.trim()).filter((p) => p.length > 20 && p.length < 2e3);
@@ -976,11 +1491,11 @@ function findContradiction(stmt, existing) {
976
1491
  }
977
1492
  function loadExistingMemories(memoryDir) {
978
1493
  const result = /* @__PURE__ */ new Map();
979
- if (!fs3.existsSync(memoryDir)) return result;
1494
+ if (!fs4.existsSync(memoryDir)) return result;
980
1495
  const dirs = ALL_CATEGORY_DIRS;
981
1496
  for (const dir of dirs) {
982
- const fullDir = path4.join(memoryDir, dir);
983
- if (!fs3.existsSync(fullDir)) continue;
1497
+ const fullDir = path8.join(memoryDir, dir);
1498
+ if (!fs4.existsSync(fullDir)) continue;
984
1499
  walkFiles(fullDir, (filePath) => {
985
1500
  const content = readFileSafe(filePath);
986
1501
  if (!content) return;
@@ -1001,10 +1516,10 @@ function writeStatement(stmt, memoryDir) {
1001
1516
  const now = /* @__PURE__ */ new Date();
1002
1517
  const dateDir = now.toISOString().split("T")[0];
1003
1518
  const categoryDir = getCategoryDir(memoryDir, stmt.category);
1004
- const dir = path4.join(categoryDir, dateDir);
1005
- fs3.mkdirSync(dir, { recursive: true });
1519
+ const dir = path8.join(categoryDir, dateDir);
1520
+ fs4.mkdirSync(dir, { recursive: true });
1006
1521
  const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;
1007
- const filePath = path4.join(dir, fileName);
1522
+ const filePath = path8.join(dir, fileName);
1008
1523
  const frontmatter = [
1009
1524
  "---",
1010
1525
  `id: ${stmt.id}`,
@@ -1025,17 +1540,17 @@ function writeStatement(stmt, memoryDir) {
1025
1540
  ${stmt.content}
1026
1541
  `;
1027
1542
  try {
1028
- fs3.writeFileSync(filePath, body);
1543
+ fs4.writeFileSync(filePath, body);
1029
1544
  return filePath;
1030
1545
  } catch {
1031
1546
  return null;
1032
1547
  }
1033
1548
  }
1034
1549
  function generateId() {
1035
- return crypto.randomUUID();
1550
+ return crypto3.randomUUID();
1036
1551
  }
1037
1552
  function hashContent(content) {
1038
- return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
1553
+ return crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
1039
1554
  }
1040
1555
  function tierFromConfidence(confidence) {
1041
1556
  if (confidence >= 0.95) return "explicit";
@@ -1045,7 +1560,7 @@ function tierFromConfidence(confidence) {
1045
1560
  }
1046
1561
  function readFileSafe(filePath) {
1047
1562
  try {
1048
- return fs3.readFileSync(filePath, "utf8");
1563
+ return fs4.readFileSync(filePath, "utf8");
1049
1564
  } catch {
1050
1565
  return null;
1051
1566
  }
@@ -1074,8 +1589,8 @@ function extractBody2(content) {
1074
1589
  return match ? match[1].trim() : content.trim();
1075
1590
  }
1076
1591
  function walkFiles(dir, callback) {
1077
- for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
1078
- const fullPath = path4.join(dir, entry.name);
1592
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1593
+ const fullPath = path8.join(dir, entry.name);
1079
1594
  if (entry.isDirectory()) {
1080
1595
  walkFiles(fullPath, callback);
1081
1596
  } else if (entry.name.endsWith(".md")) {
@@ -1085,9 +1600,9 @@ function walkFiles(dir, callback) {
1085
1600
  }
1086
1601
 
1087
1602
  // src/dedup/index.ts
1088
- import fs4 from "fs";
1089
- import path5 from "path";
1090
- import crypto2 from "crypto";
1603
+ import fs5 from "fs";
1604
+ import path9 from "path";
1605
+ import crypto4 from "crypto";
1091
1606
  function findDuplicates(options) {
1092
1607
  const startTime = Date.now();
1093
1608
  const { memoryDir, threshold = 0.85, maxLoad = 1e4 } = options;
@@ -1214,8 +1729,8 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
1214
1729
  const allCategories = categories ?? ALL_CATEGORY_DIRS;
1215
1730
  for (const category of allCategories) {
1216
1731
  if (result.length >= maxLoad) break;
1217
- const dir = path5.join(memoryDir, category);
1218
- if (!fs4.existsSync(dir)) continue;
1732
+ const dir = path9.join(memoryDir, category);
1733
+ if (!fs5.existsSync(dir)) continue;
1219
1734
  walkMdFiles(dir, (filePath) => {
1220
1735
  if (result.length >= maxLoad) return;
1221
1736
  const content = readFileSafe2(filePath);
@@ -1234,11 +1749,11 @@ function loadMemories(memoryDir, categories, maxLoad = 1e4) {
1234
1749
  return result;
1235
1750
  }
1236
1751
  function hashContent2(content) {
1237
- return crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
1752
+ return crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
1238
1753
  }
1239
1754
  function readFileSafe2(filePath) {
1240
1755
  try {
1241
- return fs4.readFileSync(filePath, "utf8");
1756
+ return fs5.readFileSync(filePath, "utf8");
1242
1757
  } catch {
1243
1758
  return null;
1244
1759
  }
@@ -1261,8 +1776,8 @@ function extractBody3(content) {
1261
1776
  return match ? match[1].trim() : content.trim();
1262
1777
  }
1263
1778
  function walkMdFiles(dir, callback) {
1264
- for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1265
- const fullPath = path5.join(dir, entry.name);
1779
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
1780
+ const fullPath = path9.join(dir, entry.name);
1266
1781
  if (entry.isDirectory()) {
1267
1782
  walkMdFiles(fullPath, callback);
1268
1783
  } else if (entry.name.endsWith(".md")) {
@@ -1272,8 +1787,8 @@ function walkMdFiles(dir, callback) {
1272
1787
  }
1273
1788
 
1274
1789
  // src/review/index.ts
1275
- import fs5 from "fs";
1276
- import path6 from "path";
1790
+ import fs6 from "fs";
1791
+ import path10 from "path";
1277
1792
  function listReviewItems(options) {
1278
1793
  const startTime = Date.now();
1279
1794
  const {
@@ -1283,8 +1798,8 @@ function listReviewItems(options) {
1283
1798
  confidenceThreshold = 0.7
1284
1799
  } = options;
1285
1800
  const items = [];
1286
- const suggestionsDir = path6.join(memoryDir, "suggestions");
1287
- if (fs5.existsSync(suggestionsDir)) {
1801
+ const suggestionsDir = path10.join(memoryDir, "suggestions");
1802
+ if (fs6.existsSync(suggestionsDir)) {
1288
1803
  walkMd(suggestionsDir, (filePath, content) => {
1289
1804
  if (items.length >= limit) return;
1290
1805
  const fm = parseFrontmatter4(content);
@@ -1303,8 +1818,8 @@ function listReviewItems(options) {
1303
1818
  });
1304
1819
  });
1305
1820
  }
1306
- const reviewDir = path6.join(memoryDir, "review");
1307
- if (fs5.existsSync(reviewDir)) {
1821
+ const reviewDir = path10.join(memoryDir, "review");
1822
+ if (fs6.existsSync(reviewDir)) {
1308
1823
  walkMd(reviewDir, (filePath, content) => {
1309
1824
  if (items.length >= limit) return;
1310
1825
  const fm = parseFrontmatter4(content);
@@ -1327,8 +1842,8 @@ function listReviewItems(options) {
1327
1842
  const categories = ALL_CATEGORY_DIRS;
1328
1843
  for (const category of categories) {
1329
1844
  if (items.length >= limit) break;
1330
- const dir = path6.join(memoryDir, category);
1331
- if (!fs5.existsSync(dir)) continue;
1845
+ const dir = path10.join(memoryDir, category);
1846
+ if (!fs6.existsSync(dir)) continue;
1332
1847
  walkMd(dir, (filePath, content) => {
1333
1848
  if (items.length >= limit) return;
1334
1849
  const fm = parseFrontmatter4(content);
@@ -1370,22 +1885,22 @@ function performReview(memoryDir, itemId, action) {
1370
1885
  function approveItem(memoryDir, itemId) {
1371
1886
  const locations = ["suggestions", "review"];
1372
1887
  for (const loc of locations) {
1373
- const dir = path6.join(memoryDir, loc);
1374
- if (!fs5.existsSync(dir)) continue;
1888
+ const dir = path10.join(memoryDir, loc);
1889
+ if (!fs6.existsSync(dir)) continue;
1375
1890
  const found = findFileById(dir, itemId);
1376
1891
  if (!found) continue;
1377
- const content = fs5.readFileSync(found, "utf8");
1892
+ const content = fs6.readFileSync(found, "utf8");
1378
1893
  const fm = parseFrontmatter4(content);
1379
1894
  const body = extractBody4(content);
1380
1895
  if (!fm) return { itemId, action: "approve", message: "Could not parse frontmatter" };
1381
1896
  const category = fm.category ?? "fact";
1382
1897
  const targetDir = getCategoryDir(memoryDir, category);
1383
1898
  const dateDir = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1384
- const outputPath = path6.join(targetDir, dateDir, path6.basename(found));
1899
+ const outputPath = path10.join(targetDir, dateDir, path10.basename(found));
1385
1900
  const updatedContent = content.replace(/confidence: [\d.]+/, "confidence: 0.9").replace(/confidenceTier: \w+/, "confidenceTier: high");
1386
- fs5.mkdirSync(path6.dirname(outputPath), { recursive: true });
1387
- fs5.writeFileSync(outputPath, updatedContent);
1388
- fs5.unlinkSync(found);
1901
+ fs6.mkdirSync(path10.dirname(outputPath), { recursive: true });
1902
+ fs6.writeFileSync(outputPath, updatedContent);
1903
+ fs6.unlinkSync(found);
1389
1904
  return {
1390
1905
  itemId,
1391
1906
  action: "approve",
@@ -1398,11 +1913,11 @@ function approveItem(memoryDir, itemId) {
1398
1913
  function dismissItem(memoryDir, itemId) {
1399
1914
  const locations = ["suggestions", "review"];
1400
1915
  for (const loc of locations) {
1401
- const dir = path6.join(memoryDir, loc);
1402
- if (!fs5.existsSync(dir)) continue;
1916
+ const dir = path10.join(memoryDir, loc);
1917
+ if (!fs6.existsSync(dir)) continue;
1403
1918
  const found = findFileById(dir, itemId);
1404
1919
  if (found) {
1405
- fs5.unlinkSync(found);
1920
+ fs6.unlinkSync(found);
1406
1921
  return { itemId, action: "dismiss", message: "Dismissed and removed" };
1407
1922
  }
1408
1923
  }
@@ -1411,11 +1926,11 @@ function dismissItem(memoryDir, itemId) {
1411
1926
  function flagItem(memoryDir, itemId) {
1412
1927
  const locations = ["suggestions", "review"];
1413
1928
  for (const loc of locations) {
1414
- const dir = path6.join(memoryDir, loc);
1415
- if (!fs5.existsSync(dir)) continue;
1929
+ const dir = path10.join(memoryDir, loc);
1930
+ if (!fs6.existsSync(dir)) continue;
1416
1931
  const found = findFileById(dir, itemId);
1417
1932
  if (found) {
1418
- const content = fs5.readFileSync(found, "utf8");
1933
+ const content = fs6.readFileSync(found, "utf8");
1419
1934
  const fixed = content.replace(
1420
1935
  /^(---\n)/,
1421
1936
  `---
@@ -1423,7 +1938,7 @@ flagged: true
1423
1938
  flaggedAt: ${(/* @__PURE__ */ new Date()).toISOString()}
1424
1939
  `
1425
1940
  );
1426
- fs5.writeFileSync(found, fixed);
1941
+ fs6.writeFileSync(found, fixed);
1427
1942
  return { itemId, action: "flag", message: "Flagged for further review" };
1428
1943
  }
1429
1944
  }
@@ -1449,7 +1964,7 @@ function parseConfidence(value, fallback) {
1449
1964
  }
1450
1965
  function readFileSafe3(filePath) {
1451
1966
  try {
1452
- return fs5.readFileSync(filePath, "utf8");
1967
+ return fs6.readFileSync(filePath, "utf8");
1453
1968
  } catch {
1454
1969
  return null;
1455
1970
  }
@@ -1472,8 +1987,8 @@ function extractBody4(content) {
1472
1987
  return match ? match[1].trim() : content.trim();
1473
1988
  }
1474
1989
  function walkMd(dir, callback) {
1475
- for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
1476
- const fullPath = path6.join(dir, entry.name);
1990
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1991
+ const fullPath = path10.join(dir, entry.name);
1477
1992
  if (entry.isDirectory()) {
1478
1993
  walkMd(fullPath, callback);
1479
1994
  } else if (entry.name.endsWith(".md")) {
@@ -1484,8 +1999,8 @@ function walkMd(dir, callback) {
1484
1999
  }
1485
2000
  function walkMdPaths(dir) {
1486
2001
  const results = [];
1487
- for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
1488
- const fullPath = path6.join(dir, entry.name);
2002
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
2003
+ const fullPath = path10.join(dir, entry.name);
1489
2004
  if (entry.isDirectory()) {
1490
2005
  results.push(...walkMdPaths(fullPath));
1491
2006
  } else if (entry.name.endsWith(".md")) {
@@ -1496,9 +2011,9 @@ function walkMdPaths(dir) {
1496
2011
  }
1497
2012
 
1498
2013
  // src/sync/index.ts
1499
- import fs6 from "fs";
1500
- import path7 from "path";
1501
- import crypto3 from "crypto";
2014
+ import fs7 from "fs";
2015
+ import path11 from "path";
2016
+ import crypto5 from "crypto";
1502
2017
  var DEFAULT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
1503
2018
  var DEFAULT_EXCLUDE2 = /* @__PURE__ */ new Set([
1504
2019
  "node_modules",
@@ -1519,7 +2034,7 @@ function syncChanges(options) {
1519
2034
  } = options;
1520
2035
  const extSet = new Set(extensions);
1521
2036
  const excludeSet = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDE2, ...excludeDirs]);
1522
- const stateFilePath = options.stateFile ?? path7.join(memoryDir, ".sync-state.json");
2037
+ const stateFilePath = options.stateFile ?? path11.join(memoryDir, ".sync-state.json");
1523
2038
  const prevState = loadState(stateFilePath);
1524
2039
  const currentFiles = scanFiles(sourceDir, extSet, excludeSet);
1525
2040
  const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);
@@ -1535,8 +2050,8 @@ function syncChanges(options) {
1535
2050
  for (const [relPath, hash] of Object.entries(currentFiles)) {
1536
2051
  newState.fileHashes[relPath] = hash;
1537
2052
  }
1538
- fs6.mkdirSync(path7.dirname(stateFilePath), { recursive: true });
1539
- fs6.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
2053
+ fs7.mkdirSync(path11.dirname(stateFilePath), { recursive: true });
2054
+ fs7.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));
1540
2055
  }
1541
2056
  return {
1542
2057
  scanned: Object.keys(currentFiles).length,
@@ -1578,21 +2093,21 @@ function scanFiles(root, extensions, exclude) {
1578
2093
  function walk(dir) {
1579
2094
  let entries;
1580
2095
  try {
1581
- entries = fs6.readdirSync(dir, { withFileTypes: true });
2096
+ entries = fs7.readdirSync(dir, { withFileTypes: true });
1582
2097
  } catch {
1583
2098
  return;
1584
2099
  }
1585
2100
  for (const entry of entries) {
1586
2101
  if (exclude.has(entry.name)) continue;
1587
- const fullPath = path7.join(dir, entry.name);
2102
+ const fullPath = path11.join(dir, entry.name);
1588
2103
  if (entry.isDirectory()) {
1589
2104
  walk(fullPath);
1590
2105
  } else if (entry.isFile()) {
1591
- const ext = path7.extname(entry.name).toLowerCase();
2106
+ const ext = path11.extname(entry.name).toLowerCase();
1592
2107
  if (!extensions.has(ext)) continue;
1593
- const relPath = path7.relative(root, fullPath);
2108
+ const relPath = path11.relative(root, fullPath);
1594
2109
  try {
1595
- const content = fs6.readFileSync(fullPath, "utf8");
2110
+ const content = fs7.readFileSync(fullPath, "utf8");
1596
2111
  result[relPath] = hashContent3(content);
1597
2112
  } catch {
1598
2113
  }
@@ -1605,11 +2120,11 @@ function scanFiles(root, extensions, exclude) {
1605
2120
  function computeDiff(current, previous, sourceDir) {
1606
2121
  const changes = [];
1607
2122
  for (const [relPath, hash] of Object.entries(current)) {
1608
- const fullPath = path7.join(sourceDir, relPath);
2123
+ const fullPath = path11.join(sourceDir, relPath);
1609
2124
  if (!(relPath in previous)) {
1610
2125
  let size = 0;
1611
2126
  try {
1612
- size = fs6.statSync(fullPath).size;
2127
+ size = fs7.statSync(fullPath).size;
1613
2128
  } catch {
1614
2129
  }
1615
2130
  changes.push({
@@ -1622,7 +2137,7 @@ function computeDiff(current, previous, sourceDir) {
1622
2137
  } else if (previous[relPath] !== hash) {
1623
2138
  let size = 0;
1624
2139
  try {
1625
- size = fs6.statSync(fullPath).size;
2140
+ size = fs7.statSync(fullPath).size;
1626
2141
  } catch {
1627
2142
  }
1628
2143
  changes.push({
@@ -1638,7 +2153,7 @@ function computeDiff(current, previous, sourceDir) {
1638
2153
  for (const relPath of Object.keys(previous)) {
1639
2154
  if (!(relPath in current)) {
1640
2155
  changes.push({
1641
- filePath: path7.join(sourceDir, relPath),
2156
+ filePath: path11.join(sourceDir, relPath),
1642
2157
  relativePath: relPath,
1643
2158
  type: "deleted",
1644
2159
  currentHash: "",
@@ -1650,7 +2165,7 @@ function computeDiff(current, previous, sourceDir) {
1650
2165
  }
1651
2166
  function loadState(stateFilePath) {
1652
2167
  try {
1653
- const raw = fs6.readFileSync(stateFilePath, "utf8");
2168
+ const raw = fs7.readFileSync(stateFilePath, "utf8");
1654
2169
  return JSON.parse(raw);
1655
2170
  } catch {
1656
2171
  return {
@@ -1661,60 +2176,313 @@ function loadState(stateFilePath) {
1661
2176
  }
1662
2177
  }
1663
2178
  function hashContent3(content) {
1664
- return crypto3.createHash("sha256").update(content).digest("hex").slice(0, 16);
2179
+ return crypto5.createHash("sha256").update(content).digest("hex").slice(0, 16);
1665
2180
  }
1666
2181
 
1667
2182
  // src/connectors/index.ts
1668
- import fs7 from "fs";
1669
- import path8 from "path";
1670
- var BUILTIN_CONNECTORS = [
1671
- {
1672
- id: "claude-code",
1673
- name: "Claude Code",
1674
- version: "1.0.0",
1675
- description: "Anthropic's Claude Code CLI \u2014 direct memory access via MCP",
1676
- capabilities: {
1677
- observe: true,
1678
- recall: true,
1679
- store: true,
1680
- search: true,
1681
- entities: true,
1682
- realtimeSync: true,
1683
- batch: false,
1684
- maxBudgetChars: 32e3,
1685
- connectionType: "mcp"
1686
- },
1687
- configSchema: {
1688
- mcpServerUrl: "URL of the MCP Remnic server",
1689
- namespace: "Optional namespace (default: 'default')"
1690
- },
1691
- homepage: "https://claude.ai/code",
1692
- author: "Anthropic",
1693
- tags: ["official", "ai", "claude"]
1694
- },
1695
- {
1696
- id: "codex-cli",
1697
- name: "Codex CLI",
1698
- version: "1.0.0",
1699
- description: "OpenAI Codex CLI \u2014 memory via MCP tool",
1700
- capabilities: {
1701
- observe: true,
1702
- recall: true,
1703
- store: true,
1704
- search: false,
1705
- entities: false,
1706
- realtimeSync: false,
1707
- batch: true,
1708
- maxBudgetChars: 8e3,
1709
- connectionType: "mcp"
1710
- },
1711
- configSchema: {
1712
- mcpServerUrl: "URL of the MCP Remnic server",
1713
- namespace: "Optional namespace"
1714
- },
1715
- homepage: "https://openai.com/codex",
1716
- author: "OpenAI",
1717
- tags: ["official", "ai", "codex"]
2183
+ import fs8 from "fs";
2184
+ import path13 from "path";
2185
+ import os from "os";
2186
+ import { spawnSync } from "child_process";
2187
+ import { createRequire } from "module";
2188
+ import { fileURLToPath } from "url";
2189
+
2190
+ // src/connectors/codex-marketplace.ts
2191
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
2192
+ import path12 from "path";
2193
+ var MARKETPLACE_SCHEMA_VERSION = 1;
2194
+ var MARKETPLACE_MANIFEST_FILENAME = "marketplace.json";
2195
+ var VALID_INSTALL_TYPES = /* @__PURE__ */ new Set(["github", "git", "local", "url"]);
2196
+ function generateMarketplaceManifest(options) {
2197
+ const version = options?.packageVersion ?? readPackageVersion() ?? "0.0.0";
2198
+ return {
2199
+ version: MARKETPLACE_SCHEMA_VERSION,
2200
+ name: "remnic",
2201
+ description: "Remnic: Local-first AI memory with semantic search and consolidation",
2202
+ plugins: [
2203
+ {
2204
+ name: "remnic",
2205
+ version,
2206
+ description: "Persistent memory plugin for Codex CLI",
2207
+ repository: "joshuaswarren/remnic",
2208
+ installType: "github",
2209
+ entry: "packages/plugin-codex",
2210
+ configSchema: "openclaw.plugin.json"
2211
+ }
2212
+ ]
2213
+ };
2214
+ }
2215
+ function validateMarketplaceManifest(manifest) {
2216
+ const validation = checkMarketplaceManifest(manifest);
2217
+ if (!validation.valid) {
2218
+ throw new Error(
2219
+ `Invalid marketplace manifest: ${validation.errors.join("; ")}`
2220
+ );
2221
+ }
2222
+ return manifest;
2223
+ }
2224
+ function checkMarketplaceManifest(manifest) {
2225
+ const errors = [];
2226
+ if (typeof manifest !== "object" || manifest === null) {
2227
+ return { valid: false, errors: ["manifest must be a non-null object"] };
2228
+ }
2229
+ const obj = manifest;
2230
+ if (obj.version !== MARKETPLACE_SCHEMA_VERSION) {
2231
+ errors.push(`version must be ${MARKETPLACE_SCHEMA_VERSION}, got ${JSON.stringify(obj.version)}`);
2232
+ }
2233
+ if (typeof obj.name !== "string" || obj.name.trim().length === 0) {
2234
+ errors.push("name must be a non-empty string");
2235
+ }
2236
+ if (typeof obj.description !== "string" || obj.description.trim().length === 0) {
2237
+ errors.push("description must be a non-empty string");
2238
+ }
2239
+ if (!Array.isArray(obj.plugins)) {
2240
+ errors.push("plugins must be an array");
2241
+ } else if (obj.plugins.length === 0) {
2242
+ errors.push("plugins must contain at least one entry");
2243
+ } else {
2244
+ for (let i = 0; i < obj.plugins.length; i++) {
2245
+ const plugin = obj.plugins[i];
2246
+ const prefix = `plugins[${i}]`;
2247
+ if (typeof plugin !== "object" || plugin === null) {
2248
+ errors.push(`${prefix} must be a non-null object`);
2249
+ continue;
2250
+ }
2251
+ if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
2252
+ errors.push(`${prefix}.name must be a non-empty string`);
2253
+ }
2254
+ if (typeof plugin.version !== "string" || plugin.version.trim().length === 0) {
2255
+ errors.push(`${prefix}.version must be a non-empty string`);
2256
+ }
2257
+ if (typeof plugin.description !== "string" || plugin.description.trim().length === 0) {
2258
+ errors.push(`${prefix}.description must be a non-empty string`);
2259
+ }
2260
+ if (typeof plugin.repository !== "string" || plugin.repository.trim().length === 0) {
2261
+ errors.push(`${prefix}.repository must be a non-empty string`);
2262
+ }
2263
+ if (typeof plugin.installType !== "string" || !VALID_INSTALL_TYPES.has(plugin.installType)) {
2264
+ errors.push(
2265
+ `${prefix}.installType must be one of: ${[...VALID_INSTALL_TYPES].join(", ")}; got ${JSON.stringify(plugin.installType)}`
2266
+ );
2267
+ }
2268
+ if ("manifestUrl" in plugin && plugin.manifestUrl !== void 0) {
2269
+ if (typeof plugin.manifestUrl !== "string" || plugin.manifestUrl.trim().length === 0) {
2270
+ errors.push(`${prefix}.manifestUrl must be a non-empty string when provided`);
2271
+ }
2272
+ }
2273
+ if ("entry" in plugin && plugin.entry !== void 0) {
2274
+ if (typeof plugin.entry !== "string" || plugin.entry.trim().length === 0) {
2275
+ errors.push(`${prefix}.entry must be a non-empty string when provided`);
2276
+ }
2277
+ }
2278
+ if ("configSchema" in plugin && plugin.configSchema !== void 0) {
2279
+ if (typeof plugin.configSchema !== "string" || plugin.configSchema.trim().length === 0) {
2280
+ errors.push(`${prefix}.configSchema must be a non-empty string when provided`);
2281
+ }
2282
+ }
2283
+ }
2284
+ }
2285
+ return { valid: errors.length === 0, errors };
2286
+ }
2287
+ async function writeMarketplaceManifest(outputDir, manifest) {
2288
+ const validation = checkMarketplaceManifest(manifest);
2289
+ if (!validation.valid) {
2290
+ throw new Error(
2291
+ `Refusing to write invalid manifest: ${validation.errors.join("; ")}`
2292
+ );
2293
+ }
2294
+ mkdirSync(outputDir, { recursive: true });
2295
+ const destPath = path12.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);
2296
+ const tmpPath = `${destPath}.tmp.${process.pid}`;
2297
+ const content = JSON.stringify(manifest, null, 2) + "\n";
2298
+ writeFileSync(tmpPath, content);
2299
+ renameSync(tmpPath, destPath);
2300
+ }
2301
+ async function installFromMarketplace(source, sourceType, config, logger) {
2302
+ const _log = logger ?? {
2303
+ info: (msg) => log.info(`[marketplace] ${msg}`),
2304
+ warn: (msg) => log.warn(`[marketplace] ${msg}`),
2305
+ debug: (msg) => log.debug(`[marketplace] ${msg}`)
2306
+ };
2307
+ if (!config.codexMarketplaceEnabled) {
2308
+ return {
2309
+ ok: false,
2310
+ message: "Codex marketplace is disabled in config (codexMarketplaceEnabled: false)",
2311
+ source,
2312
+ sourceType,
2313
+ pluginsFound: [],
2314
+ errors: ["marketplace_disabled"]
2315
+ };
2316
+ }
2317
+ try {
2318
+ const manifest = await resolveManifest(source, sourceType, _log);
2319
+ const pluginNames = manifest.plugins.map((p) => p.name);
2320
+ _log.info(`marketplace install: found ${pluginNames.length} plugin(s) from ${sourceType}://${source}`);
2321
+ return {
2322
+ ok: true,
2323
+ message: `Successfully resolved ${pluginNames.length} plugin(s) from marketplace: ${pluginNames.join(", ")}`,
2324
+ source,
2325
+ sourceType,
2326
+ pluginsFound: pluginNames,
2327
+ errors: []
2328
+ };
2329
+ } catch (err) {
2330
+ const errMsg = err instanceof Error ? err.message : String(err);
2331
+ _log.warn(`marketplace install failed: ${errMsg}`);
2332
+ return {
2333
+ ok: false,
2334
+ message: `Failed to install from marketplace: ${errMsg}`,
2335
+ source,
2336
+ sourceType,
2337
+ pluginsFound: [],
2338
+ errors: [errMsg]
2339
+ };
2340
+ }
2341
+ }
2342
+ async function resolveManifest(source, sourceType, logger) {
2343
+ switch (sourceType) {
2344
+ case "local":
2345
+ return resolveLocal(source, logger);
2346
+ case "url":
2347
+ return resolveUrl(source, logger);
2348
+ case "github":
2349
+ return resolveGithub(source, logger);
2350
+ case "git":
2351
+ return resolveGit(source, logger);
2352
+ default: {
2353
+ const _ = sourceType;
2354
+ throw new Error(`Invalid source type: ${String(_)}`);
2355
+ }
2356
+ }
2357
+ }
2358
+ function resolveLocal(dirPath, logger) {
2359
+ const manifestPath2 = path12.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);
2360
+ if (!existsSync(manifestPath2)) {
2361
+ throw new Error(`marketplace.json not found at ${manifestPath2}`);
2362
+ }
2363
+ logger.debug?.(`reading local marketplace manifest: ${manifestPath2}`);
2364
+ const raw = readFileSync(manifestPath2, "utf-8");
2365
+ let parsed;
2366
+ try {
2367
+ parsed = JSON.parse(raw);
2368
+ } catch {
2369
+ throw new Error(`Invalid JSON in ${manifestPath2}`);
2370
+ }
2371
+ if (typeof parsed !== "object" || parsed === null) {
2372
+ throw new Error(`marketplace.json at ${manifestPath2} is not a valid object`);
2373
+ }
2374
+ return validateMarketplaceManifest(parsed);
2375
+ }
2376
+ async function resolveUrl(url, logger) {
2377
+ logger.debug?.(`fetching marketplace manifest from URL: ${url}`);
2378
+ let parsedUrl;
2379
+ try {
2380
+ parsedUrl = new URL(url);
2381
+ } catch {
2382
+ throw new Error(`Invalid URL: ${url}`);
2383
+ }
2384
+ if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
2385
+ throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol} (use https or http)`);
2386
+ }
2387
+ const response = await fetch(url);
2388
+ if (!response.ok) {
2389
+ throw new Error(`HTTP ${response.status} fetching ${url}`);
2390
+ }
2391
+ const body = await response.json();
2392
+ return validateMarketplaceManifest(body);
2393
+ }
2394
+ async function resolveGithub(repo, logger) {
2395
+ if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/u.test(repo)) {
2396
+ throw new Error(`Invalid GitHub repo format: "${repo}" (expected owner/repo)`);
2397
+ }
2398
+ const rawUrl = `https://raw.githubusercontent.com/${repo}/HEAD/${MARKETPLACE_MANIFEST_FILENAME}`;
2399
+ logger.debug?.(`fetching marketplace manifest from GitHub: ${rawUrl}`);
2400
+ return resolveUrl(rawUrl, logger);
2401
+ }
2402
+ async function resolveGit(gitUrl, logger) {
2403
+ const ghMatch = gitUrl.match(
2404
+ /^(?:https?:\/\/)?github\.com\/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+?)(?:\.git)?$/u
2405
+ );
2406
+ if (ghMatch?.[1]) {
2407
+ logger.debug?.(`git URL looks like GitHub \u2014 delegating to github resolver`);
2408
+ return resolveGithub(ghMatch[1], logger);
2409
+ }
2410
+ throw new Error(
2411
+ `Git URL resolution requires a GitHub-format URL for now. Got: ${gitUrl}. Use --type github or --type url instead.`
2412
+ );
2413
+ }
2414
+ function readPackageVersion() {
2415
+ const candidates = [
2416
+ path12.resolve(import.meta.dirname ?? ".", "../../../.."),
2417
+ path12.resolve(import.meta.dirname ?? ".", "../../../../.."),
2418
+ path12.resolve(import.meta.dirname ?? ".", "..")
2419
+ ];
2420
+ for (const candidate of candidates) {
2421
+ const pkgPath = path12.join(candidate, "package.json");
2422
+ try {
2423
+ if (!existsSync(pkgPath)) continue;
2424
+ const raw = readFileSync(pkgPath, "utf-8");
2425
+ const parsed = JSON.parse(raw);
2426
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.version === "string") {
2427
+ return parsed.version;
2428
+ }
2429
+ } catch {
2430
+ }
2431
+ }
2432
+ return void 0;
2433
+ }
2434
+
2435
+ // src/connectors/index.ts
2436
+ var BUILTIN_CONNECTORS = [
2437
+ {
2438
+ id: "claude-code",
2439
+ name: "Claude Code",
2440
+ version: "1.0.0",
2441
+ description: "Anthropic's Claude Code CLI \u2014 direct memory access via MCP",
2442
+ capabilities: {
2443
+ observe: true,
2444
+ recall: true,
2445
+ store: true,
2446
+ search: true,
2447
+ entities: true,
2448
+ realtimeSync: true,
2449
+ batch: false,
2450
+ maxBudgetChars: 32e3,
2451
+ connectionType: "mcp"
2452
+ },
2453
+ configSchema: {
2454
+ mcpServerUrl: "URL of the MCP Remnic server",
2455
+ namespace: "Optional namespace (default: 'default')"
2456
+ },
2457
+ homepage: "https://claude.ai/code",
2458
+ author: "Anthropic",
2459
+ tags: ["official", "ai", "claude"],
2460
+ requiresToken: true
2461
+ },
2462
+ {
2463
+ id: "codex-cli",
2464
+ name: "Codex CLI",
2465
+ version: "1.0.0",
2466
+ description: "OpenAI Codex CLI \u2014 memory via MCP tool",
2467
+ capabilities: {
2468
+ observe: true,
2469
+ recall: true,
2470
+ store: true,
2471
+ search: false,
2472
+ entities: false,
2473
+ realtimeSync: false,
2474
+ batch: true,
2475
+ maxBudgetChars: 8e3,
2476
+ connectionType: "mcp"
2477
+ },
2478
+ configSchema: {
2479
+ mcpServerUrl: "URL of the MCP Remnic server",
2480
+ namespace: "Optional namespace"
2481
+ },
2482
+ homepage: "https://openai.com/codex",
2483
+ author: "OpenAI",
2484
+ tags: ["official", "ai", "codex"],
2485
+ requiresToken: true
1718
2486
  },
1719
2487
  {
1720
2488
  id: "cursor",
@@ -1878,7 +2646,8 @@ var BUILTIN_CONNECTORS = [
1878
2646
  },
1879
2647
  homepage: "https://replit.com",
1880
2648
  author: "Replit",
1881
- tags: ["official", "cloud"]
2649
+ tags: ["official", "cloud"],
2650
+ requiresToken: true
1882
2651
  },
1883
2652
  {
1884
2653
  id: "generic-mcp",
@@ -1903,17 +2672,72 @@ var BUILTIN_CONNECTORS = [
1903
2672
  },
1904
2673
  homepage: "https://github.com/joshuaswarren/remnic",
1905
2674
  author: "Remnic",
1906
- tags: ["generic", "mcp"]
2675
+ tags: ["generic", "mcp"],
2676
+ requiresToken: true
2677
+ },
2678
+ {
2679
+ id: "weclone",
2680
+ name: "WeClone Avatar",
2681
+ version: "1.0.0",
2682
+ description: "Memory-aware OpenAI-compatible proxy for deployed WeClone avatars \u2014 injects Remnic recall into chat completions and buffers turns via observe",
2683
+ capabilities: {
2684
+ observe: true,
2685
+ recall: true,
2686
+ store: false,
2687
+ search: false,
2688
+ entities: false,
2689
+ realtimeSync: false,
2690
+ batch: false,
2691
+ maxBudgetChars: 32e3,
2692
+ connectionType: "http"
2693
+ },
2694
+ configSchema: {
2695
+ wecloneApiUrl: "Base URL of the WeClone OpenAI-compatible API (e.g. http://localhost:8000/v1)",
2696
+ proxyPort: "Local port where the memory proxy will listen (default 8100)",
2697
+ remnicDaemonUrl: "URL of the Remnic daemon exposing /engram/v1/recall and /engram/v1/observe",
2698
+ sessionStrategy: "Per-caller session mapping strategy: 'caller-id' | 'single'",
2699
+ wecloneModelName: "Optional fine-tuned model name passed through to WeClone"
2700
+ },
2701
+ homepage: "https://github.com/xming521/weclone",
2702
+ author: "Remnic",
2703
+ tags: ["official", "ai", "weclone", "proxy"],
2704
+ requiresToken: true
2705
+ },
2706
+ {
2707
+ id: "hermes",
2708
+ name: "Hermes Agent",
2709
+ version: "1.0.0",
2710
+ description: "Hermes Agent MemoryProvider \u2014 automatic recall/observe on every turn via Python plugin protocol",
2711
+ capabilities: {
2712
+ observe: true,
2713
+ recall: true,
2714
+ store: true,
2715
+ search: true,
2716
+ entities: false,
2717
+ realtimeSync: true,
2718
+ batch: false,
2719
+ maxBudgetChars: 32e3,
2720
+ connectionType: "http"
2721
+ },
2722
+ configSchema: {
2723
+ host: "Remnic daemon host (default: 127.0.0.1)",
2724
+ port: "Remnic daemon port (default: 4318)",
2725
+ profile: "Hermes profile name (default: default)"
2726
+ },
2727
+ homepage: "https://github.com/joshuaswarren/remnic/tree/main/packages/plugin-hermes",
2728
+ author: "Remnic",
2729
+ tags: ["official", "python", "hermes"],
2730
+ requiresToken: true
1907
2731
  }
1908
2732
  ];
1909
2733
  var REGISTRY_DIR_NAME = ".engram-connectors";
1910
2734
  function getRegistryPath() {
1911
- const configDir = process.env.XDG_CONFIG_HOME ? path8.join(process.env.XDG_CONFIG_HOME, "engram") : path8.join(process.env.HOME ?? "~", ".config", "engram");
1912
- return path8.join(configDir, REGISTRY_DIR_NAME, "registry.json");
2735
+ const configDir = process.env.XDG_CONFIG_HOME ? path13.join(process.env.XDG_CONFIG_HOME, "engram") : path13.join(process.env.HOME ?? "~", ".config", "engram");
2736
+ return path13.join(configDir, REGISTRY_DIR_NAME, "registry.json");
1913
2737
  }
1914
2738
  function loadRegistry() {
1915
2739
  const regPath = getRegistryPath();
1916
- if (!fs7.existsSync(regPath)) {
2740
+ if (!fs8.existsSync(regPath)) {
1917
2741
  const registry = {
1918
2742
  connectors: BUILTIN_CONNECTORS,
1919
2743
  registryPath: regPath
@@ -1921,14 +2745,12 @@ function loadRegistry() {
1921
2745
  saveRegistry(registry);
1922
2746
  return registry;
1923
2747
  }
1924
- const raw = fs7.readFileSync(regPath, "utf8");
2748
+ const raw = fs8.readFileSync(regPath, "utf8");
1925
2749
  try {
1926
2750
  const parsed = JSON.parse(raw);
1927
- const customIds = new Set((parsed.connectors ?? []).map((c) => c.id));
1928
- const merged = [
1929
- ...BUILTIN_CONNECTORS.filter((b) => !customIds.has(b.id)),
1930
- ...parsed.connectors ?? []
1931
- ];
2751
+ const builtinIds = new Set(BUILTIN_CONNECTORS.map((b) => b.id));
2752
+ const customOnly = (parsed.connectors ?? []).filter((c) => !builtinIds.has(c.id));
2753
+ const merged = [...BUILTIN_CONNECTORS, ...customOnly];
1932
2754
  return {
1933
2755
  connectors: merged,
1934
2756
  registryPath: regPath
@@ -1944,19 +2766,19 @@ function loadRegistry() {
1944
2766
  }
1945
2767
  function saveRegistry(registry) {
1946
2768
  const regPath = registry.registryPath;
1947
- fs7.mkdirSync(path8.dirname(regPath), { recursive: true });
1948
- fs7.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
2769
+ fs8.mkdirSync(path13.dirname(regPath), { recursive: true });
2770
+ fs8.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
1949
2771
  }
1950
2772
  function listConnectors() {
1951
2773
  const registry = loadRegistry();
1952
2774
  const connectorsDir = getConnectorsDir();
1953
2775
  const installedIds = /* @__PURE__ */ new Set();
1954
- if (fs7.existsSync(connectorsDir)) {
1955
- for (const entry of fs7.readdirSync(connectorsDir)) {
2776
+ if (fs8.existsSync(connectorsDir)) {
2777
+ for (const entry of fs8.readdirSync(connectorsDir)) {
1956
2778
  if (entry.endsWith(".json")) {
1957
2779
  try {
1958
2780
  const config = JSON.parse(
1959
- fs7.readFileSync(path8.join(connectorsDir, entry), "utf8")
2781
+ fs8.readFileSync(path13.join(connectorsDir, entry), "utf8")
1960
2782
  );
1961
2783
  installedIds.add(config.connectorId);
1962
2784
  } catch {
@@ -1970,14 +2792,15 @@ function listConnectors() {
1970
2792
  }));
1971
2793
  const installed = [];
1972
2794
  for (const id of installedIds) {
1973
- const configPath = path8.join(connectorsDir, `${id}.json`);
2795
+ const configPath = path13.join(connectorsDir, `${id}.json`);
1974
2796
  try {
1975
- const config = JSON.parse(fs7.readFileSync(configPath, "utf8"));
2797
+ const raw = JSON.parse(fs8.readFileSync(configPath, "utf8"));
2798
+ const { token: _redacted, ...config } = raw;
1976
2799
  installed.push({
1977
2800
  connectorId: id,
1978
2801
  config,
1979
2802
  status: "installed",
1980
- installedAt: config.installedAt
2803
+ installedAt: raw.installedAt
1981
2804
  });
1982
2805
  } catch {
1983
2806
  }
@@ -2005,38 +2828,890 @@ function installConnector(options) {
2005
2828
  };
2006
2829
  }
2007
2830
  const configDir = getConnectorsDir();
2008
- fs7.mkdirSync(configDir, { recursive: true });
2009
- const configPath = path8.join(configDir, `${options.connectorId}.json`);
2831
+ fs8.mkdirSync(configDir, { recursive: true });
2832
+ const configPath = path13.join(configDir, `${options.connectorId}.json`);
2833
+ let hermesSavedProfile;
2834
+ let hermesSavedHost;
2835
+ let hermesSavedPort;
2836
+ let hermesResolvedProfile;
2837
+ let hermesResolvedHost;
2838
+ let hermesResolvedPort;
2839
+ if (options.connectorId === "hermes") {
2840
+ if (fs8.existsSync(configPath)) {
2841
+ try {
2842
+ const prev = JSON.parse(fs8.readFileSync(configPath, "utf8"));
2843
+ if (prev?.profile != null) {
2844
+ try {
2845
+ hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));
2846
+ } catch {
2847
+ }
2848
+ }
2849
+ if (prev?.host != null) {
2850
+ try {
2851
+ hermesSavedHost = sanitizeHermesHost(String(prev.host));
2852
+ } catch {
2853
+ }
2854
+ }
2855
+ if (prev?.port != null) {
2856
+ try {
2857
+ const coercedPort = Number(String(prev.port));
2858
+ hermesSavedPort = sanitizeHermesPort(coercedPort);
2859
+ } catch {
2860
+ }
2861
+ }
2862
+ } catch {
2863
+ }
2864
+ }
2865
+ hermesResolvedProfile = hermesSavedProfile ?? "default";
2866
+ hermesResolvedHost = hermesSavedHost ?? "127.0.0.1";
2867
+ if (options.config?.port !== void 0) {
2868
+ try {
2869
+ hermesResolvedPort = sanitizeHermesPort(Number(String(options.config.port)));
2870
+ } catch (err) {
2871
+ return {
2872
+ connectorId: options.connectorId,
2873
+ status: "error",
2874
+ message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
2875
+ };
2876
+ }
2877
+ }
2878
+ if (hermesResolvedPort === void 0) {
2879
+ hermesResolvedPort = hermesSavedPort ?? 4318;
2880
+ }
2881
+ if (options.config?.profile !== void 0) {
2882
+ try {
2883
+ hermesResolvedProfile = sanitizeHermesProfile(String(options.config.profile));
2884
+ } catch (err) {
2885
+ return {
2886
+ connectorId: options.connectorId,
2887
+ status: "error",
2888
+ message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
2889
+ };
2890
+ }
2891
+ }
2892
+ if (options.config?.host !== void 0) {
2893
+ try {
2894
+ hermesResolvedHost = sanitizeHermesHost(String(options.config.host));
2895
+ } catch (err) {
2896
+ return {
2897
+ connectorId: options.connectorId,
2898
+ status: "error",
2899
+ message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`
2900
+ };
2901
+ }
2902
+ }
2903
+ }
2904
+ const nonHermesPriorTokenStore = options.connectorId !== "hermes" && manifest.requiresToken ? loadTokenStore() : null;
2905
+ let tokenEntry = null;
2906
+ if (options.connectorId === "hermes") {
2907
+ try {
2908
+ tokenEntry = buildTokenEntry(options.connectorId);
2909
+ } catch {
2910
+ }
2911
+ } else if (manifest.requiresToken) {
2912
+ try {
2913
+ tokenEntry = generateToken(options.connectorId);
2914
+ } catch {
2915
+ if (nonHermesPriorTokenStore !== null) {
2916
+ try {
2917
+ saveTokenStore(nonHermesPriorTokenStore);
2918
+ } catch {
2919
+ }
2920
+ }
2921
+ }
2922
+ }
2923
+ if (options.connectorId !== "hermes" && manifest.requiresToken && tokenEntry === null) {
2924
+ return {
2925
+ connectorId: options.connectorId,
2926
+ status: "error",
2927
+ message: `${manifest.name} install aborted: token generation failed. Run \`remnic token generate ${options.connectorId}\` to create the token, then reinstall.`
2928
+ };
2929
+ }
2930
+ const { token: _callerToken, ...safeUserConfig } = options.config ?? {};
2010
2931
  const resolvedConfig = {
2011
2932
  connectorId: options.connectorId,
2012
2933
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
2013
- ...options.config
2014
- };
2015
- fs7.writeFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
2016
- return {
2017
- connectorId: options.connectorId,
2018
- status: "installed",
2019
- configPath,
2020
- message: `Installed ${manifest.name} v${manifest.version}`
2934
+ ...safeUserConfig,
2935
+ // For hermes, always overlay the sanitized/coerced resolved values so that
2936
+ // the connector JSON always has a numeric port and validated profile/host.
2937
+ // This also ensures options.config string values (from --config=port=5555)
2938
+ // are replaced with their sanitized numeric equivalents (Fix 2 root cause).
2939
+ ...hermesResolvedProfile !== void 0 ? {
2940
+ profile: hermesResolvedProfile,
2941
+ host: hermesResolvedHost,
2942
+ port: hermesResolvedPort
2943
+ } : {}
2021
2944
  };
2022
- }
2023
- function removeConnector(connectorId) {
2024
- const configDir = getConnectorsDir();
2025
- const configPath = path8.join(configDir, `${connectorId}.json`);
2026
- if (!fs7.existsSync(configPath)) {
2945
+ if (options.connectorId === "hermes") {
2946
+ const rawProfile = hermesResolvedProfile;
2947
+ const hermesHost = hermesResolvedHost;
2948
+ const hermesPort = hermesResolvedPort;
2949
+ let hermesProfile;
2950
+ try {
2951
+ hermesProfile = sanitizeHermesProfile(rawProfile);
2952
+ } catch (err) {
2953
+ return {
2954
+ connectorId: options.connectorId,
2955
+ status: "error",
2956
+ message: `Hermes install aborted: ${err instanceof Error ? err.message : String(err)}`
2957
+ };
2958
+ }
2959
+ if (!tokenEntry) {
2960
+ return {
2961
+ connectorId: options.connectorId,
2962
+ status: "error",
2963
+ message: "Hermes install aborted: token store unavailable. Run `remnic token generate hermes` then reinstall to complete setup."
2964
+ };
2965
+ }
2966
+ let yamlResult;
2967
+ try {
2968
+ yamlResult = upsertHermesConfig({
2969
+ profile: hermesProfile,
2970
+ host: hermesHost,
2971
+ port: hermesPort,
2972
+ token: tokenEntry.token
2973
+ });
2974
+ } catch (err) {
2975
+ return {
2976
+ connectorId: options.connectorId,
2977
+ status: "error",
2978
+ message: `Hermes install aborted: config.yaml write failed \u2014 ${err instanceof Error ? err.message : String(err)}`
2979
+ };
2980
+ }
2981
+ if (!yamlResult.updated) {
2982
+ return {
2983
+ connectorId: options.connectorId,
2984
+ status: "error",
2985
+ message: `Hermes install aborted: ${yamlResult.reason ?? "config.yaml not written"}. Create the Hermes profile directory first, then reinstall.`
2986
+ };
2987
+ }
2988
+ const priorTokenStore = loadTokenStore();
2989
+ let committed = false;
2990
+ try {
2991
+ commitTokenEntry(tokenEntry);
2992
+ committed = true;
2993
+ } catch (commitErr) {
2994
+ let tokensRolledBack = true;
2995
+ let tokensRollbackErrMsg = "";
2996
+ try {
2997
+ saveTokenStore(priorTokenStore);
2998
+ } catch (tokenRestoreErr) {
2999
+ tokensRolledBack = false;
3000
+ tokensRollbackErrMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
3001
+ }
3002
+ let yamlRolledBack = true;
3003
+ let yamlRollbackErrMsg = "";
3004
+ try {
3005
+ if (yamlResult.priorContent === null) {
3006
+ fs8.unlinkSync(yamlResult.configPath);
3007
+ } else if (typeof yamlResult.priorContent === "string") {
3008
+ writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
3009
+ }
3010
+ } catch (yamlRestoreErr) {
3011
+ yamlRolledBack = false;
3012
+ yamlRollbackErrMsg = yamlRestoreErr instanceof Error ? yamlRestoreErr.message : String(yamlRestoreErr);
3013
+ }
3014
+ const commitErrMsg = commitErr instanceof Error ? commitErr.message : String(commitErr);
3015
+ let message;
3016
+ if (tokensRolledBack && yamlRolledBack) {
3017
+ message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. config.yaml and tokens.json restored to prior state. Resolve the tokens.json access issue, then reinstall.`;
3018
+ } else if (!yamlRolledBack && tokensRolledBack) {
3019
+ message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. tokens.json restored but config.yaml rollback ALSO failed (${yamlRollbackErrMsg}). Hermes daemon may be in an inconsistent state: config references a stale token. Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and reinstall.`;
3020
+ } else if (yamlRolledBack && !tokensRolledBack) {
3021
+ message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. config.yaml restored but tokens.json rollback ALSO failed (${tokensRollbackErrMsg}). Hermes daemon may be in an inconsistent state: tokens.json is corrupt or incomplete. Manually inspect ~/.remnic/tokens.json and reinstall.`;
3022
+ } else {
3023
+ message = `Hermes install failed during token commit \u2014 ${commitErrMsg}. BOTH rollbacks failed: config.yaml rollback failed (${yamlRollbackErrMsg}); tokens.json rollback failed (${tokensRollbackErrMsg}). Hermes daemon is likely in an inconsistent state. Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and ~/.remnic/tokens.json, then reinstall.`;
3024
+ }
3025
+ return {
3026
+ connectorId: options.connectorId,
3027
+ status: "error",
3028
+ message
3029
+ };
3030
+ }
3031
+ try {
3032
+ writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
3033
+ } catch (writeErr) {
3034
+ let tokenRollbackFailed = false;
3035
+ let tokenRollbackMsg = "token store restored to pre-install snapshot";
3036
+ try {
3037
+ saveTokenStore(priorTokenStore);
3038
+ } catch (tokenRestoreErr) {
3039
+ tokenRollbackFailed = true;
3040
+ tokenRollbackMsg = `token rollback failed: ${tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr)}`;
3041
+ }
3042
+ let yamlRollbackMsg = "config.yaml restored";
3043
+ try {
3044
+ if (yamlResult.priorContent === null) {
3045
+ let unlinkSucceeded = false;
3046
+ let unlinkErr;
3047
+ try {
3048
+ fs8.unlinkSync(yamlResult.configPath);
3049
+ unlinkSucceeded = true;
3050
+ } catch (err) {
3051
+ unlinkErr = err;
3052
+ }
3053
+ if (unlinkSucceeded) {
3054
+ yamlRollbackMsg = "config.yaml removed (was newly created)";
3055
+ } else {
3056
+ const unlinkMsg = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
3057
+ yamlRollbackMsg = `config.yaml rollback failed: could not remove newly-created file \u2014 ${unlinkMsg}`;
3058
+ }
3059
+ } else if (typeof yamlResult.priorContent === "string") {
3060
+ writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
3061
+ yamlRollbackMsg = "config.yaml restored to prior content";
3062
+ }
3063
+ } catch (yamlRollbackErr) {
3064
+ yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;
3065
+ }
3066
+ const urgentSuffix = tokenRollbackFailed ? ` tokens.json may be in an inconsistent state \u2014 manually restore hermes token with 'remnic token generate hermes'.` : "";
3067
+ return {
3068
+ connectorId: options.connectorId,
3069
+ status: "error",
3070
+ message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
3071
+ };
3072
+ }
3073
+ const notes = [];
3074
+ notes.push(`Updated Hermes config: ${yamlResult.configPath}`);
3075
+ let oldProfileResolvesToDifferentFile = false;
3076
+ if (hermesSavedProfile !== void 0) {
3077
+ try {
3078
+ oldProfileResolvesToDifferentFile = hermesConfigPath(hermesSavedProfile) !== hermesConfigPath(hermesProfile);
3079
+ } catch {
3080
+ oldProfileResolvesToDifferentFile = false;
3081
+ }
3082
+ }
3083
+ if (oldProfileResolvesToDifferentFile) {
3084
+ try {
3085
+ const oldCleanResult = removeHermesConfig({ profile: hermesSavedProfile });
3086
+ if (oldCleanResult.updated) {
3087
+ notes.push(`Cleaned stale remnic: block from previous profile: ${oldCleanResult.configPath}`);
3088
+ }
3089
+ } catch {
3090
+ notes.push(`Note: could not clean stale remnic: block from previous profile "${hermesSavedProfile}"`);
3091
+ }
3092
+ }
3093
+ if (committed && tokenEntry) {
3094
+ const daemonOk = checkDaemonHealth(hermesHost, hermesPort, tokenEntry.token);
3095
+ if (daemonOk) {
3096
+ notes.push("Daemon health check: OK");
3097
+ } else {
3098
+ notes.push(
3099
+ `Daemon not reachable at ${hermesHost}:${hermesPort} \u2014 start with: remnic daemon start`
3100
+ );
3101
+ }
3102
+ }
3103
+ const suffix = notes.length > 0 ? `
3104
+ ${notes.join("\n ")}` : "";
2027
3105
  return {
2028
- connectorId,
3106
+ connectorId: options.connectorId,
3107
+ status: "installed",
2029
3108
  configPath,
2030
- message: "Not installed"
3109
+ message: `Installed ${manifest.name} v${manifest.version}${suffix}`
2031
3110
  };
2032
3111
  }
2033
- fs7.unlinkSync(configPath);
2034
- return {
2035
- connectorId,
2036
- configPath,
2037
- message: "Removed"
3112
+ let extensionMessage = "";
3113
+ let extensionInstalled = false;
3114
+ let extensionHandle = null;
3115
+ if (options.connectorId === "codex-cli") {
3116
+ const coerced = coerceInstallExtension(resolvedConfig.installExtension);
3117
+ if (coerced !== void 0) {
3118
+ resolvedConfig.installExtension = coerced;
3119
+ }
3120
+ const shouldInstall = resolvedConfig.installExtension !== false;
3121
+ resolvedConfig.installExtension = shouldInstall;
3122
+ const codexHomeOverride = typeof resolvedConfig.codexHome === "string" && resolvedConfig.codexHome.length > 0 ? resolvedConfig.codexHome : null;
3123
+ const resolvedCodexHome = resolveCodexHome(codexHomeOverride);
3124
+ resolvedConfig.codexHome = resolvedCodexHome;
3125
+ if (shouldInstall) {
3126
+ try {
3127
+ const extensionSourceOverride = typeof resolvedConfig.extensionSourceDir === "string" && resolvedConfig.extensionSourceDir.length > 0 ? resolvedConfig.extensionSourceDir : null;
3128
+ const extResult = installCodexMemoryExtension({
3129
+ codexHome: resolvedCodexHome,
3130
+ sourceDir: extensionSourceOverride
3131
+ });
3132
+ extensionMessage = ` (memory extension: ${extResult.remnicExtensionDir})`;
3133
+ extensionInstalled = true;
3134
+ extensionHandle = extResult;
3135
+ } catch (err) {
3136
+ const errMsg = err instanceof Error ? err.message : "unknown error";
3137
+ let extensionErrTokenRolledBack = false;
3138
+ let extensionErrTokenRollbackMsg = "";
3139
+ if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
3140
+ try {
3141
+ saveTokenStore(nonHermesPriorTokenStore);
3142
+ extensionErrTokenRolledBack = true;
3143
+ } catch (tokenRestoreErr) {
3144
+ extensionErrTokenRolledBack = false;
3145
+ extensionErrTokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
3146
+ }
3147
+ }
3148
+ const tokenRollbackSuffix = manifest.requiresToken ? extensionErrTokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${extensionErrTokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
3149
+ return {
3150
+ connectorId: options.connectorId,
3151
+ status: "error",
3152
+ message: `Memory extension install failed \u2014 ${errMsg}.${tokenRollbackSuffix} Resolve the issue, then reinstall.`
3153
+ };
3154
+ }
3155
+ } else {
3156
+ extensionMessage = " (memory extension: skipped via installExtension=false)";
3157
+ }
3158
+ }
3159
+ let weCloneProxyHandleRollback = null;
3160
+ if (options.connectorId === "weclone") {
3161
+ try {
3162
+ let proxyConfigPath = null;
3163
+ if (existing && fs8.existsSync(configPath)) {
3164
+ try {
3165
+ const savedRegistryConfig = JSON.parse(fs8.readFileSync(configPath, "utf8"));
3166
+ if (typeof savedRegistryConfig.proxyConfigPath === "string" && savedRegistryConfig.proxyConfigPath.length > 0) {
3167
+ proxyConfigPath = savedRegistryConfig.proxyConfigPath;
3168
+ }
3169
+ } catch {
3170
+ }
3171
+ }
3172
+ if (proxyConfigPath === null) {
3173
+ proxyConfigPath = resolveWeCloneProxyConfigPath();
3174
+ }
3175
+ const prior = readWeCloneProxyConfigIfExists(proxyConfigPath);
3176
+ const proxyConfig = buildWeCloneProxyConfig({
3177
+ userConfig: safeUserConfig,
3178
+ priorConfig: prior ? safeParseJson(prior) : null,
3179
+ authToken: tokenEntry?.token
3180
+ });
3181
+ fs8.mkdirSync(path13.dirname(proxyConfigPath), { recursive: true });
3182
+ weCloneProxyHandleRollback = () => {
3183
+ try {
3184
+ if (prior === null) {
3185
+ if (fs8.existsSync(proxyConfigPath)) {
3186
+ fs8.unlinkSync(proxyConfigPath);
3187
+ }
3188
+ } else {
3189
+ writeSecretFileSync(proxyConfigPath, prior);
3190
+ }
3191
+ } catch {
3192
+ }
3193
+ };
3194
+ try {
3195
+ writeSecretFileSync(
3196
+ proxyConfigPath,
3197
+ JSON.stringify(proxyConfig, null, 2)
3198
+ );
3199
+ } catch (writeErr) {
3200
+ try {
3201
+ weCloneProxyHandleRollback();
3202
+ } catch {
3203
+ }
3204
+ weCloneProxyHandleRollback = null;
3205
+ throw writeErr;
3206
+ }
3207
+ resolvedConfig.proxyConfigPath = proxyConfigPath;
3208
+ resolvedConfig.proxyPort = proxyConfig.proxyPort;
3209
+ resolvedConfig.wecloneApiUrl = proxyConfig.wecloneApiUrl;
3210
+ resolvedConfig.remnicDaemonUrl = proxyConfig.remnicDaemonUrl;
3211
+ resolvedConfig.sessionStrategy = proxyConfig.sessionStrategy;
3212
+ } catch (weCloneErr) {
3213
+ let tokenRolledBack = false;
3214
+ let tokenRollbackMsg = "";
3215
+ if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
3216
+ try {
3217
+ saveTokenStore(nonHermesPriorTokenStore);
3218
+ tokenRolledBack = true;
3219
+ } catch (tokenRestoreErr) {
3220
+ tokenRolledBack = false;
3221
+ tokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
3222
+ }
3223
+ }
3224
+ const tokenSuffix = manifest.requiresToken && tokenEntry !== null ? tokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${tokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
3225
+ return {
3226
+ connectorId: options.connectorId,
3227
+ status: "error",
3228
+ message: `WeClone install aborted: proxy config write failed \u2014 ${weCloneErr instanceof Error ? weCloneErr.message : String(weCloneErr)}.${tokenSuffix} Resolve the write permission issue on ~/.remnic/connectors/, then reinstall.`
3229
+ };
3230
+ }
3231
+ }
3232
+ const INTERNAL_KEYS_DENYLIST = [
3233
+ "extensionSourceDir"
3234
+ // test-only override for the plugin-codex source path
3235
+ ];
3236
+ for (const key of INTERNAL_KEYS_DENYLIST) {
3237
+ delete resolvedConfig[key];
3238
+ }
3239
+ try {
3240
+ writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
3241
+ } catch (writeErr) {
3242
+ let configWriteTokenRolledBack = false;
3243
+ let configWriteTokenRollbackMsg = "";
3244
+ if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {
3245
+ try {
3246
+ saveTokenStore(nonHermesPriorTokenStore);
3247
+ configWriteTokenRolledBack = true;
3248
+ } catch (tokenRestoreErr) {
3249
+ configWriteTokenRolledBack = false;
3250
+ configWriteTokenRollbackMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);
3251
+ }
3252
+ }
3253
+ if (extensionInstalled && extensionHandle !== null) {
3254
+ try {
3255
+ extensionHandle.rollback();
3256
+ } catch {
3257
+ console.warn(
3258
+ "[remnic/connectors] installConnector: config write failed and extension rollback also failed \u2014 manual cleanup of memories_extensions/remnic may be required."
3259
+ );
3260
+ }
3261
+ }
3262
+ if (weCloneProxyHandleRollback !== null) {
3263
+ try {
3264
+ weCloneProxyHandleRollback();
3265
+ } catch {
3266
+ }
3267
+ }
3268
+ const configWriteTokenSuffix = manifest.requiresToken && tokenEntry !== null ? configWriteTokenRolledBack ? " Token has been rolled back." : ` Token rollback FAILED (${configWriteTokenRollbackMsg}) \u2014 tokens.json may contain an orphaned entry. Manually inspect ~/.remnic/tokens.json and reinstall.` : "";
3269
+ return {
3270
+ connectorId: options.connectorId,
3271
+ status: "error",
3272
+ message: `${manifest.name} install aborted: connector config write failed \u2014 ${writeErr instanceof Error ? writeErr.message : String(writeErr)}.${configWriteTokenSuffix} Resolve the write permission issue, then reinstall.`
3273
+ };
3274
+ }
3275
+ if (extensionInstalled && extensionHandle !== null) {
3276
+ extensionHandle.commit();
3277
+ }
3278
+ return {
3279
+ connectorId: options.connectorId,
3280
+ status: "installed",
3281
+ configPath,
3282
+ message: `Installed ${manifest.name} v${manifest.version}${extensionMessage}`
3283
+ };
3284
+ }
3285
+ function removeConnector(connectorId) {
3286
+ const configDir = getConnectorsDir();
3287
+ const configPath = path13.join(configDir, `${connectorId}.json`);
3288
+ let codexHomeOverride = null;
3289
+ let savedInstallExtension = void 0;
3290
+ let configParsed = false;
3291
+ if (connectorId === "codex-cli" && fs8.existsSync(configPath)) {
3292
+ try {
3293
+ const parsed = JSON.parse(fs8.readFileSync(configPath, "utf8"));
3294
+ configParsed = true;
3295
+ if (typeof parsed.codexHome === "string" && parsed.codexHome.length > 0) {
3296
+ codexHomeOverride = parsed.codexHome;
3297
+ }
3298
+ const coerced = coerceInstallExtension(parsed.installExtension);
3299
+ if (coerced !== void 0) {
3300
+ savedInstallExtension = coerced;
3301
+ }
3302
+ } catch {
3303
+ console.debug(
3304
+ "[remnic/connectors] removeConnector: codex-cli.json parse failed \u2014 skipping extension removal to avoid touching unverified paths"
3305
+ );
3306
+ }
3307
+ }
3308
+ if (!fs8.existsSync(configPath)) {
3309
+ let staleTokenRevoked = false;
3310
+ try {
3311
+ staleTokenRevoked = revokeToken(connectorId);
3312
+ } catch {
3313
+ }
3314
+ const message = staleTokenRevoked ? `${connectorId} is not installed. Removed stale token entry for ${connectorId}.` : "Not installed";
3315
+ return {
3316
+ connectorId,
3317
+ configPath,
3318
+ status: "not_found",
3319
+ message
3320
+ };
3321
+ }
3322
+ let storedProfile = "default";
3323
+ if (connectorId === "hermes") {
3324
+ try {
3325
+ const stored = JSON.parse(fs8.readFileSync(configPath, "utf8"));
3326
+ if (typeof stored?.profile === "string") storedProfile = stored.profile;
3327
+ } catch {
3328
+ }
3329
+ }
3330
+ let weCloneProxyConfigPath = null;
3331
+ let weCloneRegistryParseFailed = false;
3332
+ if (connectorId === "weclone") {
3333
+ try {
3334
+ const stored = JSON.parse(fs8.readFileSync(configPath, "utf8"));
3335
+ if (typeof stored.proxyConfigPath === "string" && stored.proxyConfigPath.length > 0) {
3336
+ weCloneProxyConfigPath = stored.proxyConfigPath;
3337
+ }
3338
+ } catch {
3339
+ weCloneRegistryParseFailed = true;
3340
+ }
3341
+ if (weCloneProxyConfigPath === null && !weCloneRegistryParseFailed) {
3342
+ try {
3343
+ weCloneProxyConfigPath = resolveWeCloneProxyConfigPath();
3344
+ } catch {
3345
+ }
3346
+ }
3347
+ }
3348
+ if (connectorId === "weclone" && weCloneRegistryParseFailed) {
3349
+ console.warn(
3350
+ "[remnic/connectors] removeConnector: weclone.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
3351
+ );
3352
+ return {
3353
+ connectorId,
3354
+ configPath,
3355
+ message: "Removal aborted: weclone.json is malformed. Registry config left in place for inspection; proxy config NOT removed.",
3356
+ status: "skipped",
3357
+ reason: "config-parse-failed"
3358
+ };
3359
+ }
3360
+ if (connectorId === "codex-cli" && fs8.existsSync(configPath) && !configParsed) {
3361
+ console.warn(
3362
+ "[remnic/connectors] removeConnector: codex-cli.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
3363
+ );
3364
+ return {
3365
+ connectorId,
3366
+ configPath,
3367
+ message: "Removal aborted: codex-cli.json is malformed. Config file left in place for inspection.",
3368
+ status: "skipped",
3369
+ reason: "config-parse-failed"
3370
+ };
3371
+ }
3372
+ let extensionMessage = "";
3373
+ if (connectorId === "codex-cli") {
3374
+ if (savedInstallExtension === false) {
3375
+ extensionMessage = " (memory extension: skipped \u2014 installExtension=false)";
3376
+ } else if (savedInstallExtension !== true || codexHomeOverride === null) {
3377
+ extensionMessage = " (memory extension: skipped \u2014 no install provenance in saved config)";
3378
+ } else {
3379
+ const extResult = removeCodexMemoryExtension({ codexHome: codexHomeOverride });
3380
+ extensionMessage = extResult.removed ? ` (memory extension removed: ${extResult.remnicExtensionDir})` : " (no memory extension present)";
3381
+ }
3382
+ }
3383
+ try {
3384
+ fs8.unlinkSync(configPath);
3385
+ } catch (unlinkErr) {
3386
+ const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
3387
+ return {
3388
+ connectorId,
3389
+ configPath,
3390
+ status: "error",
3391
+ message: `${connectorId} remove aborted: could not delete connector file (${sanitizedErr}). Token and any connector-specific state were not modified.`
3392
+ };
3393
+ }
3394
+ const notes = [];
3395
+ let tokenRevoked = true;
3396
+ try {
3397
+ revokeToken(connectorId);
3398
+ } catch (revokeErr) {
3399
+ tokenRevoked = false;
3400
+ const revokeMsg = revokeErr instanceof Error ? revokeErr.message : String(revokeErr);
3401
+ notes.push(`Warning: token revocation failed \u2014 ${revokeMsg}. The token for ${connectorId} may still be present in tokens.json.`);
3402
+ }
3403
+ let weCloneProxyDeleteFailed = null;
3404
+ if (connectorId === "weclone") {
3405
+ if (weCloneProxyConfigPath === null) {
3406
+ notes.push(
3407
+ "WeClone proxy config cleanup skipped: no persisted path found in saved config (likely a legacy install predating proxyConfigPath provenance)."
3408
+ );
3409
+ } else {
3410
+ const expectedSuffix = path13.join("connectors", "weclone.json");
3411
+ const isSafePath = path13.isAbsolute(weCloneProxyConfigPath) && weCloneProxyConfigPath.endsWith(expectedSuffix);
3412
+ if (!isSafePath) {
3413
+ weCloneProxyDeleteFailed = `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation (must be absolute and end with "${expectedSuffix}"). Refusing to delete \u2014 remove the file manually if it exists.`;
3414
+ } else {
3415
+ try {
3416
+ if (fs8.existsSync(weCloneProxyConfigPath)) {
3417
+ fs8.unlinkSync(weCloneProxyConfigPath);
3418
+ notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);
3419
+ }
3420
+ } catch (err) {
3421
+ weCloneProxyDeleteFailed = err instanceof Error ? err.message : String(err);
3422
+ }
3423
+ }
3424
+ }
3425
+ }
3426
+ if (weCloneProxyDeleteFailed !== null && weCloneProxyConfigPath !== null) {
3427
+ const tokenStatus = tokenRevoked ? "the registry config was deleted and the token was revoked" : "the registry config was deleted but TOKEN REVOCATION ALSO FAILED \u2014 inspect ~/.remnic/tokens.json and revoke manually";
3428
+ return {
3429
+ connectorId,
3430
+ configPath,
3431
+ status: "error",
3432
+ message: `WeClone remove partially succeeded: ${tokenStatus}, but the proxy config at ${weCloneProxyConfigPath} could not be deleted (${weCloneProxyDeleteFailed}). Manually remove that file \u2014 it may still contain a Remnic daemon bearer token.`
3433
+ };
3434
+ }
3435
+ if (connectorId === "hermes") {
3436
+ try {
3437
+ const yamlResult = removeHermesConfig({ profile: storedProfile });
3438
+ if (yamlResult.updated) {
3439
+ notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);
3440
+ } else if (yamlResult.skipped) {
3441
+ notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);
3442
+ }
3443
+ } catch (err) {
3444
+ notes.push(
3445
+ `Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`
3446
+ );
3447
+ }
3448
+ }
3449
+ const suffix = notes.length > 0 ? `
3450
+ ${notes.join("\n ")}` : "";
3451
+ return {
3452
+ connectorId,
3453
+ configPath,
3454
+ status: "removed",
3455
+ message: `Removed${extensionMessage}${suffix}`
2038
3456
  };
2039
3457
  }
3458
+ function sanitizeHermesProfile(profile) {
3459
+ if (typeof profile !== "string" || profile.length === 0) {
3460
+ throw new Error("Hermes profile name must be a non-empty string");
3461
+ }
3462
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {
3463
+ throw new Error(
3464
+ `Invalid Hermes profile name: ${JSON.stringify(profile)} \u2014 must match [A-Za-z0-9][A-Za-z0-9._-]*`
3465
+ );
3466
+ }
3467
+ if (profile.includes("..")) {
3468
+ throw new Error(`Invalid Hermes profile name: ${JSON.stringify(profile)} \u2014 must not contain ".."`);
3469
+ }
3470
+ return profile;
3471
+ }
3472
+ function hermesConfigPath(profile) {
3473
+ const safeProfile = sanitizeHermesProfile(profile);
3474
+ const profilesRoot = path13.resolve(process.env.HOME ?? os.homedir(), ".hermes", "profiles");
3475
+ const cfgPath = path13.resolve(profilesRoot, safeProfile, "config.yaml");
3476
+ const rel = path13.relative(profilesRoot, cfgPath);
3477
+ if (rel.startsWith("..") || path13.isAbsolute(rel)) {
3478
+ throw new Error(
3479
+ `Invalid Hermes profile path: resolved outside ${profilesRoot}`
3480
+ );
3481
+ }
3482
+ return cfgPath;
3483
+ }
3484
+ function sanitizeHermesHost(host) {
3485
+ if (typeof host !== "string" || host.length === 0) {
3486
+ throw new Error("Hermes host must be a non-empty string");
3487
+ }
3488
+ if (host.length > 253) {
3489
+ throw new Error(`Hermes host too long (max 253 chars): ${JSON.stringify(host.slice(0, 32))}\u2026`);
3490
+ }
3491
+ if (host.startsWith("[")) {
3492
+ if (!host.endsWith("]")) {
3493
+ throw new Error(
3494
+ `Invalid Hermes host: ${JSON.stringify(host)} \u2014 unbalanced brackets in IPv6 literal`
3495
+ );
3496
+ }
3497
+ const inner = host.slice(1, -1);
3498
+ if (inner.length === 0 || !/^[0-9A-Fa-f:]+$/.test(inner)) {
3499
+ throw new Error(
3500
+ `Invalid Hermes host: ${JSON.stringify(host)} \u2014 bracketed IPv6 literal must contain only hex digits and colons`
3501
+ );
3502
+ }
3503
+ return host;
3504
+ }
3505
+ if (host.includes(":")) {
3506
+ throw new Error(
3507
+ `Invalid Hermes host: ${JSON.stringify(host)} \u2014 host must not include a port; supply the port separately with --config port=<n>`
3508
+ );
3509
+ }
3510
+ if (!/^[A-Za-z0-9._\-]+$/.test(host)) {
3511
+ throw new Error(
3512
+ `Invalid Hermes host: ${JSON.stringify(host)} \u2014 must be a plain hostname or IP literal`
3513
+ );
3514
+ }
3515
+ return host;
3516
+ }
3517
+ function sanitizeHermesPort(port) {
3518
+ const numeric = Number(port);
3519
+ if (!Number.isInteger(numeric)) {
3520
+ throw new Error(
3521
+ `Invalid Hermes port "${port}": must be a positive integer`
3522
+ );
3523
+ }
3524
+ if (numeric < 1 || numeric > 65535) {
3525
+ throw new Error(`Invalid Hermes port: ${JSON.stringify(port)} \u2014 must be an integer in [1, 65535]`);
3526
+ }
3527
+ return numeric;
3528
+ }
3529
+ function writeSecretFileSync(filePath, data) {
3530
+ fs8.writeFileSync(filePath, data, { mode: 384 });
3531
+ try {
3532
+ fs8.chmodSync(filePath, 384);
3533
+ } catch {
3534
+ }
3535
+ }
3536
+ function upsertHermesConfig(opts) {
3537
+ const cfgPath = hermesConfigPath(opts.profile);
3538
+ const profileDir = path13.dirname(cfgPath);
3539
+ const safeHost = sanitizeHermesHost(opts.host);
3540
+ const safePort = sanitizeHermesPort(opts.port);
3541
+ if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {
3542
+ throw new Error("Invalid Hermes token: contains non-alphanumeric characters");
3543
+ }
3544
+ if (!fs8.existsSync(profileDir)) {
3545
+ return {
3546
+ updated: false,
3547
+ skipped: true,
3548
+ reason: `Hermes profile directory not found: ${profileDir}`,
3549
+ configPath: cfgPath
3550
+ };
3551
+ }
3552
+ const block = [
3553
+ "remnic:",
3554
+ ` host: "${safeHost}"`,
3555
+ ` port: ${safePort}`,
3556
+ ` token: "${opts.token}"`
3557
+ ].join("\n");
3558
+ if (!fs8.existsSync(cfgPath)) {
3559
+ writeSecretFileSync(cfgPath, block + "\n");
3560
+ return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };
3561
+ }
3562
+ const raw = fs8.readFileSync(cfgPath, "utf8");
3563
+ const hasRemnicBlock = /^remnic:/m.test(raw);
3564
+ if (!hasRemnicBlock) {
3565
+ const separator = raw.endsWith("\n") ? "\n" : "\n\n";
3566
+ writeSecretFileSync(cfgPath, raw + separator + block + "\n");
3567
+ return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
3568
+ }
3569
+ const splitLines = raw.split("\n");
3570
+ if (splitLines.length > 0 && splitLines[splitLines.length - 1] === "") {
3571
+ splitLines.pop();
3572
+ }
3573
+ const lines = splitLines;
3574
+ const newLines = [];
3575
+ let inRemnicBlock = false;
3576
+ let blockWritten = false;
3577
+ const written = { host: false, port: false, token: false };
3578
+ for (let i = 0; i < lines.length; i++) {
3579
+ const line = lines[i];
3580
+ if (/^remnic:/.test(line)) {
3581
+ inRemnicBlock = true;
3582
+ newLines.push(line);
3583
+ continue;
3584
+ }
3585
+ if (inRemnicBlock) {
3586
+ if (line.length > 0 && !/^\s/.test(line)) {
3587
+ if (!written.host) newLines.push(` host: "${safeHost}"`);
3588
+ if (!written.port) newLines.push(` port: ${safePort}`);
3589
+ if (!written.token) newLines.push(` token: "${opts.token}"`);
3590
+ blockWritten = true;
3591
+ inRemnicBlock = false;
3592
+ newLines.push(line);
3593
+ continue;
3594
+ }
3595
+ if (/^\s+host:/.test(line)) {
3596
+ newLines.push(` host: "${safeHost}"`);
3597
+ written.host = true;
3598
+ } else if (/^\s+port:/.test(line)) {
3599
+ newLines.push(` port: ${safePort}`);
3600
+ written.port = true;
3601
+ } else if (/^\s+token:/.test(line)) {
3602
+ newLines.push(` token: "${opts.token}"`);
3603
+ written.token = true;
3604
+ } else {
3605
+ newLines.push(line);
3606
+ }
3607
+ continue;
3608
+ }
3609
+ newLines.push(line);
3610
+ }
3611
+ if (inRemnicBlock && !blockWritten) {
3612
+ if (!written.host) newLines.push(` host: "${safeHost}"`);
3613
+ if (!written.port) newLines.push(` port: ${safePort}`);
3614
+ if (!written.token) newLines.push(` token: "${opts.token}"`);
3615
+ }
3616
+ writeSecretFileSync(cfgPath, newLines.join("\n") + "\n");
3617
+ return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
3618
+ }
3619
+ function removeHermesConfig(opts) {
3620
+ const cfgPath = hermesConfigPath(opts.profile);
3621
+ if (!fs8.existsSync(cfgPath)) {
3622
+ return {
3623
+ updated: false,
3624
+ skipped: true,
3625
+ reason: "Hermes config.yaml not found",
3626
+ configPath: cfgPath
3627
+ };
3628
+ }
3629
+ const raw = fs8.readFileSync(cfgPath, "utf8");
3630
+ if (!/^remnic:/m.test(raw)) {
3631
+ return {
3632
+ updated: false,
3633
+ skipped: true,
3634
+ reason: "No remnic: block found in config.yaml",
3635
+ configPath: cfgPath
3636
+ };
3637
+ }
3638
+ const lines = raw.split("\n");
3639
+ const newLines = [];
3640
+ let inRemnicBlock = false;
3641
+ for (const line of lines) {
3642
+ if (/^remnic:/.test(line)) {
3643
+ inRemnicBlock = true;
3644
+ continue;
3645
+ }
3646
+ if (inRemnicBlock) {
3647
+ if (line.length > 0 && !/^\s/.test(line)) {
3648
+ inRemnicBlock = false;
3649
+ newLines.push(line);
3650
+ }
3651
+ continue;
3652
+ }
3653
+ newLines.push(line);
3654
+ }
3655
+ while (newLines.length > 0 && newLines[newLines.length - 1]?.trim() === "") {
3656
+ newLines.pop();
3657
+ }
3658
+ writeSecretFileSync(cfgPath, newLines.length > 0 ? newLines.join("\n") + "\n" : "");
3659
+ return { updated: true, skipped: false, configPath: cfgPath };
3660
+ }
3661
+ var HEALTH_EXIT_OK = 0;
3662
+ var HEALTH_EXIT_UNAUTHORIZED = 2;
3663
+ function checkDaemonHealth(host, port, authToken) {
3664
+ try {
3665
+ const safePort = Math.trunc(Number(port));
3666
+ if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) {
3667
+ return false;
3668
+ }
3669
+ const bareHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
3670
+ const script = [
3671
+ "const http = require('http');",
3672
+ "const headers = {};",
3673
+ "if (process.env.REMNIC_HEALTH_TOKEN) {",
3674
+ " headers['authorization'] = 'Bearer ' + process.env.REMNIC_HEALTH_TOKEN;",
3675
+ "}",
3676
+ "const req = http.get({",
3677
+ " host: process.env.REMNIC_HEALTH_HOST,",
3678
+ " port: parseInt(process.env.REMNIC_HEALTH_PORT, 10),",
3679
+ " path: '/engram/v1/health',",
3680
+ " headers,",
3681
+ " timeout: 3000,",
3682
+ "}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });",
3683
+ "req.on('error', () => process.exit(1));",
3684
+ "req.on('timeout', () => { req.destroy(); process.exit(1); });"
3685
+ ].join("\n");
3686
+ const env = {
3687
+ ...process.env,
3688
+ REMNIC_HEALTH_HOST: bareHost,
3689
+ REMNIC_HEALTH_PORT: String(safePort)
3690
+ };
3691
+ if (authToken) {
3692
+ env.REMNIC_HEALTH_TOKEN = authToken;
3693
+ }
3694
+ const spawnOpts = { timeout: 4e3, env };
3695
+ const result = spawnSync(process.execPath, ["-e", script], spawnOpts);
3696
+ if (result.status === HEALTH_EXIT_OK) {
3697
+ return true;
3698
+ }
3699
+ if (result.status === HEALTH_EXIT_UNAUTHORIZED) {
3700
+ console.error(
3701
+ "[remnic/connectors] health probe got 401 \u2014 retrying after token cache TTL..."
3702
+ );
3703
+ spawnSync(process.execPath, ["-e", "setTimeout(() => {}, 6000)"], {
3704
+ timeout: 7e3,
3705
+ env: {}
3706
+ });
3707
+ const retry = spawnSync(process.execPath, ["-e", script], spawnOpts);
3708
+ return retry.status === HEALTH_EXIT_OK;
3709
+ }
3710
+ return false;
3711
+ } catch {
3712
+ return false;
3713
+ }
3714
+ }
2040
3715
  async function doctorConnector(connectorId) {
2041
3716
  const installed = listConnectors().installed;
2042
3717
  const instance = installed.find((c) => c.connectorId === connectorId);
@@ -2047,15 +3722,15 @@ async function doctorConnector(connectorId) {
2047
3722
  healthy: false
2048
3723
  };
2049
3724
  }
2050
- const configPath = path8.join(getConnectorsDir(), `${connectorId}.json`);
3725
+ const configPath = path13.join(getConnectorsDir(), `${connectorId}.json`);
2051
3726
  const checks = [];
2052
3727
  checks.push({
2053
3728
  name: "Config file",
2054
- ok: fs7.existsSync(configPath),
3729
+ ok: fs8.existsSync(configPath),
2055
3730
  detail: configPath
2056
3731
  });
2057
3732
  try {
2058
- const raw = fs7.readFileSync(configPath, "utf8");
3733
+ const raw = fs8.readFileSync(configPath, "utf8");
2059
3734
  JSON.parse(raw);
2060
3735
  checks.push({ name: "Config valid", ok: true, detail: "OK" });
2061
3736
  } catch (e) {
@@ -2077,37 +3752,398 @@ async function doctorConnector(connectorId) {
2077
3752
  });
2078
3753
  }
2079
3754
  }
2080
- const memoryDir = instance.config.memoryDir;
2081
- if (memoryDir) {
2082
- if (fs7.existsSync(memoryDir)) {
2083
- checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
2084
- } else {
2085
- checks.push({ name: "Memory directory", ok: false, detail: `Not found: ${memoryDir}` });
3755
+ const memoryDir = instance.config.memoryDir;
3756
+ if (memoryDir) {
3757
+ if (fs8.existsSync(memoryDir)) {
3758
+ checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
3759
+ } else {
3760
+ checks.push({ name: "Memory directory", ok: false, detail: `Not found: ${memoryDir}` });
3761
+ }
3762
+ }
3763
+ const healthy = checks.every((c) => c.ok);
3764
+ return { connectorId, checks, healthy };
3765
+ }
3766
+ var CODEX_MEMORIES_SUBDIR = "memories";
3767
+ var CODEX_EXTENSIONS_SUBDIR = "memories_extensions";
3768
+ var REMNIC_EXTENSION_DIR_NAME = "remnic";
3769
+ function resolveCodexHome(override) {
3770
+ if (override && typeof override === "string" && override.trim().length > 0) {
3771
+ return path13.resolve(override.trim());
3772
+ }
3773
+ const envHome = process.env.CODEX_HOME;
3774
+ if (envHome && envHome.trim().length > 0) {
3775
+ return path13.resolve(envHome.trim());
3776
+ }
3777
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "~";
3778
+ return path13.resolve(home, ".codex");
3779
+ }
3780
+ function resolveCodexMemoryExtensionPaths(codexHomeOverride) {
3781
+ const codexHome = resolveCodexHome(codexHomeOverride);
3782
+ const memoriesDir = path13.join(codexHome, CODEX_MEMORIES_SUBDIR);
3783
+ const extensionsRoot = path13.join(path13.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);
3784
+ const remnicExtensionDir = path13.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);
3785
+ return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };
3786
+ }
3787
+ function locatePluginCodexExtensionSource(override) {
3788
+ if (override && typeof override === "string" && override.trim().length > 0) {
3789
+ const resolved = path13.resolve(override.trim());
3790
+ if (fs8.existsSync(resolved) && fs8.statSync(resolved).isDirectory()) {
3791
+ return resolved;
3792
+ }
3793
+ throw new Error(`Codex extension source directory not found: ${resolved}`);
3794
+ }
3795
+ const EXTENSION_SUBPATH = path13.join("memories_extensions", "remnic");
3796
+ const WORKSPACE_RELATIVE_PATH = path13.join(
3797
+ "packages",
3798
+ "plugin-codex",
3799
+ "memories_extensions",
3800
+ "remnic"
3801
+ );
3802
+ const searched = [];
3803
+ try {
3804
+ const moduleDir = path13.dirname(fileURLToPath(import.meta.url));
3805
+ const bundledCandidate = path13.join(moduleDir, "codex");
3806
+ searched.push(bundledCandidate);
3807
+ if (fs8.existsSync(bundledCandidate) && fs8.statSync(bundledCandidate).isDirectory()) {
3808
+ return bundledCandidate;
3809
+ }
3810
+ const distConnectorsCandidate = path13.join(moduleDir, "connectors", "codex");
3811
+ searched.push(distConnectorsCandidate);
3812
+ if (fs8.existsSync(distConnectorsCandidate) && fs8.statSync(distConnectorsCandidate).isDirectory()) {
3813
+ return distConnectorsCandidate;
3814
+ }
3815
+ } catch {
3816
+ }
3817
+ try {
3818
+ const requireFromHere = createRequire(import.meta.url);
3819
+ const pluginPkgJsonPath = requireFromHere.resolve("@remnic/plugin-codex/package.json");
3820
+ const pluginPkgRoot = path13.dirname(pluginPkgJsonPath);
3821
+ const candidate = path13.join(pluginPkgRoot, EXTENSION_SUBPATH);
3822
+ searched.push(candidate);
3823
+ if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
3824
+ return candidate;
3825
+ }
3826
+ } catch {
3827
+ }
3828
+ try {
3829
+ const moduleDir = path13.dirname(fileURLToPath(import.meta.url));
3830
+ let dir = moduleDir;
3831
+ for (let depth = 0; depth < 8; depth += 1) {
3832
+ const candidate = path13.join(
3833
+ dir,
3834
+ "node_modules",
3835
+ "@remnic",
3836
+ "plugin-codex",
3837
+ EXTENSION_SUBPATH
3838
+ );
3839
+ searched.push(candidate);
3840
+ if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
3841
+ return candidate;
3842
+ }
3843
+ const parent = path13.dirname(dir);
3844
+ if (parent === dir) break;
3845
+ dir = parent;
3846
+ }
3847
+ } catch {
3848
+ }
3849
+ const anchors = [];
3850
+ try {
3851
+ anchors.push(path13.dirname(fileURLToPath(import.meta.url)));
3852
+ } catch {
3853
+ }
3854
+ anchors.push(process.cwd());
3855
+ for (const anchor of anchors) {
3856
+ let dir = anchor;
3857
+ for (let depth = 0; depth < 12; depth += 1) {
3858
+ const candidate = path13.join(dir, WORKSPACE_RELATIVE_PATH);
3859
+ searched.push(candidate);
3860
+ if (fs8.existsSync(candidate) && fs8.statSync(candidate).isDirectory()) {
3861
+ return candidate;
3862
+ }
3863
+ const parent = path13.dirname(dir);
3864
+ if (parent === dir) break;
3865
+ dir = parent;
3866
+ }
3867
+ }
3868
+ throw new Error(
3869
+ "Could not locate the plugin-codex memories_extensions/remnic source directory.\nPaths searched:\n" + searched.map((p) => ` - ${p}`).join("\n") + "\nInstall @remnic/plugin-codex or pass sourceDir explicitly."
3870
+ );
3871
+ }
3872
+ function copyDirRecursiveSync(src, dest) {
3873
+ let count = 0;
3874
+ fs8.mkdirSync(dest, { recursive: true });
3875
+ const entries = fs8.readdirSync(src, { withFileTypes: true });
3876
+ for (const entry of entries) {
3877
+ const from = path13.join(src, entry.name);
3878
+ const to = path13.join(dest, entry.name);
3879
+ if (entry.isDirectory()) {
3880
+ count += copyDirRecursiveSync(from, to);
3881
+ } else if (entry.isFile()) {
3882
+ fs8.copyFileSync(from, to);
3883
+ count += 1;
3884
+ }
3885
+ }
3886
+ return count;
3887
+ }
3888
+ function installCodexMemoryExtension(options = {}) {
3889
+ const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
3890
+ const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);
3891
+ fs8.mkdirSync(paths.extensionsRoot, { recursive: true });
3892
+ const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;
3893
+ const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1e3;
3894
+ const now = Date.now();
3895
+ try {
3896
+ const existingEntries = fs8.readdirSync(paths.extensionsRoot);
3897
+ for (const entry of existingEntries) {
3898
+ if (!entry.startsWith(tmpPrefix)) continue;
3899
+ const stalePath = path13.join(paths.extensionsRoot, entry);
3900
+ try {
3901
+ const stat = fs8.statSync(stalePath);
3902
+ const ageMs = now - stat.mtimeMs;
3903
+ if (ageMs < STALE_TMP_THRESHOLD_MS) {
3904
+ continue;
3905
+ }
3906
+ fs8.rmSync(stalePath, { recursive: true, force: true });
3907
+ } catch {
3908
+ }
3909
+ }
3910
+ } catch {
3911
+ }
3912
+ const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;
3913
+ const tmpDir = path13.join(paths.extensionsRoot, tmpName);
3914
+ let filesCopied = 0;
3915
+ let commitFn = () => {
3916
+ };
3917
+ let rollbackFn = () => {
3918
+ };
3919
+ try {
3920
+ filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);
3921
+ const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;
3922
+ const hadExisting = fs8.existsSync(paths.remnicExtensionDir);
3923
+ if (hadExisting) {
3924
+ fs8.renameSync(paths.remnicExtensionDir, backupDir);
3925
+ }
3926
+ try {
3927
+ fs8.renameSync(tmpDir, paths.remnicExtensionDir);
3928
+ } catch (renameErr) {
3929
+ if (hadExisting) {
3930
+ try {
3931
+ fs8.renameSync(backupDir, paths.remnicExtensionDir);
3932
+ } catch {
3933
+ }
3934
+ }
3935
+ throw renameErr;
3936
+ }
3937
+ commitFn = () => {
3938
+ if (hadExisting) {
3939
+ try {
3940
+ fs8.rmSync(backupDir, { recursive: true, force: true });
3941
+ } catch {
3942
+ }
3943
+ }
3944
+ };
3945
+ rollbackFn = () => {
3946
+ if (hadExisting) {
3947
+ try {
3948
+ if (fs8.existsSync(paths.remnicExtensionDir)) {
3949
+ fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
3950
+ }
3951
+ fs8.renameSync(backupDir, paths.remnicExtensionDir);
3952
+ } catch {
3953
+ }
3954
+ } else {
3955
+ try {
3956
+ if (fs8.existsSync(paths.remnicExtensionDir)) {
3957
+ fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
3958
+ }
3959
+ } catch {
3960
+ }
3961
+ }
3962
+ };
3963
+ } catch (err) {
3964
+ if (fs8.existsSync(tmpDir)) {
3965
+ try {
3966
+ fs8.rmSync(tmpDir, { recursive: true, force: true });
3967
+ } catch {
3968
+ }
3969
+ }
3970
+ throw err;
3971
+ }
3972
+ const instructionsPath = path13.join(paths.remnicExtensionDir, "instructions.md");
3973
+ return {
3974
+ ...paths,
3975
+ instructionsPath,
3976
+ filesCopied,
3977
+ commit: commitFn,
3978
+ rollback: rollbackFn
3979
+ };
3980
+ }
3981
+ function removeCodexMemoryExtension(options = {}) {
3982
+ const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
3983
+ let removed = false;
3984
+ if (fs8.existsSync(paths.remnicExtensionDir)) {
3985
+ fs8.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
3986
+ removed = true;
3987
+ }
3988
+ return { ...paths, removed };
3989
+ }
3990
+ function getConnectorsDir() {
3991
+ const configDir = process.env.XDG_CONFIG_HOME ? path13.join(process.env.XDG_CONFIG_HOME, "engram") : path13.join(process.env.HOME ?? "~", ".config", "engram");
3992
+ return path13.join(configDir, REGISTRY_DIR_NAME, "connectors");
3993
+ }
3994
+ var WECLONE_PROXY_CONFIG_DIRNAME = ".remnic";
3995
+ var WECLONE_PROXY_CONFIG_FILENAME = "weclone.json";
3996
+ function resolveWeCloneProxyConfigPath() {
3997
+ const override = process.env.REMNIC_HOME ?? process.env.ENGRAM_HOME;
3998
+ if (override && override.length > 0) {
3999
+ return path13.resolve(override, "connectors", WECLONE_PROXY_CONFIG_FILENAME);
4000
+ }
4001
+ const envHome = process.env.HOME;
4002
+ const home = envHome && envHome.length > 0 ? envHome : os.homedir();
4003
+ return path13.resolve(
4004
+ home,
4005
+ WECLONE_PROXY_CONFIG_DIRNAME,
4006
+ "connectors",
4007
+ WECLONE_PROXY_CONFIG_FILENAME
4008
+ );
4009
+ }
4010
+ function readWeCloneProxyConfigIfExists(configPath) {
4011
+ try {
4012
+ if (!fs8.existsSync(configPath)) return null;
4013
+ return fs8.readFileSync(configPath, "utf8");
4014
+ } catch {
4015
+ return null;
4016
+ }
4017
+ }
4018
+ function safeParseJson(raw) {
4019
+ try {
4020
+ const parsed = JSON.parse(raw);
4021
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
4022
+ return parsed;
4023
+ }
4024
+ return null;
4025
+ } catch {
4026
+ return null;
4027
+ }
4028
+ }
4029
+ var WECLONE_DEFAULTS = {
4030
+ wecloneApiUrl: "http://localhost:8000/v1",
4031
+ wecloneModelName: "weclone-avatar",
4032
+ proxyPort: 8100,
4033
+ remnicDaemonUrl: "http://localhost:4318",
4034
+ sessionStrategy: "single",
4035
+ memoryInjection: {
4036
+ maxTokens: 1500,
4037
+ position: "system-append",
4038
+ template: "[Memory Context]\n{memories}\n[End Memory Context]"
4039
+ }
4040
+ };
4041
+ function resolveStringField(userConfig, priorConfig, key, fallback) {
4042
+ const fromUser = userConfig[key];
4043
+ if (typeof fromUser === "string" && fromUser.length > 0) return fromUser;
4044
+ if (priorConfig) {
4045
+ const fromPrior = priorConfig[key];
4046
+ if (typeof fromPrior === "string" && fromPrior.length > 0) return fromPrior;
4047
+ }
4048
+ return fallback;
4049
+ }
4050
+ function coercePort(value) {
4051
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535) {
4052
+ return value;
4053
+ }
4054
+ if (typeof value === "string" && value.length > 0) {
4055
+ const n = Number(value);
4056
+ if (Number.isInteger(n) && n >= 1 && n <= 65535) return n;
4057
+ }
4058
+ return null;
4059
+ }
4060
+ function resolvePort(userConfig, priorConfig, fallback) {
4061
+ const fromUser = coercePort(userConfig.proxyPort);
4062
+ if (fromUser !== null) return fromUser;
4063
+ if (priorConfig) {
4064
+ const fromPrior = coercePort(priorConfig.proxyPort);
4065
+ if (fromPrior !== null) return fromPrior;
4066
+ }
4067
+ return fallback;
4068
+ }
4069
+ function resolveSessionStrategy(userConfig, priorConfig) {
4070
+ const valid = /* @__PURE__ */ new Set(["caller-id", "single"]);
4071
+ const fromUser = userConfig.sessionStrategy;
4072
+ if (typeof fromUser === "string" && valid.has(fromUser)) {
4073
+ return fromUser;
4074
+ }
4075
+ if (priorConfig) {
4076
+ const fromPrior = priorConfig.sessionStrategy;
4077
+ if (typeof fromPrior === "string" && valid.has(fromPrior)) {
4078
+ return fromPrior;
2086
4079
  }
2087
4080
  }
2088
- const healthy = checks.every((c) => c.ok);
2089
- return { connectorId, checks, healthy };
4081
+ return WECLONE_DEFAULTS.sessionStrategy;
2090
4082
  }
2091
- function getConnectorsDir() {
2092
- const configDir = process.env.XDG_CONFIG_HOME ? path8.join(process.env.XDG_CONFIG_HOME, "engram") : path8.join(process.env.HOME ?? "~", ".config", "engram");
2093
- return path8.join(configDir, REGISTRY_DIR_NAME, "connectors");
4083
+ function buildWeCloneProxyConfig(args) {
4084
+ const { userConfig, priorConfig, authToken } = args;
4085
+ const wecloneApiUrl = resolveStringField(
4086
+ userConfig,
4087
+ priorConfig,
4088
+ "wecloneApiUrl",
4089
+ WECLONE_DEFAULTS.wecloneApiUrl
4090
+ );
4091
+ const wecloneModelName = resolveStringField(
4092
+ userConfig,
4093
+ priorConfig,
4094
+ "wecloneModelName",
4095
+ WECLONE_DEFAULTS.wecloneModelName
4096
+ );
4097
+ const remnicDaemonUrl = resolveStringField(
4098
+ userConfig,
4099
+ priorConfig,
4100
+ "remnicDaemonUrl",
4101
+ WECLONE_DEFAULTS.remnicDaemonUrl
4102
+ );
4103
+ const proxyPort = resolvePort(
4104
+ userConfig,
4105
+ priorConfig,
4106
+ WECLONE_DEFAULTS.proxyPort
4107
+ );
4108
+ const sessionStrategy = resolveSessionStrategy(userConfig, priorConfig);
4109
+ const memoryInjection = {
4110
+ ...WECLONE_DEFAULTS.memoryInjection,
4111
+ ...priorConfig && typeof priorConfig.memoryInjection === "object" && priorConfig.memoryInjection !== null && !Array.isArray(priorConfig.memoryInjection) ? priorConfig.memoryInjection : {},
4112
+ ...typeof userConfig.memoryInjection === "object" && userConfig.memoryInjection !== null && !Array.isArray(userConfig.memoryInjection) ? userConfig.memoryInjection : {}
4113
+ };
4114
+ const config = {
4115
+ wecloneApiUrl,
4116
+ wecloneModelName,
4117
+ proxyPort,
4118
+ remnicDaemonUrl,
4119
+ sessionStrategy,
4120
+ memoryInjection
4121
+ };
4122
+ if (authToken && authToken.length > 0) {
4123
+ config.remnicAuthToken = authToken;
4124
+ } else if (typeof userConfig.remnicAuthToken === "string" && userConfig.remnicAuthToken.length > 0) {
4125
+ config.remnicAuthToken = userConfig.remnicAuthToken;
4126
+ } else if (priorConfig && typeof priorConfig.remnicAuthToken === "string" && priorConfig.remnicAuthToken.length > 0) {
4127
+ config.remnicAuthToken = priorConfig.remnicAuthToken;
4128
+ }
4129
+ return config;
2094
4130
  }
2095
4131
 
2096
4132
  // src/spaces/index.ts
2097
- import fs8 from "fs";
2098
- import path9 from "path";
2099
- import crypto4 from "crypto";
4133
+ import fs9 from "fs";
4134
+ import path14 from "path";
4135
+ import crypto6 from "crypto";
2100
4136
  var MANIFEST_VERSION = 1;
2101
4137
  function getSpacesDir(baseDir) {
2102
4138
  const homeDir = baseDir ?? process.env.HOME ?? "~";
2103
- return path9.join(homeDir, ".config", "engram", "spaces");
4139
+ return path14.join(homeDir, ".config", "engram", "spaces");
2104
4140
  }
2105
4141
  function getManifestPath(baseDir) {
2106
- return path9.join(getSpacesDir(baseDir), "manifest.json");
4142
+ return path14.join(getSpacesDir(baseDir), "manifest.json");
2107
4143
  }
2108
4144
  function loadManifest(baseDir, memoryDirOverride) {
2109
- const manifestPath = getManifestPath(baseDir);
2110
- if (!fs8.existsSync(manifestPath)) {
4145
+ const manifestPath2 = getManifestPath(baseDir);
4146
+ if (!fs9.existsSync(manifestPath2)) {
2111
4147
  const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);
2112
4148
  const manifest = {
2113
4149
  activeSpaceId: personalSpace.id,
@@ -2117,19 +4153,19 @@ function loadManifest(baseDir, memoryDirOverride) {
2117
4153
  saveManifest(manifest, baseDir);
2118
4154
  return manifest;
2119
4155
  }
2120
- const raw = JSON.parse(fs8.readFileSync(manifestPath, "utf8"));
4156
+ const raw = JSON.parse(fs9.readFileSync(manifestPath2, "utf8"));
2121
4157
  return raw;
2122
4158
  }
2123
4159
  function saveManifest(manifest, baseDir) {
2124
- const manifestPath = getManifestPath(baseDir);
2125
- fs8.mkdirSync(path9.dirname(manifestPath), { recursive: true });
2126
- fs8.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
4160
+ const manifestPath2 = getManifestPath(baseDir);
4161
+ fs9.mkdirSync(path14.dirname(manifestPath2), { recursive: true });
4162
+ fs9.writeFileSync(manifestPath2, JSON.stringify(manifest, null, 2) + "\n");
2127
4163
  }
2128
4164
  function createPersonalSpace(baseDir, memoryDirOverride) {
2129
4165
  const homeDir = baseDir ?? process.env.HOME ?? "~";
2130
- const standalonePath = path9.join(homeDir, ".engram", "memory");
2131
- const openclawPath = path9.join(homeDir, ".openclaw", "workspace", "memory", "local");
2132
- const memoryDir = memoryDirOverride ?? process.env.ENGRAM_MEMORY_DIR ?? (fs8.existsSync(standalonePath) ? standalonePath : fs8.existsSync(openclawPath) ? openclawPath : standalonePath);
4166
+ const standalonePath = path14.join(homeDir, ".engram", "memory");
4167
+ const openclawPath = path14.join(homeDir, ".openclaw", "workspace", "memory", "local");
4168
+ const memoryDir = memoryDirOverride ?? process.env.ENGRAM_MEMORY_DIR ?? (fs9.existsSync(standalonePath) ? standalonePath : fs9.existsSync(openclawPath) ? openclawPath : standalonePath);
2133
4169
  const now = (/* @__PURE__ */ new Date()).toISOString();
2134
4170
  return {
2135
4171
  id: "personal",
@@ -2162,7 +4198,7 @@ function createSpace(options) {
2162
4198
  throw new Error(`Parent space "${options.parentSpaceId}" not found`);
2163
4199
  }
2164
4200
  const now = (/* @__PURE__ */ new Date()).toISOString();
2165
- const memoryDir = options.memoryDir ?? path9.join(
4201
+ const memoryDir = options.memoryDir ?? path14.join(
2166
4202
  getSpacesDir(options.baseDir),
2167
4203
  id,
2168
4204
  "memory"
@@ -2178,7 +4214,7 @@ function createSpace(options) {
2178
4214
  owner: process.env.USER,
2179
4215
  parentSpaceId: options.parentSpaceId
2180
4216
  };
2181
- fs8.mkdirSync(memoryDir, { recursive: true });
4217
+ fs9.mkdirSync(memoryDir, { recursive: true });
2182
4218
  manifest.spaces.push(space);
2183
4219
  manifest.updatedAt = now;
2184
4220
  saveManifest(manifest, options.baseDir);
@@ -2352,34 +4388,34 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
2352
4388
  };
2353
4389
  }
2354
4390
  function getAuditLog(baseDir) {
2355
- const auditPath = path9.join(getSpacesDir(baseDir), "audit.jsonl");
2356
- if (!fs8.existsSync(auditPath)) return [];
2357
- const lines = fs8.readFileSync(auditPath, "utf8").trim().split("\n");
4391
+ const auditPath = path14.join(getSpacesDir(baseDir), "audit.jsonl");
4392
+ if (!fs9.existsSync(auditPath)) return [];
4393
+ const lines = fs9.readFileSync(auditPath, "utf8").trim().split("\n");
2358
4394
  return lines.filter((l) => l.trim()).map((l) => JSON.parse(l));
2359
4395
  }
2360
4396
  function appendAudit(entry, baseDir) {
2361
- const auditPath = path9.join(getSpacesDir(baseDir), "audit.jsonl");
2362
- fs8.mkdirSync(path9.dirname(auditPath), { recursive: true });
4397
+ const auditPath = path14.join(getSpacesDir(baseDir), "audit.jsonl");
4398
+ fs9.mkdirSync(path14.dirname(auditPath), { recursive: true });
2363
4399
  const full = {
2364
- id: crypto4.randomUUID(),
4400
+ id: crypto6.randomUUID(),
2365
4401
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2366
4402
  ...entry
2367
4403
  };
2368
- fs8.appendFileSync(auditPath, JSON.stringify(full) + "\n");
4404
+ fs9.appendFileSync(auditPath, JSON.stringify(full) + "\n");
2369
4405
  }
2370
4406
  function copyMemories(sourceDir, targetDir, options) {
2371
4407
  let merged = 0;
2372
4408
  const conflicts = [];
2373
4409
  let skipped = 0;
2374
- if (!fs8.existsSync(sourceDir)) {
4410
+ if (!fs9.existsSync(sourceDir)) {
2375
4411
  return { merged: 0, conflicts: [], skipped: 0 };
2376
4412
  }
2377
- fs8.mkdirSync(targetDir, { recursive: true });
4413
+ fs9.mkdirSync(targetDir, { recursive: true });
2378
4414
  const sourceFiles = walkMd2(sourceDir);
2379
4415
  for (const sourcePath of sourceFiles) {
2380
- const content = fs8.readFileSync(sourcePath, "utf8");
2381
- const relativePath = path9.relative(sourceDir, sourcePath);
2382
- const targetPath = path9.join(targetDir, relativePath);
4416
+ const content = fs9.readFileSync(sourcePath, "utf8");
4417
+ const relativePath = path14.relative(sourceDir, sourcePath);
4418
+ const targetPath = path14.join(targetDir, relativePath);
2383
4419
  const sourceHash = hashContent4(content);
2384
4420
  if (options?.filterIds?.length) {
2385
4421
  const fm = parseSimpleFrontmatter(content);
@@ -2388,8 +4424,8 @@ function copyMemories(sourceDir, targetDir, options) {
2388
4424
  continue;
2389
4425
  }
2390
4426
  }
2391
- if (fs8.existsSync(targetPath) && !options?.force) {
2392
- const targetContent = fs8.readFileSync(targetPath, "utf8");
4427
+ if (fs9.existsSync(targetPath) && !options?.force) {
4428
+ const targetContent = fs9.readFileSync(targetPath, "utf8");
2393
4429
  const targetHash = hashContent4(targetContent);
2394
4430
  if (sourceHash !== targetHash) {
2395
4431
  conflicts.push({
@@ -2405,20 +4441,20 @@ function copyMemories(sourceDir, targetDir, options) {
2405
4441
  skipped++;
2406
4442
  continue;
2407
4443
  }
2408
- fs8.mkdirSync(path9.dirname(targetPath), { recursive: true });
2409
- fs8.writeFileSync(targetPath, content);
4444
+ fs9.mkdirSync(path14.dirname(targetPath), { recursive: true });
4445
+ fs9.writeFileSync(targetPath, content);
2410
4446
  merged++;
2411
4447
  }
2412
4448
  return { merged, conflicts, skipped };
2413
4449
  }
2414
4450
  function hashContent4(content) {
2415
- return crypto4.createHash("sha256").update(content).digest("hex").slice(0, 16);
4451
+ return crypto6.createHash("sha256").update(content).digest("hex").slice(0, 16);
2416
4452
  }
2417
4453
  function walkMd2(dir) {
2418
4454
  const results = [];
2419
4455
  function walk(d) {
2420
- for (const entry of fs8.readdirSync(d, { withFileTypes: true })) {
2421
- const fullPath = path9.join(d, entry.name);
4456
+ for (const entry of fs9.readdirSync(d, { withFileTypes: true })) {
4457
+ const fullPath = path14.join(d, entry.name);
2422
4458
  if (entry.isDirectory()) {
2423
4459
  walk(fullPath);
2424
4460
  } else if (entry.name.endsWith(".md")) {
@@ -2442,72 +4478,1053 @@ function parseSimpleFrontmatter(content) {
2442
4478
  }
2443
4479
  return fm;
2444
4480
  }
4481
+
4482
+ // src/memory-extension/shared-instructions.ts
4483
+ var REMNIC_SEMANTIC_OVERVIEW = `## Remnic Memory Types
4484
+
4485
+ Remnic stores memories as plain Markdown files with YAML front-matter.
4486
+ Each memory has a **type** that describes its semantic role:
4487
+
4488
+ | Type | Description |
4489
+ |------|-------------|
4490
+ | \`fact\` | An objective piece of knowledge the user confirmed or that was extracted from a session. |
4491
+ | \`preference\` | A stated or inferred user preference (e.g. coding style, tool choice). |
4492
+ | \`decision\` | An explicit decision or trade-off the user made. |
4493
+ | \`entity\` | A named thing the user cares about (project, service, person, API). |
4494
+ | \`skill\` | A reusable workflow or procedure documented for future sessions. |
4495
+ | \`correction\` | A fix or amendment to a previously stored memory. |
4496
+ | \`question\` | An open question or uncertainty flagged for future resolution. |
4497
+ | \`observation\` | A pattern noticed across sessions (e.g. "user always runs tests before commits"). |
4498
+ | \`summary\` | A condensed roll-up of recent sessions or a topic area. |
4499
+
4500
+ When reading Remnic content, the front-matter \`type\` field tells you what
4501
+ kind of knowledge you are looking at and how much weight to give it.
4502
+ `;
4503
+ var REMNIC_CITATION_FORMAT = `## Citing Remnic Memories
4504
+
4505
+ When a piece of your output draws on a Remnic file, cite it using the
4506
+ memory citation block format so the user can trace the source:
4507
+
4508
+ \`\`\`
4509
+ <oai-mem-citation path="<path-relative-to-remnic-memory-base>" />
4510
+ \`\`\`
4511
+
4512
+ The path must be **relative to the Remnic memory base** (the directory
4513
+ named \`memories/\` under \`<remnic-home>\`), not absolute. Examples:
4514
+
4515
+ - \`<oai-mem-citation path="default/MEMORY.md" />\`
4516
+ - \`<oai-mem-citation path="my-project/skills/deploy/SKILL.md" />\`
4517
+ - \`<oai-mem-citation path="shared/memory_summary.md" />\`
4518
+
4519
+ Cite each distinct source once near the fact it supports. Do not invent
4520
+ citations for files you have not actually read.
4521
+ `;
4522
+ var REMNIC_MCP_TOOL_INVENTORY = `## Remnic MCP Tools
4523
+
4524
+ When the Remnic MCP server is reachable, the following tools are
4525
+ available. Prefer MCP tools over direct file reads when the host
4526
+ supports MCP connections.
4527
+
4528
+ | Tool | Purpose |
4529
+ |------|---------|
4530
+ | \`remnic.recall\` | Retrieve contextually relevant memories for the current session. |
4531
+ | \`remnic.recall_explain\` | Like recall, but includes an explanation of why each memory was selected. |
4532
+ | \`remnic.memory_store\` | Persist a new memory (fact, preference, decision, etc.). |
4533
+ | \`remnic.memory_get\` | Fetch a specific memory by ID. |
4534
+ | \`remnic.memory_search\` | Full-text + semantic search across all memories. |
4535
+ | \`remnic.memory_timeline\` | Retrieve memories in chronological order within a time range. |
4536
+ | \`remnic.observe\` | Record an observation from the current conversation turn. |
4537
+ | \`remnic.entity_get\` | Look up a named entity and its relationships. |
4538
+ | \`remnic.memory_entities_list\` | List all known entities. |
4539
+ | \`remnic.memory_profile\` | Retrieve the user profile summary. |
4540
+ | \`remnic.day_summary\` | Generate a summary of memories from a specific day. |
4541
+ | \`remnic.briefing\` | Generate a structured briefing for an upcoming session. |
4542
+ | \`remnic.memory_feedback\` | Submit feedback on a recalled memory (useful, outdated, wrong). |
4543
+ | \`remnic.memory_promote\` | Promote a memory to a higher confidence tier. |
4544
+ | \`remnic.context_checkpoint\` | Save a conversation checkpoint for continuity. |
4545
+ | \`remnic.suggestion_submit\` | Submit a suggestion for a new memory to the review queue. |
4546
+ | \`remnic.review_queue_list\` | List pending suggestions in the review queue. |
4547
+ | \`remnic.work_task\` | Create or update a work task. |
4548
+ | \`remnic.work_project\` | Create or update a work project. |
4549
+ | \`remnic.work_board\` | View the work board. |
4550
+
4551
+ Legacy \`engram.*\` prefixed names are accepted as aliases for all tools.
4552
+ `;
4553
+ var REMNIC_RECALL_DECISION_RULES = `## When to Use Recall vs Direct Read
4554
+
4555
+ ### Use \`remnic.recall\` (MCP) when:
4556
+
4557
+ - The Remnic MCP server is reachable (the host has an active MCP
4558
+ connection to the Remnic daemon).
4559
+ - You want contextually relevant memories ranked by the recall planner
4560
+ (semantic search + reranking + importance scoring).
4561
+ - You need memories across multiple namespaces or topics.
4562
+ - The conversation benefits from Remnic's intent detection and
4563
+ adaptive recall depth.
4564
+
4565
+ ### Use direct file reads when:
4566
+
4567
+ - You are in a sandboxed environment with no network or MCP access
4568
+ (e.g. Codex phase-2 consolidation).
4569
+ - You need a specific file you already know the path to.
4570
+ - The MCP server is unavailable or unhealthy.
4571
+ - You are operating on the raw memory files for maintenance or
4572
+ migration purposes.
4573
+
4574
+ ### General guidance:
4575
+
4576
+ - Prefer MCP tools when available \u2014 they provide ranked, deduplicated,
4577
+ and context-aware results.
4578
+ - Fall back to file reads gracefully \u2014 never block on a failed MCP call
4579
+ when the data is also on disk.
4580
+ - Never write directly to the Remnic memory directory unless you are an
4581
+ authorized extraction or consolidation process.
4582
+ `;
4583
+
4584
+ // src/memory-extension/codex-publisher.ts
4585
+ import fs10 from "fs";
4586
+ import os2 from "os";
4587
+ import path15 from "path";
4588
+ var REMNIC_EXTENSION_DIR_NAME2 = "remnic";
4589
+ var CodexMemoryExtensionPublisher = class {
4590
+ hostId = "codex";
4591
+ static capabilities = {
4592
+ instructionsMd: true,
4593
+ skillsFolder: true,
4594
+ citationFormat: true,
4595
+ readPathTemplate: true
4596
+ };
4597
+ async resolveExtensionRoot(env) {
4598
+ const e = env ?? process.env;
4599
+ const codexHome = e.CODEX_HOME?.trim() || path15.join(e.HOME ?? os2.homedir(), ".codex");
4600
+ return path15.join(codexHome, "memories_extensions", REMNIC_EXTENSION_DIR_NAME2);
4601
+ }
4602
+ async isHostAvailable() {
4603
+ try {
4604
+ const home = process.env.CODEX_HOME?.trim() || path15.join(process.env.HOME ?? os2.homedir(), ".codex");
4605
+ return fs10.existsSync(home);
4606
+ } catch {
4607
+ return false;
4608
+ }
4609
+ }
4610
+ async renderInstructions(ctx) {
4611
+ const memDir = ctx.config.memoryDir;
4612
+ const ns = ctx.config.namespace ?? "default";
4613
+ const sections = [
4614
+ `# Remnic Memory Extension for Codex
4615
+ `,
4616
+ `This document tells you how to use Remnic as an authoritative local memory source. Remnic is a local-first, file-backed memory system. All Remnic content lives on disk as plain Markdown.
4617
+ `,
4618
+ REMNIC_SEMANTIC_OVERVIEW,
4619
+ `## Where Remnic Content Lives
4620
+
4621
+ Memory base directory: \`${memDir}\`
4622
+
4623
+ Namespace: \`${ns}\`
4624
+
4625
+ Under the base directory, memories are organized by namespace:
4626
+
4627
+ \`\`\`
4628
+ ${memDir}/<namespace>/
4629
+ MEMORY.md # compact top-of-mind memory
4630
+ memory_summary.md # optional longer summary
4631
+ skills/
4632
+ <skill-name>/SKILL.md # reusable workflows
4633
+ rollout_summaries/
4634
+ *.md # per-session rollup notes
4635
+ \`\`\`
4636
+ `,
4637
+ REMNIC_CITATION_FORMAT,
4638
+ REMNIC_MCP_TOOL_INVENTORY,
4639
+ REMNIC_RECALL_DECISION_RULES,
4640
+ `## Sandboxing Rules (Codex Phase-2)
4641
+
4642
+ When running inside the Codex phase-2 consolidation sandbox:
4643
+
4644
+ - **No network.** Do not attempt HTTP calls or MCP connections.
4645
+ - **No CLI invocation.** Do not shell out to \`remnic\` or \`engram\`.
4646
+ - **No MCP tool calls.** Use filesystem reads only.
4647
+ - **Local writes** are allowed only where Codex's sandbox policy permits.
4648
+ - **Respect missing files.** If a file does not exist, move on silently.
4649
+ `
4650
+ ];
4651
+ return sections.join("\n");
4652
+ }
4653
+ async publish(ctx) {
4654
+ const extensionRoot = await this.resolveExtensionRoot();
4655
+ const instructionsPath = path15.join(extensionRoot, "instructions.md");
4656
+ const filesWritten = [];
4657
+ const skipped = [];
4658
+ ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);
4659
+ fs10.mkdirSync(extensionRoot, { recursive: true });
4660
+ const content = await this.renderInstructions(ctx);
4661
+ const tmpPath = `${instructionsPath}.tmp-${process.pid}-${Date.now()}`;
4662
+ try {
4663
+ fs10.writeFileSync(tmpPath, content, "utf-8");
4664
+ fs10.renameSync(tmpPath, instructionsPath);
4665
+ filesWritten.push(instructionsPath);
4666
+ ctx.log.info(`Wrote ${instructionsPath}`);
4667
+ } catch (err) {
4668
+ try {
4669
+ if (fs10.existsSync(tmpPath)) {
4670
+ fs10.unlinkSync(tmpPath);
4671
+ }
4672
+ } catch {
4673
+ }
4674
+ throw err;
4675
+ }
4676
+ return {
4677
+ hostId: this.hostId,
4678
+ extensionRoot,
4679
+ filesWritten,
4680
+ skipped
4681
+ };
4682
+ }
4683
+ async unpublish() {
4684
+ const extensionRoot = await this.resolveExtensionRoot();
4685
+ if (fs10.existsSync(extensionRoot)) {
4686
+ fs10.rmSync(extensionRoot, { recursive: true, force: true });
4687
+ }
4688
+ }
4689
+ };
4690
+
4691
+ // src/memory-extension/claude-code-publisher.ts
4692
+ var ClaudeCodeMemoryExtensionPublisher = class {
4693
+ hostId = "claude-code";
4694
+ static capabilities = {
4695
+ instructionsMd: false,
4696
+ skillsFolder: false,
4697
+ citationFormat: false,
4698
+ readPathTemplate: false
4699
+ };
4700
+ async resolveExtensionRoot() {
4701
+ return "";
4702
+ }
4703
+ async isHostAvailable() {
4704
+ return false;
4705
+ }
4706
+ async renderInstructions(_ctx) {
4707
+ return "";
4708
+ }
4709
+ async publish(_ctx) {
4710
+ return {
4711
+ hostId: this.hostId,
4712
+ extensionRoot: "",
4713
+ filesWritten: [],
4714
+ skipped: []
4715
+ };
4716
+ }
4717
+ async unpublish() {
4718
+ }
4719
+ };
4720
+
4721
+ // src/memory-extension/hermes-publisher.ts
4722
+ var HermesMemoryExtensionPublisher = class {
4723
+ hostId = "hermes";
4724
+ static capabilities = {
4725
+ instructionsMd: false,
4726
+ skillsFolder: false,
4727
+ citationFormat: false,
4728
+ readPathTemplate: false
4729
+ };
4730
+ async resolveExtensionRoot() {
4731
+ return "";
4732
+ }
4733
+ async isHostAvailable() {
4734
+ return false;
4735
+ }
4736
+ async renderInstructions(_ctx) {
4737
+ return "";
4738
+ }
4739
+ async publish(_ctx) {
4740
+ return {
4741
+ hostId: this.hostId,
4742
+ extensionRoot: "",
4743
+ filesWritten: [],
4744
+ skipped: []
4745
+ };
4746
+ }
4747
+ async unpublish() {
4748
+ }
4749
+ };
4750
+
4751
+ // src/memory-extension/index.ts
4752
+ var PUBLISHERS = {};
4753
+ function registerPublisher(hostId, factory) {
4754
+ PUBLISHERS[hostId] = factory;
4755
+ }
4756
+ var CONNECTOR_TO_HOST = {
4757
+ "codex-cli": "codex"
4758
+ };
4759
+ function hostIdForConnector(connectorId) {
4760
+ return CONNECTOR_TO_HOST[connectorId] ?? connectorId;
4761
+ }
4762
+ function publisherFor(hostId) {
4763
+ const factory = PUBLISHERS[hostId];
4764
+ return factory ? factory() : void 0;
4765
+ }
4766
+ function publisherForConnector(connectorId) {
4767
+ return publisherFor(hostIdForConnector(connectorId));
4768
+ }
4769
+
4770
+ // src/taxonomy/default-taxonomy.ts
4771
+ var DEFAULT_TAXONOMY = {
4772
+ version: 1,
4773
+ categories: [
4774
+ {
4775
+ id: "corrections",
4776
+ name: "Corrections",
4777
+ description: "Corrections to previously stored information",
4778
+ filingRules: ["Any update that supersedes a prior fact"],
4779
+ priority: 10,
4780
+ memoryCategories: ["correction"]
4781
+ },
4782
+ {
4783
+ id: "principles",
4784
+ name: "Principles",
4785
+ description: "Rules, guidelines, and recurring patterns",
4786
+ filingRules: ["A guiding principle, rule, or skill"],
4787
+ priority: 20,
4788
+ memoryCategories: ["principle", "rule", "skill"]
4789
+ },
4790
+ {
4791
+ id: "procedures",
4792
+ name: "Procedures",
4793
+ description: "Ordered multi-step workflows the user repeats",
4794
+ filingRules: ["A repeatable sequence of steps or commands for a task"],
4795
+ priority: 25,
4796
+ memoryCategories: ["procedure"]
4797
+ },
4798
+ {
4799
+ id: "entities",
4800
+ name: "Entities",
4801
+ description: "People, organizations, places, projects",
4802
+ filingRules: ["Named entity with attributes"],
4803
+ priority: 30,
4804
+ memoryCategories: ["entity", "relationship"]
4805
+ },
4806
+ {
4807
+ id: "decisions",
4808
+ name: "Decisions",
4809
+ description: "Choices made and their rationale",
4810
+ filingRules: ["A decision or commitment with reasoning"],
4811
+ priority: 35,
4812
+ memoryCategories: ["decision", "commitment"]
4813
+ },
4814
+ {
4815
+ id: "preferences",
4816
+ name: "Preferences",
4817
+ description: "User likes, dislikes, and style choices",
4818
+ filingRules: ["Anything expressing a preference or taste"],
4819
+ priority: 40,
4820
+ memoryCategories: ["preference"]
4821
+ },
4822
+ {
4823
+ id: "facts",
4824
+ name: "Facts",
4825
+ description: "Objective statements about the world",
4826
+ filingRules: ["Any factual claim or piece of information"],
4827
+ priority: 50,
4828
+ memoryCategories: ["fact"]
4829
+ },
4830
+ {
4831
+ id: "moments",
4832
+ name: "Moments",
4833
+ description: "Significant events or experiences",
4834
+ filingRules: ["A specific event worth remembering"],
4835
+ priority: 60,
4836
+ memoryCategories: ["moment"]
4837
+ }
4838
+ ]
4839
+ };
4840
+
4841
+ // src/taxonomy/resolver.ts
4842
+ var DEFAULT_CATEGORY_ID = "facts";
4843
+ function resolveCategory(content, memoryCategory, taxonomy) {
4844
+ const contentLower = content.toLowerCase();
4845
+ const matches = taxonomy.categories.filter(
4846
+ (cat) => cat.memoryCategories.includes(memoryCategory)
4847
+ );
4848
+ if (matches.length === 0) {
4849
+ const fallback = taxonomy.categories.find((c) => c.id === DEFAULT_CATEGORY_ID) ?? taxonomy.categories[0];
4850
+ if (!fallback) {
4851
+ return {
4852
+ categoryId: DEFAULT_CATEGORY_ID,
4853
+ confidence: 0,
4854
+ reason: "Taxonomy is empty; using default category",
4855
+ alternatives: []
4856
+ };
4857
+ }
4858
+ const alternatives2 = taxonomy.categories.filter((c) => c.id !== fallback.id).map((c) => ({
4859
+ categoryId: c.id,
4860
+ reason: c.description
4861
+ }));
4862
+ return {
4863
+ categoryId: fallback.id,
4864
+ confidence: 0.3,
4865
+ reason: `No taxonomy category maps to MemoryCategory "${memoryCategory}"; falling back to "${fallback.name}"`,
4866
+ alternatives: alternatives2
4867
+ };
4868
+ }
4869
+ if (matches.length === 1) {
4870
+ const match = matches[0];
4871
+ const alternatives2 = taxonomy.categories.filter((c) => c.id !== match.id).map((c) => ({
4872
+ categoryId: c.id,
4873
+ reason: c.description
4874
+ }));
4875
+ return {
4876
+ categoryId: match.id,
4877
+ confidence: 1,
4878
+ reason: `Unique match: MemoryCategory "${memoryCategory}" maps to "${match.name}"`,
4879
+ alternatives: alternatives2
4880
+ };
4881
+ }
4882
+ const scored = matches.map((cat) => ({
4883
+ cat,
4884
+ keywordScore: computeKeywordScore(contentLower, cat)
4885
+ }));
4886
+ scored.sort((a, b) => {
4887
+ if (b.keywordScore !== a.keywordScore) return b.keywordScore - a.keywordScore;
4888
+ return a.cat.priority - b.cat.priority;
4889
+ });
4890
+ const best = scored[0];
4891
+ const runnerUp = scored[1];
4892
+ const confidence = best.keywordScore > 0 && (!runnerUp || best.keywordScore > runnerUp.keywordScore) ? 0.9 : 0.7;
4893
+ const alternatives = taxonomy.categories.filter((c) => c.id !== best.cat.id).map((c) => ({
4894
+ categoryId: c.id,
4895
+ reason: c.description
4896
+ }));
4897
+ const reason = best.keywordScore > 0 ? `Filing rules for "${best.cat.name}" matched content keywords (priority ${best.cat.priority})` : `Priority tie-break: "${best.cat.name}" has lowest priority number (${best.cat.priority})`;
4898
+ return {
4899
+ categoryId: best.cat.id,
4900
+ confidence,
4901
+ reason,
4902
+ alternatives
4903
+ };
4904
+ }
4905
+ function computeKeywordScore(contentLower, cat) {
4906
+ let score = 0;
4907
+ const ruleText = [...cat.filingRules, cat.description].join(" ").toLowerCase();
4908
+ const keywords = ruleText.split(/[^a-z0-9]+/).filter((w) => w.length >= 3);
4909
+ for (const kw of keywords) {
4910
+ if (contentLower.includes(kw)) {
4911
+ score += 1;
4912
+ }
4913
+ }
4914
+ return score;
4915
+ }
4916
+
4917
+ // src/taxonomy/resolver-doc-generator.ts
4918
+ function generateResolverDocument(taxonomy) {
4919
+ const sorted = [...taxonomy.categories].sort((a, b) => {
4920
+ if (a.priority !== b.priority) return a.priority - b.priority;
4921
+ return a.id.localeCompare(b.id);
4922
+ });
4923
+ const lines = [
4924
+ "# Memory Filing Resolver",
4925
+ "",
4926
+ "Given a new piece of knowledge, follow this tree to determine where it belongs.",
4927
+ ""
4928
+ ];
4929
+ let step = 1;
4930
+ for (const cat of sorted) {
4931
+ lines.push(`## Step ${step}: ${cat.description}?`);
4932
+ lines.push("");
4933
+ for (const rule of cat.filingRules) {
4934
+ lines.push(`- ${rule}`);
4935
+ }
4936
+ lines.push("");
4937
+ lines.push(
4938
+ `> YES: File under **${cat.id}/** (priority ${cat.priority})`
4939
+ );
4940
+ lines.push("");
4941
+ step++;
4942
+ }
4943
+ lines.push("## Tie-breaking");
4944
+ lines.push("");
4945
+ lines.push(
4946
+ "If a fact could go in multiple categories, file under the one with the **lowest priority number**."
4947
+ );
4948
+ lines.push("");
4949
+ lines.push(`---`);
4950
+ lines.push(`*Generated from taxonomy v${taxonomy.version}*`);
4951
+ lines.push("");
4952
+ return lines.join("\n");
4953
+ }
4954
+
4955
+ // src/taxonomy/taxonomy-loader.ts
4956
+ import { readFile, mkdir, writeFile } from "fs/promises";
4957
+ import path16 from "path";
4958
+ var TAXONOMY_DIR = ".taxonomy";
4959
+ var TAXONOMY_FILE = "taxonomy.json";
4960
+ var MAX_SLUG_LENGTH = 32;
4961
+ var SLUG_RE = /^[a-z][a-z0-9-]*$/;
4962
+ function validateSlug(slug) {
4963
+ if (slug.length === 0) {
4964
+ throw new Error("Taxonomy category ID must not be empty");
4965
+ }
4966
+ if (slug.length > MAX_SLUG_LENGTH) {
4967
+ throw new Error(
4968
+ `Taxonomy category ID "${slug}" exceeds ${MAX_SLUG_LENGTH} characters`
4969
+ );
4970
+ }
4971
+ if (!SLUG_RE.test(slug)) {
4972
+ throw new Error(
4973
+ `Taxonomy category ID "${slug}" is invalid: must be lowercase letters, digits, and hyphens, starting with a letter`
4974
+ );
4975
+ }
4976
+ }
4977
+ function validateTaxonomy(taxonomy) {
4978
+ if (typeof taxonomy.version !== "number" || taxonomy.version < 1) {
4979
+ throw new Error("Taxonomy version must be a positive integer");
4980
+ }
4981
+ if (!Array.isArray(taxonomy.categories)) {
4982
+ throw new Error("Taxonomy categories must be an array");
4983
+ }
4984
+ const seenIds = /* @__PURE__ */ new Set();
4985
+ for (const cat of taxonomy.categories) {
4986
+ validateSlug(cat.id);
4987
+ if (seenIds.has(cat.id)) {
4988
+ throw new Error(`Duplicate taxonomy category ID: "${cat.id}"`);
4989
+ }
4990
+ seenIds.add(cat.id);
4991
+ if (typeof cat.name !== "string" || cat.name.trim().length === 0) {
4992
+ throw new Error(`Taxonomy category "${cat.id}" must have a non-empty name`);
4993
+ }
4994
+ if (typeof cat.description !== "string" || cat.description.trim().length === 0) {
4995
+ throw new Error(`Taxonomy category "${cat.id}" must have a non-empty description`);
4996
+ }
4997
+ if (!Array.isArray(cat.filingRules)) {
4998
+ throw new Error(`Taxonomy category "${cat.id}" filingRules must be an array`);
4999
+ }
5000
+ if (typeof cat.priority !== "number" || !Number.isFinite(cat.priority)) {
5001
+ throw new Error(`Taxonomy category "${cat.id}" must have a finite numeric priority`);
5002
+ }
5003
+ if (!Array.isArray(cat.memoryCategories)) {
5004
+ throw new Error(`Taxonomy category "${cat.id}" memoryCategories must be an array`);
5005
+ }
5006
+ if (cat.parentId !== void 0) {
5007
+ if (typeof cat.parentId !== "string") {
5008
+ throw new Error(`Taxonomy category "${cat.id}" parentId must be a string if set`);
5009
+ }
5010
+ }
5011
+ }
5012
+ for (const cat of taxonomy.categories) {
5013
+ if (cat.parentId !== void 0 && !seenIds.has(cat.parentId)) {
5014
+ throw new Error(
5015
+ `Taxonomy category "${cat.id}" references unknown parentId "${cat.parentId}"`
5016
+ );
5017
+ }
5018
+ }
5019
+ }
5020
+ async function loadTaxonomy(memoryDir) {
5021
+ const taxonomyPath = path16.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
5022
+ let raw;
5023
+ try {
5024
+ raw = await readFile(taxonomyPath, "utf-8");
5025
+ } catch (err) {
5026
+ if (err instanceof Error && err.code === "ENOENT") {
5027
+ return structuredClone(DEFAULT_TAXONOMY);
5028
+ }
5029
+ throw err;
5030
+ }
5031
+ const parsed = JSON.parse(raw);
5032
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
5033
+ throw new Error("taxonomy.json must be a JSON object");
5034
+ }
5035
+ const obj = parsed;
5036
+ const userVersion = typeof obj.version === "number" ? obj.version : DEFAULT_TAXONOMY.version;
5037
+ const userCategories = Array.isArray(obj.categories) ? obj.categories : [];
5038
+ const userIdCounts = /* @__PURE__ */ new Map();
5039
+ for (const cat of userCategories) {
5040
+ const id = typeof cat.id === "string" ? cat.id : String(cat.id);
5041
+ userIdCounts.set(id, (userIdCounts.get(id) ?? 0) + 1);
5042
+ }
5043
+ const duplicateIds = [...userIdCounts.entries()].filter(([, count]) => count > 1).map(([id]) => id);
5044
+ if (duplicateIds.length > 0) {
5045
+ throw new Error(
5046
+ `Duplicate category IDs in taxonomy.json: ${duplicateIds.map((id) => `"${id}"`).join(", ")}`
5047
+ );
5048
+ }
5049
+ const mergedMap = /* @__PURE__ */ new Map();
5050
+ for (const cat of DEFAULT_TAXONOMY.categories) {
5051
+ mergedMap.set(cat.id, { ...cat });
5052
+ }
5053
+ for (const cat of userCategories) {
5054
+ mergedMap.set(cat.id, cat);
5055
+ }
5056
+ const merged = {
5057
+ version: userVersion,
5058
+ categories: [...mergedMap.values()]
5059
+ };
5060
+ validateTaxonomy(merged);
5061
+ return merged;
5062
+ }
5063
+ async function saveTaxonomy(memoryDir, taxonomy) {
5064
+ validateTaxonomy(taxonomy);
5065
+ const dir = path16.join(memoryDir, TAXONOMY_DIR);
5066
+ await mkdir(dir, { recursive: true });
5067
+ const filePath = path16.join(dir, TAXONOMY_FILE);
5068
+ await writeFile(filePath, JSON.stringify(taxonomy, null, 2) + "\n", "utf-8");
5069
+ }
5070
+ function getTaxonomyDir(memoryDir) {
5071
+ return path16.join(memoryDir, TAXONOMY_DIR);
5072
+ }
5073
+ function getTaxonomyFilePath(memoryDir) {
5074
+ return path16.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);
5075
+ }
5076
+
5077
+ // src/enrichment/types.ts
5078
+ function defaultEnrichmentPipelineConfig() {
5079
+ return {
5080
+ enabled: false,
5081
+ providers: [],
5082
+ importanceThresholds: {
5083
+ critical: [],
5084
+ high: [],
5085
+ normal: [],
5086
+ low: []
5087
+ },
5088
+ maxCandidatesPerEntity: 20,
5089
+ autoEnrichOnCreate: false,
5090
+ scheduleIntervalMs: 36e5
5091
+ };
5092
+ }
5093
+
5094
+ // src/enrichment/provider-registry.ts
5095
+ var EnrichmentProviderRegistry = class {
5096
+ providers = /* @__PURE__ */ new Map();
5097
+ /** Register a provider. Overwrites any existing provider with the same id. */
5098
+ register(provider) {
5099
+ this.providers.set(provider.id, provider);
5100
+ }
5101
+ /** Look up a single provider by id. */
5102
+ get(id) {
5103
+ return this.providers.get(id);
5104
+ }
5105
+ /**
5106
+ * Return all registered providers whose id appears in the config's
5107
+ * `providers` list with `enabled: true`.
5108
+ */
5109
+ listEnabled(config) {
5110
+ const enabledIds = new Set(
5111
+ config.providers.filter((p) => p.enabled).map((p) => p.id)
5112
+ );
5113
+ const result = [];
5114
+ for (const [id, provider] of this.providers.entries()) {
5115
+ if (enabledIds.has(id)) {
5116
+ result.push(provider);
5117
+ }
5118
+ }
5119
+ return result;
5120
+ }
5121
+ /**
5122
+ * Return providers that should run for a given importance level.
5123
+ * Providers are resolved from `config.importanceThresholds[level]` and
5124
+ * filtered to only those that are both registered and enabled.
5125
+ */
5126
+ getForImportance(level, config) {
5127
+ if (level === "trivial") return [];
5128
+ const thresholds = config.importanceThresholds;
5129
+ const providerIds = level === "critical" ? thresholds.critical : level === "high" ? thresholds.high : level === "normal" ? thresholds.normal : thresholds.low;
5130
+ const enabledIds = new Set(
5131
+ config.providers.filter((p) => p.enabled).map((p) => p.id)
5132
+ );
5133
+ const result = [];
5134
+ for (const id of providerIds) {
5135
+ if (!enabledIds.has(id)) continue;
5136
+ const provider = this.providers.get(id);
5137
+ if (provider) {
5138
+ result.push(provider);
5139
+ }
5140
+ }
5141
+ return result;
5142
+ }
5143
+ };
5144
+
5145
+ // src/enrichment/web-search-provider.ts
5146
+ var WebSearchProvider = class {
5147
+ id = "web-search";
5148
+ costTier = "cheap";
5149
+ searchFn;
5150
+ constructor(options = {}) {
5151
+ this.searchFn = options.searchFn;
5152
+ }
5153
+ async isAvailable() {
5154
+ return this.searchFn !== void 0;
5155
+ }
5156
+ async enrich(entity) {
5157
+ if (!this.searchFn) return [];
5158
+ const query = `${entity.name} ${entity.type}`;
5159
+ let snippets;
5160
+ try {
5161
+ snippets = await this.searchFn(query);
5162
+ } catch {
5163
+ return [];
5164
+ }
5165
+ return snippets.filter((s) => typeof s === "string" && s.trim().length > 0).map((snippet) => ({
5166
+ text: snippet.trim(),
5167
+ source: this.id,
5168
+ sourceUrl: void 0,
5169
+ confidence: 0.5,
5170
+ category: "fact",
5171
+ tags: ["web-search"]
5172
+ }));
5173
+ }
5174
+ };
5175
+
5176
+ // src/enrichment/pipeline.ts
5177
+ function isRateLimited(provider, config, buckets) {
5178
+ const providerCfg = config.providers.find((p) => p.id === provider.id);
5179
+ if (!providerCfg?.rateLimit) return false;
5180
+ const now = Date.now();
5181
+ let bucket = buckets.get(provider.id);
5182
+ if (!bucket) {
5183
+ bucket = {
5184
+ minuteCount: 0,
5185
+ minuteReset: now + 6e4,
5186
+ dayCount: 0,
5187
+ dayReset: now + 864e5
5188
+ };
5189
+ buckets.set(provider.id, bucket);
5190
+ }
5191
+ if (now >= bucket.minuteReset) {
5192
+ bucket.minuteCount = 0;
5193
+ bucket.minuteReset = now + 6e4;
5194
+ }
5195
+ if (now >= bucket.dayReset) {
5196
+ bucket.dayCount = 0;
5197
+ bucket.dayReset = now + 864e5;
5198
+ }
5199
+ const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;
5200
+ return bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay;
5201
+ }
5202
+ function recordCall(providerId, buckets) {
5203
+ const bucket = buckets.get(providerId);
5204
+ if (bucket) {
5205
+ bucket.minuteCount += 1;
5206
+ bucket.dayCount += 1;
5207
+ }
5208
+ }
5209
+ async function runEnrichmentPipeline(entities, registry, config, log2) {
5210
+ if (!config.enabled) return [];
5211
+ if (entities.length === 0) return [];
5212
+ const rateBuckets = /* @__PURE__ */ new Map();
5213
+ const results = [];
5214
+ for (const entity of entities) {
5215
+ const providers = registry.getForImportance(entity.importanceLevel, config);
5216
+ for (const provider of providers) {
5217
+ const start = Date.now();
5218
+ let available;
5219
+ try {
5220
+ available = await provider.isAvailable();
5221
+ } catch {
5222
+ available = false;
5223
+ }
5224
+ if (!available) {
5225
+ log2.debug?.(
5226
+ `enrichment: skipping provider ${provider.id} for ${entity.name} \u2014 unavailable`
5227
+ );
5228
+ results.push({
5229
+ entityName: entity.name,
5230
+ provider: provider.id,
5231
+ candidatesFound: 0,
5232
+ candidatesAccepted: 0,
5233
+ candidatesRejected: 0,
5234
+ acceptedCandidates: [],
5235
+ elapsed: Date.now() - start
5236
+ });
5237
+ continue;
5238
+ }
5239
+ if (isRateLimited(provider, config, rateBuckets)) {
5240
+ log2.debug?.(
5241
+ `enrichment: skipping provider ${provider.id} for ${entity.name} \u2014 rate limited`
5242
+ );
5243
+ results.push({
5244
+ entityName: entity.name,
5245
+ provider: provider.id,
5246
+ candidatesFound: 0,
5247
+ candidatesAccepted: 0,
5248
+ candidatesRejected: 0,
5249
+ acceptedCandidates: [],
5250
+ elapsed: Date.now() - start
5251
+ });
5252
+ continue;
5253
+ }
5254
+ let candidates;
5255
+ try {
5256
+ candidates = await provider.enrich(entity);
5257
+ } catch (err) {
5258
+ recordCall(provider.id, rateBuckets);
5259
+ log2.error?.(
5260
+ `enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`
5261
+ );
5262
+ results.push({
5263
+ entityName: entity.name,
5264
+ provider: provider.id,
5265
+ candidatesFound: 0,
5266
+ candidatesAccepted: 0,
5267
+ candidatesRejected: 0,
5268
+ acceptedCandidates: [],
5269
+ elapsed: Date.now() - start
5270
+ });
5271
+ continue;
5272
+ }
5273
+ recordCall(provider.id, rateBuckets);
5274
+ for (const candidate of candidates) {
5275
+ candidate.source = provider.id;
5276
+ }
5277
+ const maxCandidates = config.maxCandidatesPerEntity;
5278
+ let accepted;
5279
+ if (maxCandidates === 0) {
5280
+ accepted = [];
5281
+ } else if (maxCandidates > 0 && candidates.length > maxCandidates) {
5282
+ accepted = candidates.slice(0, maxCandidates);
5283
+ } else {
5284
+ accepted = candidates;
5285
+ }
5286
+ const rejected = candidates.length - accepted.length;
5287
+ results.push({
5288
+ entityName: entity.name,
5289
+ provider: provider.id,
5290
+ candidatesFound: candidates.length,
5291
+ candidatesAccepted: accepted.length,
5292
+ candidatesRejected: rejected,
5293
+ acceptedCandidates: accepted,
5294
+ elapsed: Date.now() - start
5295
+ });
5296
+ }
5297
+ }
5298
+ return results;
5299
+ }
5300
+
5301
+ // src/enrichment/audit.ts
5302
+ import { mkdir as mkdir2, readFile as readFile2, appendFile } from "fs/promises";
5303
+ import { existsSync as existsSync2 } from "fs";
5304
+ import path17 from "path";
5305
+ var AUDIT_FILENAME = "enrichment-audit.jsonl";
5306
+ function auditFilePath(auditDir) {
5307
+ return path17.join(auditDir, AUDIT_FILENAME);
5308
+ }
5309
+ async function appendAuditEntry(auditDir, entry) {
5310
+ await mkdir2(auditDir, { recursive: true });
5311
+ const line = JSON.stringify(entry) + "\n";
5312
+ await appendFile(auditFilePath(auditDir), line, "utf-8");
5313
+ }
5314
+ async function readAuditLog(auditDir, since) {
5315
+ const filePath = auditFilePath(auditDir);
5316
+ if (!existsSync2(filePath)) return [];
5317
+ const raw = await readFile2(filePath, "utf-8");
5318
+ const entries = [];
5319
+ for (const line of raw.split("\n")) {
5320
+ const trimmed = line.trim();
5321
+ if (trimmed.length === 0) continue;
5322
+ try {
5323
+ const parsed = JSON.parse(trimmed);
5324
+ if (typeof parsed === "object" && parsed !== null && "timestamp" in parsed && "entityName" in parsed) {
5325
+ const entry = parsed;
5326
+ if (since && entry.timestamp < since) continue;
5327
+ entries.push(entry);
5328
+ }
5329
+ } catch {
5330
+ }
5331
+ }
5332
+ return entries;
5333
+ }
2445
5334
  export {
5335
+ BRIEFING_FORMAT_ALLOWED,
2446
5336
  BootstrapEngine,
5337
+ CITATION_UNKNOWN,
5338
+ CODEX_THREAD_KEY_PREFIX,
5339
+ ClaudeCodeMemoryExtensionPublisher,
5340
+ CodexMemoryExtensionPublisher,
5341
+ DEFAULT_CITATION_FORMAT,
5342
+ DEFAULT_GRACE_PERIOD_DAYS,
5343
+ DEFAULT_MAX_BINARY_SIZE_BYTES,
5344
+ DEFAULT_SCAN_PATTERNS,
5345
+ DEFAULT_TAXONOMY,
2447
5346
  EngramAccessHttpServer,
2448
5347
  EngramAccessInputError,
2449
5348
  EngramAccessService,
2450
5349
  EngramMcpServer,
5350
+ EnrichmentProviderRegistry,
2451
5351
  ExtractionEngine,
5352
+ FileCalendarSource,
5353
+ FilesystemBackend,
5354
+ HermesMemoryExtensionPublisher,
5355
+ LEGACY_PLUGIN_ID,
2452
5356
  LanceDbBackend,
5357
+ MARKETPLACE_MANIFEST_FILENAME,
5358
+ MARKETPLACE_SCHEMA_VERSION,
5359
+ MATERIALIZE_VERSION,
2453
5360
  MeilisearchBackend,
5361
+ NoneBackend,
2454
5362
  OramaBackend,
2455
5363
  Orchestrator,
5364
+ PLUGIN_ID,
5365
+ PUBLISHERS,
2456
5366
  QmdClient,
5367
+ REMNIC_CITATION_FORMAT,
5368
+ REMNIC_EXTENSIONS_TOTAL_TOKEN_LIMIT,
5369
+ REMNIC_MCP_TOOL_INVENTORY,
5370
+ REMNIC_RECALL_DECISION_RULES,
5371
+ REMNIC_SEMANTIC_OVERVIEW,
5372
+ SENTINEL_FILE,
2457
5373
  StorageManager,
5374
+ WebSearchProvider,
5375
+ appendAuditEntry,
5376
+ attachCitation,
5377
+ briefingFilename,
5378
+ buildBriefing,
5379
+ buildCitationGuidance,
2458
5380
  buildEntityRecallSection,
5381
+ buildExtensionsBlockForConsolidation,
5382
+ buildExtensionsFooterForSummary,
5383
+ buildProcedureMarkdownBody,
5384
+ buildProcedureRecallSection,
5385
+ checkMarketplaceManifest,
5386
+ clearBulkImportSources,
5387
+ clearTrainingExportAdapters,
5388
+ clearVerdictCache,
5389
+ coerceInstallExtension,
5390
+ convertMemoriesToRecords,
5391
+ createBackend,
2459
5392
  createSpace,
5393
+ createVerdictCache,
5394
+ createVersion,
2460
5395
  curate,
5396
+ decideSemanticDedup,
5397
+ defaultEnrichmentPipelineConfig,
2461
5398
  defaultWorkspaceDir,
2462
5399
  deleteSpace,
5400
+ deriveSessionId,
5401
+ describeMemoriesDir,
5402
+ diffVersions,
5403
+ discoverMemoryExtensions,
2463
5404
  doctorConnector,
5405
+ emptyManifest,
5406
+ ensureSentinel,
2464
5407
  findContradictions,
2465
5408
  findDuplicates,
5409
+ focusMatchesEntity,
5410
+ focusMatchesMemory,
5411
+ formatBatchTranscript,
5412
+ formatCitation,
5413
+ formatOaiMemCitation,
2466
5414
  formatZodError,
2467
5415
  generateContextTree,
5416
+ generateMarketplaceManifest,
5417
+ generateResolverDocument,
2468
5418
  generateToken,
2469
5419
  getActiveSpace,
2470
5420
  getAllValidTokens,
2471
5421
  getAllValidTokensCached,
2472
5422
  getAuditLog,
5423
+ getBulkImportSource,
2473
5424
  getManifestPath,
5425
+ getMemoryForActiveMemory,
2474
5426
  getSpacesDir,
5427
+ getTaxonomyDir,
5428
+ getTaxonomyFilePath,
5429
+ getTrainingExportAdapter,
5430
+ getVersion,
5431
+ hasBroadGraphIntent,
5432
+ hasCitation,
5433
+ hostIdForConnector,
5434
+ inferIntentFromText,
2475
5435
  initLogger,
2476
5436
  installConnector,
5437
+ installFromMarketplace,
5438
+ intentCompatibilityScore,
5439
+ isImportRole,
5440
+ isTaskInitiationIntent,
2477
5441
  isTrustZoneName,
5442
+ judgeFactDurability,
5443
+ listBulkImportSources,
2478
5444
  listConnectors,
2479
5445
  listReviewItems,
2480
5446
  listSpaces,
2481
5447
  listTokens,
5448
+ listTrainingExportAdapters,
5449
+ listVersions,
2482
5450
  loadDaySummaryPrompt,
2483
5451
  loadManifest,
2484
5452
  loadRegistry,
5453
+ loadTaxonomy,
2485
5454
  loadTokenStore,
2486
5455
  log,
5456
+ manifestDir,
5457
+ manifestPath,
5458
+ matchesPatterns,
5459
+ materializeForNamespace,
2487
5460
  memoryStoreRequestSchema,
2488
5461
  mergeSpaces,
2489
5462
  migrateFromEngram,
2490
5463
  observeRequestSchema,
2491
5464
  onboard,
5465
+ parseAllCitations,
5466
+ parseBriefingFocus,
5467
+ parseBriefingWindow,
5468
+ parseCitation,
2492
5469
  parseConfig,
5470
+ parseIsoTimestamp,
5471
+ parseOaiMemCitation,
5472
+ parseProcedureStepsFromBody,
5473
+ parseStrictCliDate,
2493
5474
  performReview,
5475
+ planRecallMode,
2494
5476
  promoteSpace,
5477
+ publisherFor,
5478
+ publisherForConnector,
2495
5479
  pullFromSpace,
2496
5480
  pushToSpace,
5481
+ readAuditLog,
5482
+ readManifest,
5483
+ recallForActiveMemory,
2497
5484
  recallRequestSchema,
5485
+ registerBulkImportSource,
5486
+ registerPublisher,
5487
+ registerTrainingExportAdapter,
2498
5488
  removeConnector,
5489
+ renderBriefingMarkdown,
5490
+ renderExtensionsBlock,
5491
+ renderExtensionsFooter,
5492
+ resolveBriefingSaveDir,
5493
+ resolveCategory,
2499
5494
  resolveConnectorFromToken,
5495
+ resolveExtensionsRoot,
5496
+ resolvePrincipal,
5497
+ resolveRemnicPluginEntry,
5498
+ revertToVersion,
2500
5499
  revokeToken,
2501
5500
  rollbackFromEngramMigration,
5501
+ runBinaryLifecyclePipeline,
5502
+ runBulkImportCliCommand,
5503
+ runBulkImportPipeline,
5504
+ runCodexMaterialize,
5505
+ runEnrichmentPipeline,
5506
+ runPostConsolidationMaterialize,
5507
+ sanitizeNoteForCitation,
2502
5508
  sanitizeSessionKeyForFilename,
2503
5509
  saveManifest,
2504
5510
  saveRegistry,
5511
+ saveTaxonomy,
2505
5512
  saveTokenStore,
5513
+ scanForBinaries,
2506
5514
  shareSpace,
5515
+ stripCitation,
2507
5516
  suggestionSubmitRequestSchema,
2508
5517
  switchSpace,
2509
5518
  syncChanges,
5519
+ validateBriefingFormat,
5520
+ validateImportTurn,
5521
+ validateMarketplaceManifest,
2510
5522
  validateRequest,
2511
- watchForChanges
5523
+ validateSlug,
5524
+ validateTaxonomy,
5525
+ verdictCacheSize,
5526
+ watchForChanges,
5527
+ writeManifest,
5528
+ writeMarketplaceManifest
2512
5529
  };
2513
5530
  //# sourceMappingURL=index.js.map