@remnic/core 10.0.92 → 10.0.93

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 (486) hide show
  1. package/dist/access-admin-ops-surface.js +331 -58
  2. package/dist/access-authorization-probe.js +2 -283
  3. package/dist/access-boundary.d.ts +1 -1
  4. package/dist/access-boundary.d.ts.map +1 -1
  5. package/dist/access-boundary.js +1 -282
  6. package/dist/access-cli.js +286 -302
  7. package/dist/access-cli.js.map +1 -1
  8. package/dist/access-coding-context-resolution.js +3 -3
  9. package/dist/access-extraction-force-flush.js +331 -58
  10. package/dist/access-http-briefing.js +331 -59
  11. package/dist/access-http-query.js +331 -60
  12. package/dist/access-http.js +329 -111
  13. package/dist/access-identity-continuity-surface.js +331 -58
  14. package/dist/access-lcm-surface.js +331 -58
  15. package/dist/access-mcp-frames.d.ts +20 -6
  16. package/dist/access-mcp-frames.d.ts.map +1 -1
  17. package/dist/access-mcp-frames.js +1 -1
  18. package/dist/access-mcp.d.ts +5 -0
  19. package/dist/access-mcp.d.ts.map +1 -1
  20. package/dist/access-mcp.js +329 -86
  21. package/dist/access-namespace-preflight.js +2 -282
  22. package/dist/access-observe-transcript.js +3 -3
  23. package/dist/access-observe-write-category.js +6 -6
  24. package/dist/access-observe-write-surface.js +331 -58
  25. package/dist/access-offline-manifest.js +12 -11
  26. package/dist/access-operations-batch.js +329 -63
  27. package/dist/access-operations.js +329 -72
  28. package/dist/access-recall-concurrency.js +331 -58
  29. package/dist/access-recall-diagnostics.js +5 -4
  30. package/dist/access-recall-response.js +331 -58
  31. package/dist/access-recall-surface.js +331 -58
  32. package/dist/access-schema.js +2 -2
  33. package/dist/access-service.js +334 -61
  34. package/dist/access-surface-catalog.js +6 -283
  35. package/dist/access-surface-catalog.js.map +1 -1
  36. package/dist/active-context-transform.js +1 -1
  37. package/dist/active-memory-bridge.js +3 -2
  38. package/dist/active-recall.js +35 -36
  39. package/dist/active-recall.js.map +1 -1
  40. package/dist/activity/journal-extract.d.ts +13 -1
  41. package/dist/activity/journal-extract.d.ts.map +1 -1
  42. package/dist/{auto-sync-3AZW7MXI.js → auto-sync-BO5YZKM6.js} +13 -15
  43. package/dist/{auto-sync-3AZW7MXI.js.map → auto-sync-BO5YZKM6.js.map} +1 -1
  44. package/dist/briefing.js +19 -19
  45. package/dist/buffer.js +16 -16
  46. package/dist/calibration.js +10 -10
  47. package/dist/{capsule-merge-2IIXFXN5.js → capsule-merge-6IDAYTKG.js} +6 -6
  48. package/dist/causal-consolidation.js +22 -22
  49. package/dist/{chunk-ATPWZ4F7.js → chunk-2LMA22UU.js} +5 -5
  50. package/dist/{chunk-RGUUBAT5.js → chunk-2WCLTWV2.js} +3 -3
  51. package/dist/{chunk-BQZEGOZ4.js → chunk-2YODOG2O.js} +2 -2
  52. package/dist/{chunk-WYG5UNDY.js → chunk-3AUDELZL.js} +2 -2
  53. package/dist/{chunk-N4KAE2NB.js → chunk-3CYOCE6U.js} +3 -3
  54. package/dist/{chunk-MB6ZJDN6.js → chunk-3FBAEPCU.js} +2 -2
  55. package/dist/{chunk-PIITB2YZ.js → chunk-3ZS3NUEI.js} +4 -4
  56. package/dist/{chunk-ANZZLRUX.js → chunk-4IVRQMZW.js} +2 -2
  57. package/dist/{chunk-63E37JCN.js → chunk-4RAJT5L2.js} +4 -4
  58. package/dist/{chunk-45VUHDV2.js → chunk-5CO5G4MN.js} +2 -2
  59. package/dist/{chunk-BRLXOZLM.js → chunk-5YT2IIFX.js} +729 -17
  60. package/dist/chunk-5YT2IIFX.js.map +1 -0
  61. package/dist/{chunk-E4IRW2FG.js → chunk-622V2AZA.js} +233 -40
  62. package/dist/chunk-622V2AZA.js.map +1 -0
  63. package/dist/{chunk-LUJCQQ3L.js → chunk-65HWAQ4F.js} +2 -2
  64. package/dist/{chunk-T67LUYCR.js → chunk-77NTFICT.js} +5 -5
  65. package/dist/chunk-A2WFUMUN.js +86 -0
  66. package/dist/chunk-A2WFUMUN.js.map +1 -0
  67. package/dist/{chunk-UE6RRXMF.js → chunk-A4F7OIC5.js} +2 -2
  68. package/dist/chunk-AHA5VQGP.js +117 -0
  69. package/dist/chunk-AHA5VQGP.js.map +1 -0
  70. package/dist/{chunk-SUSW74WU.js → chunk-ATG2PQ4V.js} +56 -5
  71. package/dist/chunk-ATG2PQ4V.js.map +1 -0
  72. package/dist/{chunk-U6UNF5ZS.js → chunk-B33YWUJV.js} +3 -3
  73. package/dist/{chunk-RRJBDXAQ.js → chunk-B377OKUP.js} +2 -2
  74. package/dist/{chunk-IUKYFTGW.js → chunk-B3DR2JBZ.js} +140 -125
  75. package/dist/chunk-B3DR2JBZ.js.map +1 -0
  76. package/dist/{chunk-ETZLQOUR.js → chunk-BGHCSLE5.js} +2 -2
  77. package/dist/{chunk-YYOMGOHB.js → chunk-BOK6BN5M.js} +2 -2
  78. package/dist/{chunk-OH7QLI6W.js → chunk-BPPUH5JM.js} +5 -5
  79. package/dist/{chunk-4J363OAA.js → chunk-BTI4C4MA.js} +2 -2
  80. package/dist/{chunk-O6ZSWHVR.js → chunk-C2KE5DMD.js} +4 -4
  81. package/dist/{chunk-47F6WWLQ.js → chunk-C73N66PR.js} +2 -2
  82. package/dist/{chunk-E3A7ELSG.js → chunk-CGJYL633.js} +2 -2
  83. package/dist/{chunk-WRT7GJ2D.js → chunk-CIWMTNHW.js} +4 -4
  84. package/dist/{chunk-O55EKIEC.js → chunk-D3ZKHLN2.js} +2 -2
  85. package/dist/{chunk-CD4BCCNP.js → chunk-DJ7WVEWX.js} +2 -2
  86. package/dist/{chunk-PWXPRVLK.js → chunk-EFL24KOI.js} +2 -2
  87. package/dist/{chunk-4CJTQHEG.js → chunk-ESLOLCD4.js} +3 -3
  88. package/dist/{chunk-6F42QONS.js → chunk-ETBVCRVL.js} +4 -4
  89. package/dist/{chunk-VILPVT4S.js → chunk-EZ47HOJ6.js} +4 -4
  90. package/dist/{chunk-EFYN5I7Z.js → chunk-F4QFU5IZ.js} +2 -2
  91. package/dist/{chunk-T6OZW2WR.js → chunk-FA7SLQ5X.js} +7 -1
  92. package/dist/chunk-FA7SLQ5X.js.map +1 -0
  93. package/dist/{chunk-7W3RXOIG.js → chunk-FMKILENU.js} +14 -14
  94. package/dist/{chunk-YLXETSAH.js → chunk-GCZJ7XRI.js} +2 -2
  95. package/dist/{chunk-5HFXGXE5.js → chunk-GQXOBKPL.js} +7 -9
  96. package/dist/{chunk-5HFXGXE5.js.map → chunk-GQXOBKPL.js.map} +1 -1
  97. package/dist/{chunk-UDKWFDV2.js → chunk-HLUHEMN4.js} +27 -3
  98. package/dist/chunk-HLUHEMN4.js.map +1 -0
  99. package/dist/{chunk-QFEDITXM.js → chunk-JNDMIAJ4.js} +2 -2
  100. package/dist/chunk-JTSSSR4L.js +359 -0
  101. package/dist/chunk-JTSSSR4L.js.map +1 -0
  102. package/dist/{chunk-5KBIOI4G.js → chunk-JVBAPAWZ.js} +217 -13
  103. package/dist/chunk-JVBAPAWZ.js.map +1 -0
  104. package/dist/{chunk-E5PPE4VA.js → chunk-K2VRKCPA.js} +2 -2
  105. package/dist/{chunk-MKRYQROF.js → chunk-LGEHAQHK.js} +4 -4
  106. package/dist/{chunk-7HTUYFR3.js → chunk-LLPMMMWG.js} +27 -10
  107. package/dist/chunk-LLPMMMWG.js.map +1 -0
  108. package/dist/{chunk-XYTQJD7F.js → chunk-LMJVP5CM.js} +2 -2
  109. package/dist/{chunk-FQKBJJYB.js → chunk-LPV72KRQ.js} +2 -2
  110. package/dist/{chunk-2LCRG2YV.js → chunk-M2XR6B6V.js} +2 -2
  111. package/dist/{chunk-P4WNBSTW.js → chunk-MH632KPO.js} +2 -2
  112. package/dist/{chunk-EQ4U73UH.js → chunk-MLAMXQAW.js} +14 -2
  113. package/dist/chunk-MLAMXQAW.js.map +1 -0
  114. package/dist/{chunk-ZU5JDLOH.js → chunk-MMRTPH3J.js} +2 -2
  115. package/dist/{chunk-PMJFRDJI.js → chunk-MUX7IL77.js} +2 -2
  116. package/dist/{chunk-HF3BKUVV.js → chunk-NAQ4A5IX.js} +7 -7
  117. package/dist/{chunk-TVIVY7KU.js → chunk-NED7IR7B.js} +4 -4
  118. package/dist/{chunk-6F5MJFXY.js → chunk-NHVAX7MH.js} +2 -2
  119. package/dist/{chunk-TG3R2SID.js → chunk-NMGHGTVR.js} +5 -5
  120. package/dist/{chunk-YCNIGRRF.js → chunk-NS7U4ZYU.js} +13 -117
  121. package/dist/chunk-NS7U4ZYU.js.map +1 -0
  122. package/dist/{chunk-NGHDW6UU.js → chunk-NUQEREIP.js} +2 -2
  123. package/dist/{chunk-5IIQU4JZ.js → chunk-OB72KNMF.js} +4 -4
  124. package/dist/{chunk-JV4S6VDH.js → chunk-OJXJWNFY.js} +2 -2
  125. package/dist/{chunk-QJPJXCS4.js → chunk-P3WRNWWM.js} +157 -4
  126. package/dist/chunk-P3WRNWWM.js.map +1 -0
  127. package/dist/{chunk-E6M225BJ.js → chunk-PC5DRBGD.js} +18 -8
  128. package/dist/chunk-PC5DRBGD.js.map +1 -0
  129. package/dist/{chunk-Z7JIZ42P.js → chunk-PRAUN6PN.js} +3 -3
  130. package/dist/{chunk-RWGRSKN5.js → chunk-PT4DZDJA.js} +2 -2
  131. package/dist/{chunk-7EPY3IQL.js → chunk-PZJQ46DH.js} +2 -2
  132. package/dist/{chunk-MXHMNKWG.js → chunk-QDESY76F.js} +3 -3
  133. package/dist/{chunk-I6QGI65T.js → chunk-QRMG4HK4.js} +3 -3
  134. package/dist/{chunk-ZU3NJNO7.js → chunk-QSF5SJAM.js} +4 -4
  135. package/dist/{chunk-5IYOKDM4.js → chunk-R23JWJD3.js} +5 -5
  136. package/dist/{chunk-RHQLIHMB.js → chunk-R4BBGPI2.js} +218 -218
  137. package/dist/chunk-R4BBGPI2.js.map +1 -0
  138. package/dist/{chunk-7GLJMDEQ.js → chunk-RM2YUFH6.js} +6 -6
  139. package/dist/{chunk-IPT2OY4B.js → chunk-RQFXZYTF.js} +35 -33
  140. package/dist/chunk-RQFXZYTF.js.map +1 -0
  141. package/dist/{chunk-AP6NCP3V.js → chunk-S3S72HRM.js} +8 -6
  142. package/dist/chunk-S3S72HRM.js.map +1 -0
  143. package/dist/{chunk-RIMK327Y.js → chunk-SGQPR5SX.js} +7 -7
  144. package/dist/{chunk-ZGFLIK5H.js → chunk-SPAKBUJY.js} +2 -2
  145. package/dist/{chunk-ER7XANP3.js → chunk-SQ3GEIEL.js} +4 -4
  146. package/dist/{chunk-BLJT6JP2.js → chunk-SVT5IA2H.js} +2 -2
  147. package/dist/{chunk-HVX4LPNY.js → chunk-T4KY6UYF.js} +5 -3
  148. package/dist/{chunk-HVX4LPNY.js.map → chunk-T4KY6UYF.js.map} +1 -1
  149. package/dist/{chunk-GVGAO3WV.js → chunk-TJ4F6H4H.js} +3 -3
  150. package/dist/{chunk-R6HMAIKM.js → chunk-TOSPNM4T.js} +2 -2
  151. package/dist/{chunk-HH5LY6RR.js → chunk-TRQE3RYR.js} +5 -5
  152. package/dist/{chunk-RMXS4I66.js → chunk-TVDJEUV5.js} +2 -2
  153. package/dist/{chunk-PLCYKAWH.js → chunk-TYRWSE4B.js} +2 -2
  154. package/dist/{chunk-B4QYQFGY.js → chunk-UBEKET2V.js} +383 -36
  155. package/dist/chunk-UBEKET2V.js.map +1 -0
  156. package/dist/{chunk-IQWUACVG.js → chunk-UHMHJX5G.js} +3 -3
  157. package/dist/{chunk-YODUDA6R.js → chunk-UN4RVUG2.js} +22 -22
  158. package/dist/{chunk-E6Z4FBX6.js → chunk-V2QSNFCT.js} +2 -2
  159. package/dist/{chunk-NBH6K6G5.js → chunk-V65H7MI6.js} +345 -16
  160. package/dist/chunk-V65H7MI6.js.map +1 -0
  161. package/dist/chunk-W54Z2MOP.js +4500 -0
  162. package/dist/chunk-W54Z2MOP.js.map +1 -0
  163. package/dist/{chunk-E7YZXWIS.js → chunk-WKSQTMU7.js} +2 -2
  164. package/dist/{chunk-RIZ5NUNN.js → chunk-WQ4XKIOX.js} +4 -4
  165. package/dist/{chunk-FSO3G4JJ.js → chunk-WQURALGV.js} +4995 -2747
  166. package/dist/chunk-WQURALGV.js.map +1 -0
  167. package/dist/{chunk-QEYTRFR3.js → chunk-X26YOFGS.js} +2 -2
  168. package/dist/{chunk-ITHZFHST.js → chunk-X2BVB74H.js} +2 -2
  169. package/dist/{chunk-XYKBWTWI.js → chunk-XIYLWEXC.js} +2 -2
  170. package/dist/{chunk-IBJ6U753.js → chunk-XKZBZOKV.js} +4 -4
  171. package/dist/{chunk-65AUYLU3.js → chunk-XPXHQI6N.js} +2 -2
  172. package/dist/{chunk-EXHXJETE.js → chunk-XRDYRWZI.js} +2 -2
  173. package/dist/chunk-XRWWCW4H.js +102 -0
  174. package/dist/chunk-XRWWCW4H.js.map +1 -0
  175. package/dist/chunk-YKJBSMMP.js +67137 -0
  176. package/dist/chunk-YKJBSMMP.js.map +1 -0
  177. package/dist/{chunk-2JSDPUKD.js → chunk-YMFPFXEX.js} +2 -2
  178. package/dist/{chunk-645WOU5I.js → chunk-ZDZJNXL6.js} +4 -4
  179. package/dist/{chunk-6TCXIRP5.js → chunk-ZKE4CO6B.js} +4 -4
  180. package/dist/{chunk-FNXQZGUS.js → chunk-ZSE774RV.js} +2 -2
  181. package/dist/cli.js +302 -184
  182. package/dist/coding/export-okf-codegraph.js +28 -28
  183. package/dist/compounding/engine.js +22 -23
  184. package/dist/config.js +35 -36
  185. package/dist/connectors/codex-materialize-runner.js +19 -19
  186. package/dist/connectors/index.js +20 -20
  187. package/dist/connectors/live/google-drive.d.ts +2 -0
  188. package/dist/connectors/live/google-drive.d.ts.map +1 -1
  189. package/dist/consolidation-provenance-check.js +4 -4
  190. package/dist/consolidation-undo.js +5 -5
  191. package/dist/contradiction/index.js +7 -6
  192. package/dist/{contradiction-scan-P2MHYFVG.js → contradiction-scan-IBPYGQZF.js} +4 -3
  193. package/dist/convergence-refresh.js +21 -21
  194. package/dist/corpus-watermark.js +22 -22
  195. package/dist/deep-recall-http-glue.js +2 -2
  196. package/dist/deep-recall-policy-llm.js +4 -4
  197. package/dist/direct-answer-wiring.js +2 -2
  198. package/dist/embedding-fallback.js +1 -1
  199. package/dist/entity-retrieval.js +20 -20
  200. package/dist/eval-trajectory-store.js +2 -2
  201. package/dist/eval-trajectory.js +2 -2
  202. package/dist/event-order-recall.js +2 -2
  203. package/dist/explicit-capture.d.ts +3 -2
  204. package/dist/explicit-capture.d.ts.map +1 -1
  205. package/dist/explicit-capture.js +530 -6
  206. package/dist/explicit-cue-recall.js +2 -2
  207. package/dist/external-wiki-access.js +2 -283
  208. package/dist/extraction-faithfulness.js +4 -4
  209. package/dist/extraction-judge.js +5 -5
  210. package/dist/extraction-normalization.js +1 -1
  211. package/dist/extraction-quote-grounding.d.ts.map +1 -1
  212. package/dist/extraction-quote-grounding.js +1 -1
  213. package/dist/extraction-source-grounding-rules.js +2 -2
  214. package/dist/extraction-source-grounding.js +3 -3
  215. package/dist/extraction.js +42 -43
  216. package/dist/fallback-llm-codex-subscription.js +2 -2
  217. package/dist/fallback-llm.js +3 -3
  218. package/dist/{first-start-migration-QFI7B5SH.js → first-start-migration-FILJH3RO.js} +6 -5
  219. package/dist/{first-start-migration-QFI7B5SH.js.map → first-start-migration-FILJH3RO.js.map} +1 -1
  220. package/dist/focused-list-recall.js +2 -2
  221. package/dist/harmonic-retrieval.js +19 -19
  222. package/dist/index.js +772 -793
  223. package/dist/lcm/index.js +2 -2
  224. package/dist/live-connectors-runner.js +2 -3
  225. package/dist/local-llm-config.js +2 -2
  226. package/dist/local-llm-helpers.d.ts +4 -0
  227. package/dist/local-llm-helpers.d.ts.map +1 -1
  228. package/dist/local-llm-helpers.js +3 -1
  229. package/dist/local-llm.d.ts +4 -1
  230. package/dist/local-llm.d.ts.map +1 -1
  231. package/dist/local-llm.js +4 -4
  232. package/dist/location/index.js +26 -28
  233. package/dist/maintenance/memory-governance.js +19 -19
  234. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +19 -19
  235. package/dist/maintenance/rebuild-memory-projection-runner.js +21 -21
  236. package/dist/maintenance/rebuild-memory-projection.js +20 -20
  237. package/dist/memory-browse-operations.js +2 -283
  238. package/dist/memory-browse.js +8 -7
  239. package/dist/memory-controller.js +2 -2
  240. package/dist/memory-subject.js +2 -2
  241. package/dist/namespaces/migrate.js +20 -20
  242. package/dist/namespaces/search.js +5 -5
  243. package/dist/namespaces/storage.js +19 -19
  244. package/dist/offline-sync-changeset-generations.js +1 -1
  245. package/dist/offline-sync-embedding-generation.js +1 -1
  246. package/dist/offline-sync-impression-drain.js +6 -6
  247. package/dist/offline-sync-snapshot-stream.js +1 -1
  248. package/dist/offline-sync.js +1 -1
  249. package/dist/operator-doctor-corpus.js +23 -23
  250. package/dist/operator-toolkit.js +57 -60
  251. package/dist/orchestration/backlog-scheduler.js +277 -293
  252. package/dist/orchestration/backlog-scheduler.js.map +1 -1
  253. package/dist/orchestration/buffer-drain-runner.js +277 -293
  254. package/dist/orchestration/extraction-run.d.ts +4 -0
  255. package/dist/orchestration/extraction-run.d.ts.map +1 -1
  256. package/dist/orchestration/fact-dedup-window.d.ts +1 -1
  257. package/dist/orchestration/fact-dedup-window.d.ts.map +1 -1
  258. package/dist/orchestration/maintenance.js +38 -41
  259. package/dist/orchestrator.d.ts +9 -11
  260. package/dist/orchestrator.d.ts.map +1 -1
  261. package/dist/orchestrator.js +289 -306
  262. package/dist/page-versioning.js +3 -3
  263. package/dist/qmd-daemon-session.js +2 -2
  264. package/dist/qmd.js +3 -3
  265. package/dist/recall-explain-renderer.js +1 -1
  266. package/dist/recall-navigation-operations.js +2 -283
  267. package/dist/recall-navigation.js +331 -58
  268. package/dist/recall-planner-llm.js +3 -3
  269. package/dist/recall-state-view-admission.js +6 -6
  270. package/dist/recall-why-service.js +10 -9
  271. package/dist/recall-why.js +4 -3
  272. package/dist/recall-xray-cli.js +2 -2
  273. package/dist/recall-xray-renderer.js +1 -1
  274. package/dist/reconcile/manifest.js +3 -3
  275. package/dist/{resolution-N7H3F2E4.js → resolution-OVVIF7ZY.js} +8 -7
  276. package/dist/response-guidance-recall.js +2 -2
  277. package/dist/resume-bundles.js +39 -40
  278. package/dist/salvage-envelope.js +4 -4
  279. package/dist/search/factory.js +4 -4
  280. package/dist/search/index.js +4 -4
  281. package/dist/semantic-consolidation.js +22 -22
  282. package/dist/semantic-rule-promotion.js +19 -19
  283. package/dist/semantic-rule-verifier.js +19 -19
  284. package/dist/shared-context/manager.js +2 -3
  285. package/dist/source-agent-qualifier.js +31 -32
  286. package/dist/standup/build.js +19 -19
  287. package/dist/storage/content-hash-index.d.ts +10 -0
  288. package/dist/storage/content-hash-index.d.ts.map +1 -1
  289. package/dist/storage.js +18 -18
  290. package/dist/subject-config.js +3 -3
  291. package/dist/summarizer.d.ts +4 -1
  292. package/dist/summarizer.d.ts.map +1 -1
  293. package/dist/summarizer.js +6 -6
  294. package/dist/support-passport/index.js +53 -297
  295. package/dist/targeted-fact-recall.js +2 -2
  296. package/dist/tier-routing.js +3 -2
  297. package/dist/{tier-stats-FQGQSFET.js → tier-stats-FXEUJN73.js} +6 -5
  298. package/dist/{tier-stats-FQGQSFET.js.map → tier-stats-FXEUJN73.js.map} +1 -1
  299. package/dist/transcript.js +2 -2
  300. package/dist/transfer/capsule-import.js +4 -4
  301. package/dist/transfer/export-okf.js +19 -19
  302. package/dist/verified-recall.js +19 -19
  303. package/dist/wearables/memory-gen.d.ts +5 -1
  304. package/dist/wearables/memory-gen.d.ts.map +1 -1
  305. package/dist/wearables/service.d.ts.map +1 -1
  306. package/dist/who-knows-cli.js +20 -20
  307. package/dist/who-knows.js +19 -19
  308. package/dist/write-envelope.js +3 -3
  309. package/package.json +2 -2
  310. package/src/access-boundary.ts +5 -1
  311. package/src/access-category-alias.test.ts +5 -0
  312. package/src/access-mcp-frames.test.ts +60 -5
  313. package/src/access-mcp-frames.ts +72 -18
  314. package/src/access-mcp.test.ts +66 -2
  315. package/src/access-mcp.ts +19 -13
  316. package/src/access-service-coding-write.test.ts +7 -0
  317. package/src/activity/journal-extract-storage.test.ts +31 -0
  318. package/src/activity/journal-extract.test.ts +1 -0
  319. package/src/activity/journal-extract.ts +24 -3
  320. package/src/connectors/live/google-drive.test.ts +39 -0
  321. package/src/connectors/live/google-drive.ts +41 -12
  322. package/src/explicit-capture.test.ts +109 -17
  323. package/src/explicit-capture.ts +90 -37
  324. package/src/extraction-quote-grounding.test.ts +20 -0
  325. package/src/extraction-quote-grounding.ts +21 -11
  326. package/src/fact-dedup-window.test.ts +37 -0
  327. package/src/local-llm-helpers.ts +12 -0
  328. package/src/local-llm-shutdown.test.ts +194 -0
  329. package/src/local-llm.ts +32 -30
  330. package/src/orchestration/extraction-run.ts +12 -3
  331. package/src/orchestration/fact-dedup-window.ts +5 -1
  332. package/src/orchestrator-connector-dedup.test.ts +9 -5
  333. package/src/orchestrator.ts +47 -50
  334. package/src/storage/content-hash-index.ts +27 -2
  335. package/src/storage-fact-hash-publish-failure.test.ts +87 -0
  336. package/src/summarizer-shutdown.test.ts +195 -0
  337. package/src/summarizer.ts +15 -2
  338. package/src/wearables/memory-gen.test.ts +20 -0
  339. package/src/wearables/memory-gen.ts +14 -7
  340. package/src/wearables/service.ts +14 -1
  341. package/src/wearables/storage-io.test.ts +25 -0
  342. package/dist/chunk-2FGLEF7C.js +0 -61
  343. package/dist/chunk-2FGLEF7C.js.map +0 -1
  344. package/dist/chunk-3RLK65ZK.js +0 -203
  345. package/dist/chunk-3RLK65ZK.js.map +0 -1
  346. package/dist/chunk-4WR2OD7K.js +0 -43
  347. package/dist/chunk-4WR2OD7K.js.map +0 -1
  348. package/dist/chunk-5KBIOI4G.js.map +0 -1
  349. package/dist/chunk-5M5M22GW.js +0 -346
  350. package/dist/chunk-5M5M22GW.js.map +0 -1
  351. package/dist/chunk-7HTUYFR3.js.map +0 -1
  352. package/dist/chunk-AP6NCP3V.js.map +0 -1
  353. package/dist/chunk-B4QYQFGY.js.map +0 -1
  354. package/dist/chunk-BJRTMNCB.js +0 -1919
  355. package/dist/chunk-BJRTMNCB.js.map +0 -1
  356. package/dist/chunk-BRLXOZLM.js.map +0 -1
  357. package/dist/chunk-BSACVS6R.js +0 -621
  358. package/dist/chunk-BSACVS6R.js.map +0 -1
  359. package/dist/chunk-DKAA2IHQ.js +0 -3644
  360. package/dist/chunk-DKAA2IHQ.js.map +0 -1
  361. package/dist/chunk-E4IRW2FG.js.map +0 -1
  362. package/dist/chunk-E6M225BJ.js.map +0 -1
  363. package/dist/chunk-EQ4U73UH.js.map +0 -1
  364. package/dist/chunk-ETSJD5IV.js +0 -361
  365. package/dist/chunk-ETSJD5IV.js.map +0 -1
  366. package/dist/chunk-F55YQP7N.js +0 -336
  367. package/dist/chunk-F55YQP7N.js.map +0 -1
  368. package/dist/chunk-FHGWKODN.js +0 -2300
  369. package/dist/chunk-FHGWKODN.js.map +0 -1
  370. package/dist/chunk-FLRP63GU.js +0 -32219
  371. package/dist/chunk-FLRP63GU.js.map +0 -1
  372. package/dist/chunk-FSO3G4JJ.js.map +0 -1
  373. package/dist/chunk-ID6JGIMD.js +0 -25
  374. package/dist/chunk-ID6JGIMD.js.map +0 -1
  375. package/dist/chunk-IGLJNHOX.js +0 -62
  376. package/dist/chunk-IGLJNHOX.js.map +0 -1
  377. package/dist/chunk-IPT2OY4B.js.map +0 -1
  378. package/dist/chunk-IUKYFTGW.js.map +0 -1
  379. package/dist/chunk-NBH6K6G5.js.map +0 -1
  380. package/dist/chunk-NTRUKAAE.js +0 -223
  381. package/dist/chunk-NTRUKAAE.js.map +0 -1
  382. package/dist/chunk-OGXXGHW6.js +0 -223
  383. package/dist/chunk-OGXXGHW6.js.map +0 -1
  384. package/dist/chunk-PPCLZ4JU.js +0 -28
  385. package/dist/chunk-PPCLZ4JU.js.map +0 -1
  386. package/dist/chunk-QJPJXCS4.js.map +0 -1
  387. package/dist/chunk-R2LBF4HH.js +0 -729
  388. package/dist/chunk-R2LBF4HH.js.map +0 -1
  389. package/dist/chunk-RHQLIHMB.js.map +0 -1
  390. package/dist/chunk-RRTVGD3J.js +0 -792
  391. package/dist/chunk-RRTVGD3J.js.map +0 -1
  392. package/dist/chunk-SUSW74WU.js.map +0 -1
  393. package/dist/chunk-T6OZW2WR.js.map +0 -1
  394. package/dist/chunk-UDKWFDV2.js.map +0 -1
  395. package/dist/chunk-V7IZKJI3.js +0 -22
  396. package/dist/chunk-V7IZKJI3.js.map +0 -1
  397. package/dist/chunk-X562NQFA.js +0 -10144
  398. package/dist/chunk-X562NQFA.js.map +0 -1
  399. package/dist/chunk-XDSCJOH7.js +0 -3991
  400. package/dist/chunk-XDSCJOH7.js.map +0 -1
  401. package/dist/chunk-YCNIGRRF.js.map +0 -1
  402. package/dist/chunk-ZTTCDQ4I.js +0 -18796
  403. package/dist/chunk-ZTTCDQ4I.js.map +0 -1
  404. /package/dist/{capsule-merge-2IIXFXN5.js.map → capsule-merge-6IDAYTKG.js.map} +0 -0
  405. /package/dist/{chunk-ATPWZ4F7.js.map → chunk-2LMA22UU.js.map} +0 -0
  406. /package/dist/{chunk-RGUUBAT5.js.map → chunk-2WCLTWV2.js.map} +0 -0
  407. /package/dist/{chunk-BQZEGOZ4.js.map → chunk-2YODOG2O.js.map} +0 -0
  408. /package/dist/{chunk-WYG5UNDY.js.map → chunk-3AUDELZL.js.map} +0 -0
  409. /package/dist/{chunk-N4KAE2NB.js.map → chunk-3CYOCE6U.js.map} +0 -0
  410. /package/dist/{chunk-MB6ZJDN6.js.map → chunk-3FBAEPCU.js.map} +0 -0
  411. /package/dist/{chunk-PIITB2YZ.js.map → chunk-3ZS3NUEI.js.map} +0 -0
  412. /package/dist/{chunk-ANZZLRUX.js.map → chunk-4IVRQMZW.js.map} +0 -0
  413. /package/dist/{chunk-63E37JCN.js.map → chunk-4RAJT5L2.js.map} +0 -0
  414. /package/dist/{chunk-45VUHDV2.js.map → chunk-5CO5G4MN.js.map} +0 -0
  415. /package/dist/{chunk-LUJCQQ3L.js.map → chunk-65HWAQ4F.js.map} +0 -0
  416. /package/dist/{chunk-T67LUYCR.js.map → chunk-77NTFICT.js.map} +0 -0
  417. /package/dist/{chunk-UE6RRXMF.js.map → chunk-A4F7OIC5.js.map} +0 -0
  418. /package/dist/{chunk-U6UNF5ZS.js.map → chunk-B33YWUJV.js.map} +0 -0
  419. /package/dist/{chunk-RRJBDXAQ.js.map → chunk-B377OKUP.js.map} +0 -0
  420. /package/dist/{chunk-ETZLQOUR.js.map → chunk-BGHCSLE5.js.map} +0 -0
  421. /package/dist/{chunk-YYOMGOHB.js.map → chunk-BOK6BN5M.js.map} +0 -0
  422. /package/dist/{chunk-OH7QLI6W.js.map → chunk-BPPUH5JM.js.map} +0 -0
  423. /package/dist/{chunk-4J363OAA.js.map → chunk-BTI4C4MA.js.map} +0 -0
  424. /package/dist/{chunk-O6ZSWHVR.js.map → chunk-C2KE5DMD.js.map} +0 -0
  425. /package/dist/{chunk-47F6WWLQ.js.map → chunk-C73N66PR.js.map} +0 -0
  426. /package/dist/{chunk-E3A7ELSG.js.map → chunk-CGJYL633.js.map} +0 -0
  427. /package/dist/{chunk-WRT7GJ2D.js.map → chunk-CIWMTNHW.js.map} +0 -0
  428. /package/dist/{chunk-O55EKIEC.js.map → chunk-D3ZKHLN2.js.map} +0 -0
  429. /package/dist/{chunk-CD4BCCNP.js.map → chunk-DJ7WVEWX.js.map} +0 -0
  430. /package/dist/{chunk-PWXPRVLK.js.map → chunk-EFL24KOI.js.map} +0 -0
  431. /package/dist/{chunk-4CJTQHEG.js.map → chunk-ESLOLCD4.js.map} +0 -0
  432. /package/dist/{chunk-6F42QONS.js.map → chunk-ETBVCRVL.js.map} +0 -0
  433. /package/dist/{chunk-VILPVT4S.js.map → chunk-EZ47HOJ6.js.map} +0 -0
  434. /package/dist/{chunk-EFYN5I7Z.js.map → chunk-F4QFU5IZ.js.map} +0 -0
  435. /package/dist/{chunk-7W3RXOIG.js.map → chunk-FMKILENU.js.map} +0 -0
  436. /package/dist/{chunk-YLXETSAH.js.map → chunk-GCZJ7XRI.js.map} +0 -0
  437. /package/dist/{chunk-QFEDITXM.js.map → chunk-JNDMIAJ4.js.map} +0 -0
  438. /package/dist/{chunk-E5PPE4VA.js.map → chunk-K2VRKCPA.js.map} +0 -0
  439. /package/dist/{chunk-MKRYQROF.js.map → chunk-LGEHAQHK.js.map} +0 -0
  440. /package/dist/{chunk-XYTQJD7F.js.map → chunk-LMJVP5CM.js.map} +0 -0
  441. /package/dist/{chunk-FQKBJJYB.js.map → chunk-LPV72KRQ.js.map} +0 -0
  442. /package/dist/{chunk-2LCRG2YV.js.map → chunk-M2XR6B6V.js.map} +0 -0
  443. /package/dist/{chunk-P4WNBSTW.js.map → chunk-MH632KPO.js.map} +0 -0
  444. /package/dist/{chunk-ZU5JDLOH.js.map → chunk-MMRTPH3J.js.map} +0 -0
  445. /package/dist/{chunk-PMJFRDJI.js.map → chunk-MUX7IL77.js.map} +0 -0
  446. /package/dist/{chunk-HF3BKUVV.js.map → chunk-NAQ4A5IX.js.map} +0 -0
  447. /package/dist/{chunk-TVIVY7KU.js.map → chunk-NED7IR7B.js.map} +0 -0
  448. /package/dist/{chunk-6F5MJFXY.js.map → chunk-NHVAX7MH.js.map} +0 -0
  449. /package/dist/{chunk-TG3R2SID.js.map → chunk-NMGHGTVR.js.map} +0 -0
  450. /package/dist/{chunk-NGHDW6UU.js.map → chunk-NUQEREIP.js.map} +0 -0
  451. /package/dist/{chunk-5IIQU4JZ.js.map → chunk-OB72KNMF.js.map} +0 -0
  452. /package/dist/{chunk-JV4S6VDH.js.map → chunk-OJXJWNFY.js.map} +0 -0
  453. /package/dist/{chunk-Z7JIZ42P.js.map → chunk-PRAUN6PN.js.map} +0 -0
  454. /package/dist/{chunk-RWGRSKN5.js.map → chunk-PT4DZDJA.js.map} +0 -0
  455. /package/dist/{chunk-7EPY3IQL.js.map → chunk-PZJQ46DH.js.map} +0 -0
  456. /package/dist/{chunk-MXHMNKWG.js.map → chunk-QDESY76F.js.map} +0 -0
  457. /package/dist/{chunk-I6QGI65T.js.map → chunk-QRMG4HK4.js.map} +0 -0
  458. /package/dist/{chunk-ZU3NJNO7.js.map → chunk-QSF5SJAM.js.map} +0 -0
  459. /package/dist/{chunk-5IYOKDM4.js.map → chunk-R23JWJD3.js.map} +0 -0
  460. /package/dist/{chunk-7GLJMDEQ.js.map → chunk-RM2YUFH6.js.map} +0 -0
  461. /package/dist/{chunk-RIMK327Y.js.map → chunk-SGQPR5SX.js.map} +0 -0
  462. /package/dist/{chunk-ZGFLIK5H.js.map → chunk-SPAKBUJY.js.map} +0 -0
  463. /package/dist/{chunk-ER7XANP3.js.map → chunk-SQ3GEIEL.js.map} +0 -0
  464. /package/dist/{chunk-BLJT6JP2.js.map → chunk-SVT5IA2H.js.map} +0 -0
  465. /package/dist/{chunk-GVGAO3WV.js.map → chunk-TJ4F6H4H.js.map} +0 -0
  466. /package/dist/{chunk-R6HMAIKM.js.map → chunk-TOSPNM4T.js.map} +0 -0
  467. /package/dist/{chunk-HH5LY6RR.js.map → chunk-TRQE3RYR.js.map} +0 -0
  468. /package/dist/{chunk-RMXS4I66.js.map → chunk-TVDJEUV5.js.map} +0 -0
  469. /package/dist/{chunk-PLCYKAWH.js.map → chunk-TYRWSE4B.js.map} +0 -0
  470. /package/dist/{chunk-IQWUACVG.js.map → chunk-UHMHJX5G.js.map} +0 -0
  471. /package/dist/{chunk-YODUDA6R.js.map → chunk-UN4RVUG2.js.map} +0 -0
  472. /package/dist/{chunk-E6Z4FBX6.js.map → chunk-V2QSNFCT.js.map} +0 -0
  473. /package/dist/{chunk-E7YZXWIS.js.map → chunk-WKSQTMU7.js.map} +0 -0
  474. /package/dist/{chunk-RIZ5NUNN.js.map → chunk-WQ4XKIOX.js.map} +0 -0
  475. /package/dist/{chunk-QEYTRFR3.js.map → chunk-X26YOFGS.js.map} +0 -0
  476. /package/dist/{chunk-ITHZFHST.js.map → chunk-X2BVB74H.js.map} +0 -0
  477. /package/dist/{chunk-XYKBWTWI.js.map → chunk-XIYLWEXC.js.map} +0 -0
  478. /package/dist/{chunk-IBJ6U753.js.map → chunk-XKZBZOKV.js.map} +0 -0
  479. /package/dist/{chunk-65AUYLU3.js.map → chunk-XPXHQI6N.js.map} +0 -0
  480. /package/dist/{chunk-EXHXJETE.js.map → chunk-XRDYRWZI.js.map} +0 -0
  481. /package/dist/{chunk-2JSDPUKD.js.map → chunk-YMFPFXEX.js.map} +0 -0
  482. /package/dist/{chunk-645WOU5I.js.map → chunk-ZDZJNXL6.js.map} +0 -0
  483. /package/dist/{chunk-6TCXIRP5.js.map → chunk-ZKE4CO6B.js.map} +0 -0
  484. /package/dist/{chunk-FNXQZGUS.js.map → chunk-ZSE774RV.js.map} +0 -0
  485. /package/dist/{contradiction-scan-P2MHYFVG.js.map → contradiction-scan-IBPYGQZF.js.map} +0 -0
  486. /package/dist/{resolution-N7H3F2E4.js.map → resolution-OVVIF7ZY.js.map} +0 -0
@@ -1,28 +1,41 @@
1
1
  /**
2
- * Content-Length framing for the MCP stdio transport.
2
+ * Framing for the MCP stdio transport.
3
3
  *
4
- * `push` appends raw bytes. `next` returns one frame at a time and `null` when
5
- * the buffered bytes hold no complete frame yet. The header and the body are
6
- * each bounded to MCP_STDIO_MAX_FRAME_BYTES, which matches the HTTP access
7
- * default `maxBodyBytes`, so a peer cannot grow the buffer without limit.
4
+ * The MCP spec sends one JSON message per line. Some clients send a
5
+ * `Content-Length` header instead. `push` appends raw bytes. `next` returns one
6
+ * frame at a time and `null` when the buffered bytes hold no complete frame
7
+ * yet. A buffer whose first non-blank byte is `{` or `[` is read up to the next
8
+ * newline. Any other buffer is a header block. Each form is bounded to
9
+ * MCP_STDIO_MAX_FRAME_BYTES, so a peer cannot grow the buffer without limit.
8
10
  */
9
11
  export const MCP_STDIO_MAX_FRAME_BYTES = 131072;
10
12
 
11
- export type McpFrame = { kind: "body"; text: string } | { kind: "too-large" };
13
+ export type McpFrameForm = "header" | "line";
12
14
 
13
- const TOO_LARGE: McpFrame = { kind: "too-large" };
15
+ export type McpFrame =
16
+ | { kind: "body"; text: string; form: McpFrameForm }
17
+ | { kind: "too-large"; form: McpFrameForm };
18
+
19
+ const HEADER_PREFIX = "content-length:";
14
20
 
15
21
  export class McpFrameReader {
16
22
  private buffer = Buffer.alloc(0);
17
- /** Body bytes of a refused oversized frame that have not arrived yet. */
23
+ /** Body bytes of a refused oversized header frame that have not arrived yet. */
18
24
  private skipBytes = 0;
25
+ /** True while the rest of a refused oversized line has not arrived yet. */
26
+ private skipLine = false;
19
27
  /** Buffer length when `next` last stopped on an incomplete frame. */
20
28
  private waitingBytes = 0;
21
29
 
22
30
  push(chunk: Buffer): void {
23
31
  const dropped = Math.min(this.skipBytes, chunk.length);
24
32
  this.skipBytes -= dropped;
25
- const kept = chunk.subarray(dropped);
33
+ let kept = chunk.subarray(dropped);
34
+ if (this.skipLine) {
35
+ const newline = kept.indexOf("\n");
36
+ this.skipLine = newline < 0;
37
+ kept = kept.subarray(newline < 0 ? kept.length : newline + 1);
38
+ }
26
39
  if (kept.length > 0) this.buffer = Buffer.concat([this.buffer, kept]);
27
40
  }
28
41
 
@@ -33,15 +46,33 @@ export class McpFrameReader {
33
46
 
34
47
  next(): McpFrame | null {
35
48
  this.waitingBytes = 0;
49
+ // A blank line carries no message. Skip it and read the next frame, so a
50
+ // run of blank lines cannot stall the frames that follow in this buffer.
51
+ for (;;) {
52
+ const frame = this.isHeaderFrame() ? this.nextHeader() : this.nextLine();
53
+ if (frame !== "blank") return frame;
54
+ }
55
+ }
56
+
57
+ /** A line frame starts with `{` or `[`, as every JSON-RPC message does. Any other first byte starts a header block. */
58
+ private isHeaderFrame(): boolean {
59
+ const first = this.buffer.find((byte) => byte > 0x20);
60
+ return first !== undefined && first !== 0x7b && first !== 0x5b;
61
+ }
62
+
63
+ private nextHeader(): McpFrame | null {
64
+ // Blank lines before a header block carry no message; drop them so they
65
+ // cannot end up inside the first header line.
66
+ this.buffer = this.release(this.buffer.subarray(this.buffer.findIndex((byte) => byte > 0x20)));
36
67
  const headerEnd = this.buffer.indexOf("\r\n\r\n");
37
68
  if (headerEnd < 0) {
38
69
  if (this.buffer.length <= MCP_STDIO_MAX_FRAME_BYTES) return this.wait();
39
- return this.discardBuffer();
70
+ return this.discardBuffer("header");
40
71
  }
41
- if (headerEnd > MCP_STDIO_MAX_FRAME_BYTES) return this.discardBuffer();
72
+ if (headerEnd > MCP_STDIO_MAX_FRAME_BYTES) return this.discardBuffer("header");
42
73
 
43
74
  const headers = this.buffer.subarray(0, headerEnd).toString("utf-8").split("\r\n");
44
- const lengthHeader = headers.find((line) => line.toLowerCase().startsWith("content-length:"));
75
+ const lengthHeader = headers.find((line) => line.toLowerCase().startsWith(HEADER_PREFIX));
45
76
  const contentLength = lengthHeader ? Number.parseInt(lengthHeader.split(":")[1]?.trim() ?? "0", 10) : NaN;
46
77
  if (!Number.isFinite(contentLength) || contentLength < 0) {
47
78
  this.buffer = Buffer.alloc(0);
@@ -52,14 +83,37 @@ export class McpFrameReader {
52
83
  if (contentLength > MCP_STDIO_MAX_FRAME_BYTES) {
53
84
  const rest = this.buffer.subarray(bodyStart);
54
85
  this.skipBytes = Math.max(0, contentLength - rest.length);
55
- this.buffer = rest.subarray(Math.min(contentLength, rest.length));
56
- return TOO_LARGE;
86
+ this.buffer = this.release(rest.subarray(Math.min(contentLength, rest.length)));
87
+ return { kind: "too-large", form: "header" };
57
88
  }
58
89
  const bodyEnd = bodyStart + contentLength;
59
90
  if (this.buffer.length < bodyEnd) return this.wait();
60
91
  const text = this.buffer.subarray(bodyStart, bodyEnd).toString("utf-8");
61
- this.buffer = this.buffer.subarray(bodyEnd);
62
- return { kind: "body", text };
92
+ this.buffer = this.release(this.buffer.subarray(bodyEnd));
93
+ return { kind: "body", text, form: "header" };
94
+ }
95
+
96
+ /** A line frame, `"blank"` for an empty line, or `null` when more bytes must arrive. */
97
+ private nextLine(): McpFrame | "blank" | null {
98
+ const newline = this.buffer.indexOf("\n");
99
+ if (newline < 0) {
100
+ if (this.buffer.length <= MCP_STDIO_MAX_FRAME_BYTES) return this.wait();
101
+ this.skipLine = true;
102
+ return this.discardBuffer("line");
103
+ }
104
+ if (newline > MCP_STDIO_MAX_FRAME_BYTES) {
105
+ this.buffer = this.release(this.buffer.subarray(newline + 1));
106
+ return { kind: "too-large", form: "line" };
107
+ }
108
+ const text = this.buffer.subarray(0, newline).toString("utf-8").replace(/\r$/, "");
109
+ this.buffer = this.release(this.buffer.subarray(newline + 1));
110
+ if (text.trim().length === 0) return "blank";
111
+ return { kind: "body", text, form: "line" };
112
+ }
113
+
114
+ /** An empty view would keep the old allocation alive while the connection idles. */
115
+ private release(view: Buffer<ArrayBuffer>): Buffer<ArrayBuffer> {
116
+ return view.length === 0 ? Buffer.alloc(0) : view;
63
117
  }
64
118
 
65
119
  private wait(): null {
@@ -67,8 +121,8 @@ export class McpFrameReader {
67
121
  return null;
68
122
  }
69
123
 
70
- private discardBuffer(): McpFrame {
124
+ private discardBuffer(form: McpFrameForm): McpFrame {
71
125
  this.buffer = Buffer.alloc(0);
72
- return TOO_LARGE;
126
+ return { kind: "too-large", form };
73
127
  }
74
128
  }
@@ -1624,6 +1624,15 @@ function stdioFrame(payload: StdioRequest): string {
1624
1624
  }
1625
1625
 
1626
1626
  function readStdioMessages(raw: string): StdioMessage[] {
1627
+ if (!raw.toLowerCase().startsWith("content-length:")) {
1628
+ return raw
1629
+ .split("\n")
1630
+ .filter((line) => line.trim().length > 0)
1631
+ .map((line) => {
1632
+ const message: StdioMessage = JSON.parse(line);
1633
+ return message;
1634
+ });
1635
+ }
1627
1636
  const messages: StdioMessage[] = [];
1628
1637
  let rest = raw;
1629
1638
  while (rest.length > 0) {
@@ -1638,7 +1647,7 @@ function readStdioMessages(raw: string): StdioMessage[] {
1638
1647
  }
1639
1648
 
1640
1649
  /** Feeds each chunk in its own tick so the server sees separate data events. */
1641
- async function runStdioChunks(chunks: string[]): Promise<StdioMessage[]> {
1650
+ async function runStdioRaw(chunks: string[]): Promise<string> {
1642
1651
  const input = new PassThrough();
1643
1652
  const output = new PassThrough();
1644
1653
  output.setEncoding("utf8");
@@ -1653,7 +1662,11 @@ async function runStdioChunks(chunks: string[]): Promise<StdioMessage[]> {
1653
1662
  }
1654
1663
  input.end();
1655
1664
  await running;
1656
- return readStdioMessages(raw);
1665
+ return raw;
1666
+ }
1667
+
1668
+ async function runStdioChunks(chunks: string[]): Promise<StdioMessage[]> {
1669
+ return readStdioMessages(await runStdioRaw(chunks));
1657
1670
  }
1658
1671
 
1659
1672
  const stdioPing = (id: number) => stdioFrame({ jsonrpc: "2.0", id, method: "ping" });
@@ -1685,3 +1698,54 @@ test("MCP stdio reassembles a frame split inside its header and inside its body"
1685
1698
  assert.equal(messages.length, 1);
1686
1699
  assert.equal(messages[0]?.id, 9);
1687
1700
  });
1701
+
1702
+ // Issue #3357: a spec client sends one JSON message per line and reads the
1703
+ // reply in the same form. The server keeps Content-Length for a header client.
1704
+ type StdioLineRequest = { jsonrpc: "2.0"; id: number; method: string; params?: { protocolVersion?: string } };
1705
+ const stdioInit: StdioLineRequest = { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18" } };
1706
+ const stdioTools: StdioLineRequest = { jsonrpc: "2.0", id: 2, method: "tools/list" };
1707
+
1708
+ function stdioLine(payload: StdioLineRequest): string {
1709
+ return `${JSON.stringify(payload)}\n`;
1710
+ }
1711
+
1712
+ test("MCP stdio answers a newline-delimited initialize and tools/list with newline-delimited replies", async () => {
1713
+ const messages = await runStdioChunks([stdioLine(stdioInit), stdioLine(stdioTools)]);
1714
+ assert.equal(messages.length, 2);
1715
+ assert.equal(messages[0]?.id, 1);
1716
+ assert.equal(messages[1]?.id, 2);
1717
+ assert.ok(messages[1]?.result, "tools/list returns a result");
1718
+ });
1719
+
1720
+ test("MCP stdio answers a Content-Length initialize and tools/list with Content-Length replies", async () => {
1721
+ const header = (payload: StdioLineRequest): string => {
1722
+ const body = JSON.stringify(payload);
1723
+ return `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
1724
+ };
1725
+ const messages = await runStdioChunks([header(stdioInit), header(stdioTools)]);
1726
+ assert.equal(messages.length, 2);
1727
+ assert.equal(messages[0]?.id, 1);
1728
+ assert.equal(messages[1]?.id, 2);
1729
+ });
1730
+
1731
+ test("MCP stdio refuses an oversized newline-delimited line and answers the next line", async () => {
1732
+ const messages = await runStdioChunks([
1733
+ `{${"A".repeat(MCP_STDIO_MAX_FRAME_BYTES + 1)}\n`,
1734
+ stdioLine({ jsonrpc: "2.0", id: 4, method: "ping" }),
1735
+ ]);
1736
+ assert.equal(messages.length, 2);
1737
+ assert.match(messages[0]?.error?.message ?? "", /frame too large/);
1738
+ assert.equal(messages[1]?.id, 4);
1739
+ });
1740
+
1741
+ test("MCP stdio takes the reply form from the first JSON frame, not from a refused first frame", async () => {
1742
+ const raw = await runStdioRaw([
1743
+ "A".repeat(MCP_STDIO_MAX_FRAME_BYTES + 1),
1744
+ stdioLine({ jsonrpc: "2.0", id: 5, method: "ping" }),
1745
+ stdioLine({ jsonrpc: "2.0", id: 6, method: "ping" }),
1746
+ ]);
1747
+ assert.equal(raw.split("Content-Length").length - 1, 1, "only the reply to the refused frame is Content-Length framed");
1748
+ const afterHeader = raw.slice(raw.indexOf("\r\n\r\n") + 4);
1749
+ const replies = afterHeader.slice(afterHeader.indexOf("}}") + 2).split("\n").filter((line) => line.length > 0);
1750
+ assert.deepEqual(replies.map((line) => JSON.parse(line).id), [5, 6]);
1751
+ });
package/src/access-mcp.ts CHANGED
@@ -32,7 +32,7 @@ import {
32
32
  import { EngramAccessInputError, type EngramAccessRecallResponse, type EngramAccessService } from "./access-service.js";
33
33
  import { EngramAccessForbiddenError, unknownToolError } from "./access-errors.js";
34
34
  import { log } from "./logger.js";
35
- import { McpFrameReader } from "./access-mcp-frames.js";
35
+ import { McpFrameReader, type McpFrameForm } from "./access-mcp-frames.js";
36
36
  import "./access-operations.js";
37
37
  import { validateBriefingFormat } from "./briefing.js";
38
38
  import { processChatMessage } from "./chat/chat-factory.js";
@@ -470,6 +470,8 @@ async function getMcpServerVersion(): Promise<string> {
470
470
 
471
471
  export class EngramMcpServer {
472
472
  private readonly frames = new McpFrameReader();
473
+ /** The form of the first frame on this connection. Replies use the same form. */
474
+ private replyForm: McpFrameForm | null = null;
473
475
  private flushTask: Promise<void> | null = null;
474
476
  private readonly tools: McpTool[];
475
477
  private readonly resources: McpResource[];
@@ -2493,7 +2495,7 @@ export class EngramMcpServer {
2493
2495
  private async flushBuffer(output: Writable): Promise<void> {
2494
2496
  for (let frame = this.frames.next(); frame; frame = this.frames.next()) {
2495
2497
  if (frame.kind === "too-large") {
2496
- this.writeMessage(output, { jsonrpc: "2.0", id: null, error: { code: -32600, message: "frame too large" } });
2498
+ this.writeRefusal(output, frame.form, -32600, "frame too large");
2497
2499
  continue;
2498
2500
  }
2499
2501
  let parsed: JsonRpcRequest;
@@ -2501,16 +2503,10 @@ export class EngramMcpServer {
2501
2503
  // SAFETY: handleRequest defaults missing fields; a non-object body throws and scheduleFlush answers -32700.
2502
2504
  parsed = JSON.parse(frame.text) as JsonRpcRequest;
2503
2505
  } catch {
2504
- this.writeMessage(output, {
2505
- jsonrpc: "2.0",
2506
- id: null,
2507
- error: {
2508
- code: -32700,
2509
- message: "Parse error",
2510
- },
2511
- });
2506
+ this.writeRefusal(output, frame.form, -32700, "Parse error");
2512
2507
  continue;
2513
2508
  }
2509
+ this.replyForm ??= frame.form;
2514
2510
  const response = await this.handleRequest(parsed);
2515
2511
  if (response) {
2516
2512
  this.writeMessage(output, response);
@@ -2519,9 +2515,19 @@ export class EngramMcpServer {
2519
2515
  }
2520
2516
 
2521
2517
  private writeMessage(output: Writable, payload: Record<string, unknown>): void {
2522
- const body = JSON.stringify(payload);
2523
- const message = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
2524
- output.write(message);
2518
+ output.write(this.frameBody(JSON.stringify(payload), this.replyForm));
2519
+ }
2520
+
2521
+ /** Answers a frame that held no valid JSON in its own form, without fixing the connection's reply form. */
2522
+ private writeRefusal(output: Writable, frameForm: McpFrameForm, code: number, message: string): void {
2523
+ const body = JSON.stringify({ jsonrpc: "2.0", id: null, error: { code, message } });
2524
+ output.write(this.frameBody(body, this.replyForm ?? frameForm));
2525
+ }
2526
+
2527
+ private frameBody(body: string, form: McpFrameForm | null): string {
2528
+ return form === "line"
2529
+ ? `${body}\n`
2530
+ : `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r\n\r\n${body}`;
2525
2531
  }
2526
2532
 
2527
2533
  private toolAcceptsArgument(name: string, key: string): boolean {
@@ -19,6 +19,8 @@
19
19
  * - Precedence: explicit `namespace` wins; namespaces-disabled is a no-op.
20
20
  */
21
21
  import assert from "node:assert/strict";
22
+ import os from "node:os";
23
+ import path from "node:path";
22
24
  import test from "node:test";
23
25
 
24
26
  import { EngramAccessService } from "./access-service.js";
@@ -39,9 +41,14 @@ import { sealedWriteToLegacyArgs, type SealedMemoryEnvelope } from "./write-enve
39
41
  // the PRODUCTION mapping (`sealedWriteToLegacyArgs`), so mock behavior
40
42
  // cannot drift from the real envelope→options translation.
41
43
  function withSealedWrite<T extends { writeMemory: (...args: never[]) => unknown }>(stub: T): T {
44
+ // SAFETY: the decorated stub only gains writeSealedMemory and dir, both assigned below before any caller reads them.
42
45
  const decorated = stub as T & {
43
46
  writeSealedMemory?: (envelope: SealedMemoryEnvelope, extras?: Record<string, unknown>) => unknown;
47
+ dir?: string;
44
48
  };
49
+ // #3354: the fact-dedup window keys its lock on storage.dir and takes a
50
+ // real file lock, so the fake needs a writable per-process dir.
51
+ decorated.dir = path.join(os.tmpdir(), `remnic-coding-write-fake-${process.pid}`);
45
52
  decorated.writeSealedMemory = (envelope, extras = {}) => {
46
53
  const { category, content, options } = sealedWriteToLegacyArgs(envelope, extras);
47
54
  return (stub.writeMemory as (c: unknown, b: unknown, o: unknown) => unknown)(category, content, options);
@@ -6,6 +6,7 @@ import test from "node:test";
6
6
  import { parseConfig } from "../config.js";
7
7
  import { NamespaceStorageRouter } from "../namespaces/storage.js";
8
8
  import { StorageManager } from "../storage.js";
9
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
9
10
  import { withTempDir } from "../testing/tmp-dir.js";
10
11
  import {
11
12
  createJournalMemoryWriter,
@@ -127,3 +128,33 @@ test("tombstoned journal facts are skipped and not written as pending_review", a
127
128
  assert.equal(pending.length, 0, "tombstoned content must not land as a new pending_review file");
128
129
  });
129
130
  });
131
+
132
+ test("a fact an extraction run commits after the journal probe is not written again", async () => {
133
+ await withTempDir(async (dir) => {
134
+ const config = parseConfig({ memoryDir: dir });
135
+ const storage = new StorageManager(config.memoryDir);
136
+ await storage.ensureDirectories();
137
+ const real = createJournalMemoryWriter(storage);
138
+ const deps = extractDeps(storage, "fact");
139
+ deps.writer = {
140
+ writeSealedMemory: real.writeSealedMemory,
141
+ hasJournalMemoryContent: async (content) => {
142
+ const known = await real.hasJournalMemoryContent(content);
143
+ assert.equal(known, false, "the probe runs before the other writer commits");
144
+ await persistUnderFactDedupWindow(storage.dir, async () => false, () => storage.writeMemory("fact", FACT));
145
+ return known;
146
+ },
147
+ };
148
+ const result = await runJournalReviewExtraction({
149
+ date: "2026-08-20",
150
+ journalText: FACT,
151
+ source: "memoryDir",
152
+ journalConfig: { extractionMode: "review" },
153
+ deps,
154
+ });
155
+ assert.equal(result.pendingReview, 0);
156
+ assert.equal(result.skipped, 1);
157
+ const copies = (await storage.readAllMemories()).filter((memory) => memory.content.includes(FACT));
158
+ assert.equal(copies.length, 1, "the corpus holds one copy of the fact");
159
+ });
160
+ });
@@ -298,6 +298,7 @@ test("memoryDir source provenance differs only in the attribute value", async ()
298
298
  test("createJournalMemoryWriter dedups journal-tagged memories beyond the fact hash", async () => {
299
299
  const calls: string[] = [];
300
300
  const storage = {
301
+ dir: "/tmp/journal-writer-test-unused",
301
302
  writeSealedMemory: async () => ({}),
302
303
  hasFactContentHash: async (content: string) => {
303
304
  calls.push(`hash:${content}`);
@@ -11,6 +11,7 @@
11
11
  * Per-day change detection lives in journal-state.ts (timeline.json);
12
12
  * this module is the pure pass over one day's POST-STRIP text.
13
13
  */
14
+ import { persistUnderFactDedupWindow } from "../orchestration/fact-dedup-window.js";
14
15
  import { scoreImportance } from "../importance.js";
15
16
  import { getVerdictKind, type JudgeCandidate, type JudgeVerdict } from "../extraction-judge.js";
16
17
  import { log } from "../logger.js";
@@ -29,6 +30,7 @@ import type { ActivityTimelineJournalConfig } from "./types.js";
29
30
 
30
31
  /** The subset of StorageManager the journal pass writes through. */
31
32
  export interface JournalStorageIo {
33
+ readonly dir: string;
32
34
  writeSealedMemory(
33
35
  envelope: SealedMemoryEnvelope,
34
36
  extras: { status: MemoryStatus; contentHashSource: string; importance?: ImportanceScore },
@@ -38,7 +40,14 @@ export interface JournalStorageIo {
38
40
  }
39
41
 
40
42
  export interface JournalMemoryWriter {
41
- writeSealedMemory: JournalStorageIo["writeSealedMemory"];
43
+ /**
44
+ * `duplicate` is true when a concurrent writer committed the same fact
45
+ * first and nothing was written.
46
+ */
47
+ writeSealedMemory(
48
+ envelope: SealedMemoryEnvelope,
49
+ extras: { status: MemoryStatus; contentHashSource: string; importance?: ImportanceScore },
50
+ ): Promise<{ tombstoneBlocked?: boolean; duplicate?: boolean }>;
42
51
  /** Dedup beyond the fact-only hash index: scans journal-tagged memories. */
43
52
  hasJournalMemoryContent(content: string): Promise<boolean>;
44
53
  }
@@ -51,7 +60,19 @@ export interface JournalMemoryWriter {
51
60
  */
52
61
  export function createJournalMemoryWriter(storage: JournalStorageIo): JournalMemoryWriter {
53
62
  return {
54
- writeSealedMemory: storage.writeSealedMemory.bind(storage),
63
+ writeSealedMemory: async (envelope, extras) => {
64
+ // Facts register in the fact hash index, so extraction and this pass can
65
+ // commit the same new fact. Commit it under the same window extraction
66
+ // uses and recheck the index there. The scan in hasJournalMemoryContent
67
+ // stays outside: it is corpus-sized and the window is bounded.
68
+ if (envelope.category !== "fact") return storage.writeSealedMemory(envelope, extras);
69
+ const outcome = await persistUnderFactDedupWindow(
70
+ storage.dir,
71
+ () => storage.hasFactContentHash(extras.contentHashSource),
72
+ () => storage.writeSealedMemory(envelope, extras),
73
+ );
74
+ return outcome.suppressed ? { duplicate: true } : outcome.result;
75
+ },
55
76
  hasJournalMemoryContent: async (content: string) => {
56
77
  if (await storage.hasFactContentHash(content)) return true;
57
78
  const needle = stripAttributesSuffix(content);
@@ -210,7 +231,7 @@ export async function runJournalReviewExtraction(input: {
210
231
  contentHashSource: normalizedKey,
211
232
  importance: scoreImportance(fact.content, fact.category, fact.tags),
212
233
  });
213
- if (write.tombstoneBlocked) {
234
+ if (write.tombstoneBlocked || write.duplicate) {
214
235
  result.skipped += 1;
215
236
  continue;
216
237
  }
@@ -14,6 +14,7 @@ import {
14
14
  type GoogleDriveClientFactory,
15
15
  type GoogleDriveSyncResult,
16
16
  } from "./index.js";
17
+ import { SECRET_RESOLVE_DEADLINE_MS } from "./google-drive.js";
17
18
  import type { ConnectorConfig, ConnectorCursor } from "./framework.js";
18
19
 
19
20
  /**
@@ -123,6 +124,44 @@ test("a SecretRef with no resolver fails the connector and hides the reference",
123
124
  assert.equal(message.includes("keychain"), false);
124
125
  });
125
126
 
127
+ const DRIVE_REF_CONFIG: ConnectorConfig = {
128
+ ...SYNTHETIC_CREDS,
129
+ clientSecret: { source: "keychain", id: "projects/drive-secret" },
130
+ };
131
+
132
+ async function driveSyncError(args: { resolveSecretRef: () => Promise<string | undefined>; abortSignal?: AbortSignal }): Promise<Error> {
133
+ const connector = createGoogleDriveConnector({ clientFactory: makeFactory(makeMockClient({})) });
134
+ try {
135
+ await connector.syncIncremental({ cursor: null, config: DRIVE_REF_CONFIG, ...args });
136
+ } catch (err) {
137
+ return err instanceof Error ? err : new Error("non-error thrown");
138
+ }
139
+ return new Error("sync did not fail");
140
+ }
141
+
142
+ test("a SecretRef resolver that never settles is cut off by the abort signal", async () => {
143
+ const controller = new AbortController();
144
+ const failure = driveSyncError({ resolveSecretRef: () => new Promise(() => undefined), abortSignal: controller.signal });
145
+ await new Promise((resolve) => setImmediate(resolve));
146
+ controller.abort();
147
+ assert.equal((await failure).name, "AbortError");
148
+ });
149
+
150
+ test("a SecretRef resolver that never settles fails at the deadline", async (t) => {
151
+ t.mock.timers.enable({ apis: ["setTimeout"] });
152
+ const failure = driveSyncError({ resolveSecretRef: () => new Promise(() => undefined) });
153
+ await new Promise((resolve) => setImmediate(resolve));
154
+ t.mock.timers.tick(SECRET_RESOLVE_DEADLINE_MS);
155
+ const err = await failure;
156
+ assert.match(err.message, /clientSecret secret reference was not resolved within 30000 ms/);
157
+ assert.equal(err.message.includes("projects/drive-secret"), false);
158
+ });
159
+
160
+ test("a SecretRef that resolves to no value says the resolver may be unavailable", async () => {
161
+ const err = await driveSyncError({ resolveSecretRef: async () => undefined });
162
+ assert.match(err.message, /resolved to no value \(the host secret resolver may be unavailable\)/);
163
+ });
164
+
126
165
  test("validateGoogleDriveConfig rejects non-object input", () => {
127
166
  assert.throws(() => validateGoogleDriveConfig(null), /must be an object/);
128
167
  assert.throws(() => validateGoogleDriveConfig([]), /must be an object/);
@@ -64,7 +64,7 @@ import type {
64
64
  SyncIncrementalResult,
65
65
  } from "./framework.js";
66
66
  import { isTransientHttpError } from "./transient-errors.js";
67
- import { throwIfAborted } from "../../abort-error.js";
67
+ import { raceAbort, throwIfAborted } from "../../abort-error.js";
68
68
  import type { ResolveSecretRefFn } from "../../resolve-auth-token.js";
69
69
  import type { SecretRef } from "../../types.js";
70
70
 
@@ -370,10 +370,37 @@ function requireNonEmptyString(value: unknown, key: string): string {
370
370
  }
371
371
 
372
372
 
373
+ /** Longest wait for the host secret resolver, so a stalled secret store cannot hold the sync loop. */
374
+ export const SECRET_RESOLVE_DEADLINE_MS = 30_000;
375
+
376
+ async function resolveWithinDeadline(
377
+ ref: SecretRef,
378
+ key: string,
379
+ resolver: ResolveSecretRefFn,
380
+ abortSignal: AbortSignal | undefined,
381
+ ): Promise<string | undefined> {
382
+ throwIfAborted(abortSignal, "googleDrive: sync aborted");
383
+ const deadline = new AbortController();
384
+ const timer = setTimeout(() => deadline.abort(), SECRET_RESOLVE_DEADLINE_MS);
385
+ const signal = abortSignal ? AbortSignal.any([abortSignal, deadline.signal]) : deadline.signal;
386
+ try {
387
+ return await raceAbort(Promise.resolve(resolver(ref)), signal, "googleDrive: secret resolve aborted");
388
+ } catch {
389
+ throwIfAborted(abortSignal, "googleDrive: sync aborted");
390
+ if (deadline.signal.aborted) {
391
+ throw new Error(`googleDrive: ${key} secret reference was not resolved within ${SECRET_RESOLVE_DEADLINE_MS} ms`);
392
+ }
393
+ throw new Error(`googleDrive: ${key} secret reference could not be resolved`);
394
+ } finally {
395
+ clearTimeout(timer);
396
+ }
397
+ }
398
+
373
399
  async function resolveCredential(
374
400
  value: DriveCredential,
375
401
  key: string,
376
402
  resolver: ResolveSecretRefFn | null | undefined,
403
+ abortSignal: AbortSignal | undefined,
377
404
  ): Promise<string> {
378
405
  if (Object.prototype.toString.call(value) !== "[object Object]") {
379
406
  // SAFETY: a DriveCredential that is not an object is the plain string form.
@@ -384,17 +411,14 @@ async function resolveCredential(
384
411
  if (resolver == null) {
385
412
  throw new Error(`googleDrive: ${key} is a secret reference but no resolver is configured`);
386
413
  }
387
- let resolved: unknown;
388
- try {
389
- resolved = await resolver(ref);
390
- } catch {
391
- throw new Error(`googleDrive: ${key} secret reference could not be resolved`);
414
+ const resolved = await resolveWithinDeadline(ref, key, resolver, abortSignal);
415
+ if (resolved === undefined) {
416
+ throw new Error(`googleDrive: ${key} secret reference resolved to no value (the host secret resolver may be unavailable)`);
392
417
  }
393
418
  if (Object.prototype.toString.call(resolved) !== "[object String]") {
394
419
  throw new Error(`googleDrive: ${key} secret reference resolved to a non-string value`);
395
420
  }
396
- // SAFETY: the toString check above proves `resolved` is a string.
397
- const text = (resolved as string).trim();
421
+ const text = resolved.trim();
398
422
  if (text.length === 0) {
399
423
  throw new Error(`googleDrive: ${key} secret reference resolved to an empty value`);
400
424
  }
@@ -404,11 +428,12 @@ async function resolveCredential(
404
428
  async function resolveGoogleDriveConfig(
405
429
  stored: GoogleDriveConnectorConfig,
406
430
  resolver: ResolveSecretRefFn | null | undefined,
431
+ abortSignal: AbortSignal | undefined,
407
432
  ): Promise<GoogleDriveResolvedConfig> {
408
433
  return {
409
- clientId: await resolveCredential(stored.clientId, "clientId", resolver),
410
- [CLIENT_SECRET_FIELD]: await resolveCredential(stored[CLIENT_SECRET_FIELD], CLIENT_SECRET_FIELD, resolver),
411
- [REFRESH_TOKEN_FIELD]: await resolveCredential(stored[REFRESH_TOKEN_FIELD], REFRESH_TOKEN_FIELD, resolver),
434
+ clientId: await resolveCredential(stored.clientId, "clientId", resolver, abortSignal),
435
+ [CLIENT_SECRET_FIELD]: await resolveCredential(stored[CLIENT_SECRET_FIELD], CLIENT_SECRET_FIELD, resolver, abortSignal),
436
+ [REFRESH_TOKEN_FIELD]: await resolveCredential(stored[REFRESH_TOKEN_FIELD], REFRESH_TOKEN_FIELD, resolver, abortSignal),
412
437
  pollIntervalMs: stored.pollIntervalMs,
413
438
  folderIds: stored.folderIds,
414
439
  };
@@ -512,7 +537,11 @@ export function createGoogleDriveConnector(
512
537
 
513
538
  async syncIncremental(args: SyncIncrementalArgs): Promise<SyncIncrementalResult> {
514
539
  // Re-validate on every pass: a JS caller could mutate it between passes.
515
- const config = await resolveGoogleDriveConfig(validateGoogleDriveConfig(args.config), args.resolveSecretRef);
540
+ const config = await resolveGoogleDriveConfig(
541
+ validateGoogleDriveConfig(args.config),
542
+ args.resolveSecretRef,
543
+ args.abortSignal,
544
+ );
516
545
  throwIfAborted(args.abortSignal, "googleDrive: sync aborted");
517
546
 
518
547
  const client = await clientFactory(config);