@remnic/core 9.4.2 → 9.6.0

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 (379) hide show
  1. package/dist/access-admin-ops-surface.d.ts +6 -6
  2. package/dist/access-admin-ops-surface.js +28 -28
  3. package/dist/access-boundary.d.ts +27 -8
  4. package/dist/access-boundary.js +33 -29
  5. package/dist/access-cli.js +83 -81
  6. package/dist/access-cli.js.map +1 -1
  7. package/dist/access-http.d.ts +52 -6
  8. package/dist/access-http.js +40 -38
  9. package/dist/access-identity-continuity-surface.d.ts +5 -5
  10. package/dist/access-identity-continuity-surface.js +28 -28
  11. package/dist/access-lcm-surface.d.ts +6 -6
  12. package/dist/access-lcm-surface.js +28 -28
  13. package/dist/access-mcp.d.ts +16 -6
  14. package/dist/access-mcp.js +37 -35
  15. package/dist/access-observe-write-surface.d.ts +6 -6
  16. package/dist/access-observe-write-surface.js +28 -28
  17. package/dist/access-operations-batch.js +33 -32
  18. package/dist/access-operations.d.ts +9 -9
  19. package/dist/access-operations.js +35 -34
  20. package/dist/access-recall-surface.d.ts +6 -6
  21. package/dist/access-recall-surface.js +28 -28
  22. package/dist/access-schema.d.ts +4 -4
  23. package/dist/access-schema.js +4 -4
  24. package/dist/{access-service-m-2VO_Ig.d.ts → access-service-CXJaql3A.d.ts} +4 -4
  25. package/dist/access-service.d.ts +6 -6
  26. package/dist/access-service.js +28 -28
  27. package/dist/access-surface-catalog.d.ts +6 -6
  28. package/dist/access-surface-catalog.js +1 -0
  29. package/dist/access-surface-catalog.js.map +1 -1
  30. package/dist/access-token-capabilities.d.ts +174 -0
  31. package/dist/access-token-capabilities.js +33 -0
  32. package/dist/action-confidence.d.ts +1 -1
  33. package/dist/active-memory-bridge.d.ts +1 -1
  34. package/dist/active-recall.d.ts +1 -1
  35. package/dist/active-recall.js +2 -2
  36. package/dist/adapters/index.js +4 -4
  37. package/dist/adapters/registry.js +2 -2
  38. package/dist/{auto-sync-HMHQHUAG.js → auto-sync-4A5YBWYO.js} +4 -4
  39. package/dist/behavior-learner.d.ts +1 -1
  40. package/dist/behavior-signals.d.ts +1 -1
  41. package/dist/bootstrap.d.ts +5 -5
  42. package/dist/briefing.d.ts +2 -2
  43. package/dist/briefing.js +9 -9
  44. package/dist/buffer-surprise-report.d.ts +1 -1
  45. package/dist/buffer.d.ts +2 -2
  46. package/dist/calibration.d.ts +1 -1
  47. package/dist/capabilities.d.ts +1 -1
  48. package/dist/{capsule-crypto-CZJSLEFG.js → capsule-crypto-RAARDY3M.js} +2 -2
  49. package/dist/capsule-crypto-RAARDY3M.js.map +1 -0
  50. package/dist/{catalog-BxWokdC2.d.ts → catalog-C9cu67Ig.d.ts} +1 -1
  51. package/dist/causal-behavior.d.ts +1 -1
  52. package/dist/causal-behavior.js +5 -5
  53. package/dist/causal-chain.js +3 -3
  54. package/dist/causal-consolidation.d.ts +1 -1
  55. package/dist/causal-consolidation.js +13 -13
  56. package/dist/causal-retrieval.js +3 -3
  57. package/dist/causal-trajectory-graph.d.ts +1 -1
  58. package/dist/causal-trajectory.js +1 -1
  59. package/dist/{chunk-6ZIETXZ2.js → chunk-23JFRB73.js} +2 -2
  60. package/dist/{chunk-IC6QX5TC.js → chunk-3XIY7MDQ.js} +6 -6
  61. package/dist/{chunk-FZGGZAAU.js → chunk-4GNEDXDI.js} +146 -2
  62. package/dist/chunk-4GNEDXDI.js.map +1 -0
  63. package/dist/{chunk-LOR6ZM5R.js → chunk-4LV23CHK.js} +2 -2
  64. package/dist/{chunk-E2SPGGUI.js → chunk-4OKLXWDZ.js} +13 -6
  65. package/dist/chunk-4OKLXWDZ.js.map +1 -0
  66. package/dist/{chunk-AER6MT24.js → chunk-6SXVCD7W.js} +2 -2
  67. package/dist/{chunk-OHDJ34CC.js → chunk-72WG5QKN.js} +2 -2
  68. package/dist/{chunk-CZAZX7JP.js → chunk-7MW3CVLD.js} +4 -4
  69. package/dist/{chunk-CXLNZXG5.js → chunk-7XGSIUP5.js} +1 -1
  70. package/dist/{chunk-CZAA5N5S.js → chunk-AYZJID4S.js} +2 -2
  71. package/dist/{chunk-M7XQSUBB.js → chunk-B3ABVQ36.js} +116 -9
  72. package/dist/chunk-B3ABVQ36.js.map +1 -0
  73. package/dist/{chunk-6BISDEN6.js → chunk-BEM4D2QG.js} +14 -14
  74. package/dist/{chunk-ZPOROUTX.js → chunk-CYWA2CXR.js} +5 -5
  75. package/dist/{chunk-M5QKGHCR.js → chunk-CYWCKFCL.js} +5 -5
  76. package/dist/chunk-D7FOKIY5.js +173 -0
  77. package/dist/chunk-D7FOKIY5.js.map +1 -0
  78. package/dist/{chunk-4BFRBP45.js → chunk-DEG5ULFJ.js} +2 -2
  79. package/dist/{chunk-JLR3JSTL.js → chunk-DS7MM2ES.js} +3 -3
  80. package/dist/{chunk-2BFLDH5F.js → chunk-EEIROWEG.js} +2 -2
  81. package/dist/{chunk-5QZDMSSN.js → chunk-FKCRGLRD.js} +237 -82
  82. package/dist/{chunk-5QZDMSSN.js.map → chunk-FKCRGLRD.js.map} +1 -1
  83. package/dist/{chunk-X4A62ET7.js → chunk-H2DXDQCQ.js} +17 -17
  84. package/dist/{chunk-27FLVMDC.js → chunk-HLN7RROI.js} +7 -7
  85. package/dist/chunk-IC2TCZJ2.js +54 -0
  86. package/dist/chunk-IC2TCZJ2.js.map +1 -0
  87. package/dist/{chunk-2U2A5HF4.js → chunk-J3XHPFEU.js} +6 -6
  88. package/dist/{chunk-AKUCU7CI.js → chunk-JI5AVXYO.js} +5 -5
  89. package/dist/{chunk-KCJ5H6XQ.js → chunk-JSNVWNGX.js} +2 -2
  90. package/dist/{chunk-7Z2DZNWN.js → chunk-KL4UAXUY.js} +43 -2
  91. package/dist/{chunk-7Z2DZNWN.js.map → chunk-KL4UAXUY.js.map} +1 -1
  92. package/dist/{chunk-FLVL4L3L.js → chunk-KTULX4YO.js} +4 -4
  93. package/dist/{chunk-ILFJ7AVS.js → chunk-LADFWYHY.js} +35 -21
  94. package/dist/chunk-LADFWYHY.js.map +1 -0
  95. package/dist/{chunk-437S3G37.js → chunk-LNQUAJNC.js} +1 -1
  96. package/dist/{chunk-EPGWWCZF.js → chunk-LR5DZ56F.js} +6 -6
  97. package/dist/{chunk-YDSWRD33.js → chunk-N3JDIRBW.js} +4 -4
  98. package/dist/{chunk-OUMTBFSQ.js → chunk-NKCPUHCI.js} +5 -5
  99. package/dist/{chunk-7QWMTOIF.js → chunk-P454KXD6.js} +112 -34
  100. package/dist/chunk-P454KXD6.js.map +1 -0
  101. package/dist/{chunk-DR6PEJU3.js → chunk-PCLIWBGD.js} +44 -59
  102. package/dist/chunk-PCLIWBGD.js.map +1 -0
  103. package/dist/{chunk-BGTAL46Y.js → chunk-PNLFN2PZ.js} +2 -2
  104. package/dist/{chunk-P6SYWE4G.js → chunk-PVUJ6WVV.js} +10 -10
  105. package/dist/{chunk-YPEMNNNY.js → chunk-RGK27ELN.js} +6 -6
  106. package/dist/{chunk-2QSZNTDO.js → chunk-RKNJBZ55.js} +4 -4
  107. package/dist/{chunk-66FLU5BT.js → chunk-SIRPLF54.js} +6 -6
  108. package/dist/{chunk-M7GGMGMZ.js → chunk-SVOHGUMI.js} +1202 -126
  109. package/dist/chunk-SVOHGUMI.js.map +1 -0
  110. package/dist/{chunk-HRCACVHL.js → chunk-TLUDFPZL.js} +2 -2
  111. package/dist/{chunk-2FOD2YY3.js → chunk-UFUKHGZD.js} +236 -103
  112. package/dist/chunk-UFUKHGZD.js.map +1 -0
  113. package/dist/{chunk-2MR3MFQB.js → chunk-UPSAT4PY.js} +2 -2
  114. package/dist/{chunk-4P4626RY.js → chunk-UTRJFDP2.js} +4 -4
  115. package/dist/{chunk-CGS7UKWM.js → chunk-VCW74PNH.js} +2 -2
  116. package/dist/{chunk-5GPPACXK.js → chunk-VSMRDBFT.js} +2 -1
  117. package/dist/{chunk-NG7YBCJO.js → chunk-XCL57M6Q.js} +4 -4
  118. package/dist/{chunk-CLX5VDNY.js → chunk-Y4RKAQZ7.js} +4 -4
  119. package/dist/{chunk-DGYCWCQ7.js → chunk-Y7UNNAZD.js} +2 -2
  120. package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
  121. package/dist/{chunk-XN3TUUX2.js → chunk-YQB32INI.js} +2 -2
  122. package/dist/{cli-EiBBOt3r.d.ts → cli-CqdPA6z9.d.ts} +3 -3
  123. package/dist/cli.d.ts +7 -7
  124. package/dist/cli.js +61 -59
  125. package/dist/compounding/engine.d.ts +2 -2
  126. package/dist/compounding/engine.js +9 -9
  127. package/dist/compounding/preference-consolidator.d.ts +1 -1
  128. package/dist/compression-optimizer.d.ts +1 -1
  129. package/dist/config.d.ts +1 -1
  130. package/dist/config.js +2 -2
  131. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  132. package/dist/connectors/codex-materialize-runner.js +9 -9
  133. package/dist/connectors/codex-materialize.d.ts +1 -1
  134. package/dist/connectors/index.d.ts +1 -1
  135. package/dist/connectors/index.js +13 -11
  136. package/dist/consolidation-provenance-check.d.ts +2 -2
  137. package/dist/consolidation-undo.d.ts +2 -2
  138. package/dist/contradiction/index.d.ts +2 -2
  139. package/dist/contradiction/index.js +4 -4
  140. package/dist/conversation-index/backend.d.ts +1 -1
  141. package/dist/conversation-index/backend.js +2 -2
  142. package/dist/conversation-index/chunker.d.ts +1 -1
  143. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  144. package/dist/conversation-index/indexer.d.ts +1 -1
  145. package/dist/conversation-index/search.d.ts +1 -1
  146. package/dist/dashboard-runtime.js +2 -2
  147. package/dist/day-summary.d.ts +1 -1
  148. package/dist/delinearize.d.ts +1 -1
  149. package/dist/direct-answer-wiring.d.ts +1 -1
  150. package/dist/direct-answer.d.ts +1 -1
  151. package/dist/embedding-fallback.d.ts +1 -1
  152. package/dist/enrichment/index.d.ts +1 -1
  153. package/dist/entity-retrieval.d.ts +2 -2
  154. package/dist/entity-retrieval.js +9 -9
  155. package/dist/entity-schema.d.ts +1 -1
  156. package/dist/explicit-capture.d.ts +5 -5
  157. package/dist/extraction-faithfulness.d.ts +1 -1
  158. package/dist/extraction-judge-telemetry.d.ts +1 -1
  159. package/dist/extraction-judge-training.d.ts +1 -1
  160. package/dist/extraction-judge.d.ts +1 -1
  161. package/dist/extraction.d.ts +1 -1
  162. package/dist/fallback-llm.d.ts +1 -1
  163. package/dist/{first-start-migration-DY6YCRC2.js → first-start-migration-VBJQL6OI.js} +5 -5
  164. package/dist/graph-dashboard-diff.d.ts +1 -1
  165. package/dist/graph-dashboard-key.d.ts +1 -1
  166. package/dist/graph-dashboard-parser.d.ts +1 -1
  167. package/dist/graph-edge-reinforcement.d.ts +1 -1
  168. package/dist/graph-snapshot.d.ts +1 -1
  169. package/dist/graph.d.ts +1 -1
  170. package/dist/identity-continuity.d.ts +1 -1
  171. package/dist/importance.d.ts +1 -1
  172. package/dist/index.d.ts +271 -17
  173. package/dist/index.js +163 -99
  174. package/dist/intent.d.ts +1 -1
  175. package/dist/lcm/engine.d.ts +1 -1
  176. package/dist/lcm/engine.js +2 -2
  177. package/dist/lcm/index.d.ts +1 -1
  178. package/dist/lcm/index.js +2 -2
  179. package/dist/lcm/tools.d.ts +1 -1
  180. package/dist/lifecycle.d.ts +1 -1
  181. package/dist/live-connectors-runner.d.ts +1 -1
  182. package/dist/local-llm.d.ts +1 -1
  183. package/dist/local-model-endpoint.d.ts +1 -1
  184. package/dist/maintenance/memory-governance.d.ts +1 -1
  185. package/dist/maintenance/memory-governance.js +10 -10
  186. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +9 -9
  187. package/dist/maintenance/rebuild-memory-projection.js +11 -11
  188. package/dist/mcp-memory-inspector-app.d.ts +6 -6
  189. package/dist/mcp-read-only-tools.d.ts +16 -0
  190. package/dist/mcp-read-only-tools.js +8 -0
  191. package/dist/mcp-read-only-tools.js.map +1 -0
  192. package/dist/memory-action-policy.d.ts +1 -1
  193. package/dist/memory-cache.d.ts +1 -1
  194. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  195. package/dist/memory-projection-store.d.ts +1 -1
  196. package/dist/memory-provenance.d.ts +1 -1
  197. package/dist/memory-worth-outcomes.d.ts +2 -2
  198. package/dist/models-json.d.ts +1 -1
  199. package/dist/namespaces/migrate.d.ts +3 -3
  200. package/dist/namespaces/migrate.js +19 -19
  201. package/dist/namespaces/principal.d.ts +1 -1
  202. package/dist/namespaces/search.d.ts +1 -1
  203. package/dist/namespaces/search.js +10 -10
  204. package/dist/namespaces/storage.d.ts +3 -3
  205. package/dist/namespaces/storage.js +9 -9
  206. package/dist/native-knowledge.d.ts +1 -1
  207. package/dist/operator-toolkit.d.ts +2 -2
  208. package/dist/operator-toolkit.js +25 -25
  209. package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
  210. package/dist/orchestration/maintenance.d.ts +2 -2
  211. package/dist/orchestration/maintenance.js +11 -11
  212. package/dist/{orchestrator-B2Y28Z9t.d.ts → orchestrator-CWFrT8AF.d.ts} +91 -60
  213. package/dist/orchestrator.d.ts +5 -5
  214. package/dist/orchestrator.js +85 -83
  215. package/dist/patterns-cli.d.ts +1 -1
  216. package/dist/policy-runtime.d.ts +1 -1
  217. package/dist/provenance.d.ts +1 -1
  218. package/dist/{qmd-Db9mOkzB.d.ts → qmd--fnPMK60.d.ts} +1 -1
  219. package/dist/qmd-preflight.d.ts +2 -2
  220. package/dist/qmd-recall-cache.d.ts +1 -1
  221. package/dist/qmd.d.ts +2 -2
  222. package/dist/recall-disclosure-escalation.d.ts +1 -1
  223. package/dist/recall-explain-renderer.d.ts +1 -1
  224. package/dist/recall-planner-llm.d.ts +1 -1
  225. package/dist/recall-state.d.ts +1 -1
  226. package/dist/recall-tag-filter.d.ts +1 -1
  227. package/dist/recall-timings.d.ts +1 -1
  228. package/dist/recall-xray-cli.d.ts +1 -1
  229. package/dist/recall-xray-renderer.d.ts +1 -1
  230. package/dist/recall-xray.d.ts +1 -1
  231. package/dist/resolve-auth-token.d.ts +1 -1
  232. package/dist/resume-bundles.js +4 -4
  233. package/dist/retrieval-agents.d.ts +2 -2
  234. package/dist/retrieval-tiers.d.ts +1 -1
  235. package/dist/routing/engine.d.ts +1 -1
  236. package/dist/routing/store.d.ts +1 -1
  237. package/dist/schemas.d.ts +22 -22
  238. package/dist/search/document-scanner.js +2 -2
  239. package/dist/search/embed-helper.d.ts +1 -1
  240. package/dist/search/factory.d.ts +1 -1
  241. package/dist/search/factory.js +9 -9
  242. package/dist/search/index.d.ts +1 -1
  243. package/dist/search/index.js +13 -13
  244. package/dist/search/lancedb-backend.d.ts +1 -1
  245. package/dist/search/lancedb-backend.js +3 -3
  246. package/dist/search/meilisearch-backend.d.ts +1 -1
  247. package/dist/search/meilisearch-backend.js +3 -3
  248. package/dist/search/noop-backend.d.ts +1 -1
  249. package/dist/search/orama-backend.d.ts +1 -1
  250. package/dist/search/orama-backend.js +3 -3
  251. package/dist/search/port.d.ts +1 -1
  252. package/dist/search/remote-backend.d.ts +1 -1
  253. package/dist/{semantic-consolidation-BDVIbLRw.d.ts → semantic-consolidation-O5lMZ4LG.d.ts} +1 -1
  254. package/dist/semantic-consolidation.d.ts +2 -2
  255. package/dist/semantic-consolidation.js +11 -11
  256. package/dist/semantic-rule-promotion.js +9 -9
  257. package/dist/semantic-rule-verifier.d.ts +1 -1
  258. package/dist/semantic-rule-verifier.js +10 -10
  259. package/dist/session-observer-bands.d.ts +1 -1
  260. package/dist/session-observer-state.d.ts +1 -1
  261. package/dist/shared-context/manager.d.ts +1 -1
  262. package/dist/signal.d.ts +1 -1
  263. package/dist/storage-C1maCNbc.d.ts +1591 -0
  264. package/dist/storage.d.ts +5 -1239
  265. package/dist/storage.js +8 -8
  266. package/dist/summarizer.d.ts +1 -1
  267. package/dist/summary-snapshot.d.ts +1 -1
  268. package/dist/temporal-supersession.d.ts +2 -2
  269. package/dist/temporal-validity.d.ts +1 -1
  270. package/dist/threading.d.ts +1 -1
  271. package/dist/tier-migration.d.ts +2 -2
  272. package/dist/tier-routing.d.ts +1 -1
  273. package/dist/{tier-stats-TG6GUZ6N.js → tier-stats-5XF4UBRQ.js} +4 -4
  274. package/dist/tokens.d.ts +17 -2
  275. package/dist/tokens.js +3 -1
  276. package/dist/topics.d.ts +1 -1
  277. package/dist/transcript.d.ts +1 -1
  278. package/dist/transfer/backup.js +2 -2
  279. package/dist/transfer/capsule-export.js +3 -3
  280. package/dist/transfer/capsule-import.js +3 -3
  281. package/dist/transfer/types.d.ts +12 -12
  282. package/dist/trust-score-stage.d.ts +1 -1
  283. package/dist/trust-score.d.ts +1 -1
  284. package/dist/{types-ljkd82rn.d.ts → types-CGgcNWCG.d.ts} +31 -1
  285. package/dist/types.d.ts +1 -1
  286. package/dist/utility-runtime.d.ts +1 -1
  287. package/dist/verified-recall.js +10 -10
  288. package/package.json +2 -2
  289. package/src/access-boundary.test.ts +62 -0
  290. package/src/access-boundary.ts +165 -126
  291. package/src/access-http.test.ts +781 -0
  292. package/src/access-http.ts +334 -108
  293. package/src/access-mcp.test.ts +264 -0
  294. package/src/access-mcp.ts +45 -70
  295. package/src/access-operations-batch.ts +35 -12
  296. package/src/access-surface-catalog.test.ts +22 -14
  297. package/src/access-surface-catalog.ts +6 -3
  298. package/src/access-token-capabilities.test.ts +844 -0
  299. package/src/access-token-capabilities.ts +391 -0
  300. package/src/chat/chat-mcp.test.ts +232 -0
  301. package/src/chat/chat-session.ts +34 -0
  302. package/src/cli-wearables-fuse.test.ts +197 -0
  303. package/src/cli.ts +25 -0
  304. package/src/index.ts +14 -0
  305. package/src/mcp-read-only-tools.ts +61 -0
  306. package/src/storage.ts +16 -0
  307. package/src/tokens.ts +32 -3
  308. package/src/wearables/cli.test.ts +102 -0
  309. package/src/wearables/cli.ts +65 -0
  310. package/src/wearables/config.test.ts +32 -0
  311. package/src/wearables/config.ts +52 -0
  312. package/src/wearables/day-store.test.ts +246 -0
  313. package/src/wearables/day-store.ts +272 -3
  314. package/src/wearables/fusion/cluster.ts +230 -0
  315. package/src/wearables/fusion/fuse.ts +218 -0
  316. package/src/wearables/fusion/fusion.test.ts +2575 -0
  317. package/src/wearables/fusion/index.ts +50 -0
  318. package/src/wearables/fusion/reconcile.ts +831 -0
  319. package/src/wearables/fusion/reconstruct.ts +251 -0
  320. package/src/wearables/fusion/store.ts +480 -0
  321. package/src/wearables/fusion/types.ts +228 -0
  322. package/src/wearables/index.ts +41 -0
  323. package/src/wearables/service.test.ts +1030 -2
  324. package/src/wearables/service.ts +258 -16
  325. package/src/wearables/speakers.test.ts +128 -0
  326. package/src/wearables/speakers.ts +58 -4
  327. package/src/wearables/storage-io.test.ts +146 -0
  328. package/src/wearables/types.ts +31 -0
  329. package/dist/chunk-2FOD2YY3.js.map +0 -1
  330. package/dist/chunk-7QWMTOIF.js.map +0 -1
  331. package/dist/chunk-DR6PEJU3.js.map +0 -1
  332. package/dist/chunk-E2SPGGUI.js.map +0 -1
  333. package/dist/chunk-FZGGZAAU.js.map +0 -1
  334. package/dist/chunk-ILFJ7AVS.js.map +0 -1
  335. package/dist/chunk-M7GGMGMZ.js.map +0 -1
  336. package/dist/chunk-M7XQSUBB.js.map +0 -1
  337. /package/dist/{capsule-crypto-CZJSLEFG.js.map → access-token-capabilities.js.map} +0 -0
  338. /package/dist/{auto-sync-HMHQHUAG.js.map → auto-sync-4A5YBWYO.js.map} +0 -0
  339. /package/dist/{chunk-6ZIETXZ2.js.map → chunk-23JFRB73.js.map} +0 -0
  340. /package/dist/{chunk-IC6QX5TC.js.map → chunk-3XIY7MDQ.js.map} +0 -0
  341. /package/dist/{chunk-LOR6ZM5R.js.map → chunk-4LV23CHK.js.map} +0 -0
  342. /package/dist/{chunk-AER6MT24.js.map → chunk-6SXVCD7W.js.map} +0 -0
  343. /package/dist/{chunk-OHDJ34CC.js.map → chunk-72WG5QKN.js.map} +0 -0
  344. /package/dist/{chunk-CZAZX7JP.js.map → chunk-7MW3CVLD.js.map} +0 -0
  345. /package/dist/{chunk-CXLNZXG5.js.map → chunk-7XGSIUP5.js.map} +0 -0
  346. /package/dist/{chunk-CZAA5N5S.js.map → chunk-AYZJID4S.js.map} +0 -0
  347. /package/dist/{chunk-6BISDEN6.js.map → chunk-BEM4D2QG.js.map} +0 -0
  348. /package/dist/{chunk-ZPOROUTX.js.map → chunk-CYWA2CXR.js.map} +0 -0
  349. /package/dist/{chunk-M5QKGHCR.js.map → chunk-CYWCKFCL.js.map} +0 -0
  350. /package/dist/{chunk-4BFRBP45.js.map → chunk-DEG5ULFJ.js.map} +0 -0
  351. /package/dist/{chunk-JLR3JSTL.js.map → chunk-DS7MM2ES.js.map} +0 -0
  352. /package/dist/{chunk-2BFLDH5F.js.map → chunk-EEIROWEG.js.map} +0 -0
  353. /package/dist/{chunk-X4A62ET7.js.map → chunk-H2DXDQCQ.js.map} +0 -0
  354. /package/dist/{chunk-27FLVMDC.js.map → chunk-HLN7RROI.js.map} +0 -0
  355. /package/dist/{chunk-2U2A5HF4.js.map → chunk-J3XHPFEU.js.map} +0 -0
  356. /package/dist/{chunk-AKUCU7CI.js.map → chunk-JI5AVXYO.js.map} +0 -0
  357. /package/dist/{chunk-KCJ5H6XQ.js.map → chunk-JSNVWNGX.js.map} +0 -0
  358. /package/dist/{chunk-FLVL4L3L.js.map → chunk-KTULX4YO.js.map} +0 -0
  359. /package/dist/{chunk-437S3G37.js.map → chunk-LNQUAJNC.js.map} +0 -0
  360. /package/dist/{chunk-EPGWWCZF.js.map → chunk-LR5DZ56F.js.map} +0 -0
  361. /package/dist/{chunk-YDSWRD33.js.map → chunk-N3JDIRBW.js.map} +0 -0
  362. /package/dist/{chunk-OUMTBFSQ.js.map → chunk-NKCPUHCI.js.map} +0 -0
  363. /package/dist/{chunk-BGTAL46Y.js.map → chunk-PNLFN2PZ.js.map} +0 -0
  364. /package/dist/{chunk-P6SYWE4G.js.map → chunk-PVUJ6WVV.js.map} +0 -0
  365. /package/dist/{chunk-YPEMNNNY.js.map → chunk-RGK27ELN.js.map} +0 -0
  366. /package/dist/{chunk-2QSZNTDO.js.map → chunk-RKNJBZ55.js.map} +0 -0
  367. /package/dist/{chunk-66FLU5BT.js.map → chunk-SIRPLF54.js.map} +0 -0
  368. /package/dist/{chunk-HRCACVHL.js.map → chunk-TLUDFPZL.js.map} +0 -0
  369. /package/dist/{chunk-2MR3MFQB.js.map → chunk-UPSAT4PY.js.map} +0 -0
  370. /package/dist/{chunk-4P4626RY.js.map → chunk-UTRJFDP2.js.map} +0 -0
  371. /package/dist/{chunk-CGS7UKWM.js.map → chunk-VCW74PNH.js.map} +0 -0
  372. /package/dist/{chunk-5GPPACXK.js.map → chunk-VSMRDBFT.js.map} +0 -0
  373. /package/dist/{chunk-NG7YBCJO.js.map → chunk-XCL57M6Q.js.map} +0 -0
  374. /package/dist/{chunk-CLX5VDNY.js.map → chunk-Y4RKAQZ7.js.map} +0 -0
  375. /package/dist/{chunk-DGYCWCQ7.js.map → chunk-Y7UNNAZD.js.map} +0 -0
  376. /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
  377. /package/dist/{chunk-XN3TUUX2.js.map → chunk-YQB32INI.js.map} +0 -0
  378. /package/dist/{first-start-migration-DY6YCRC2.js.map → first-start-migration-VBJQL6OI.js.map} +0 -0
  379. /package/dist/{tier-stats-TG6GUZ6N.js.map → tier-stats-5XF4UBRQ.js.map} +0 -0
@@ -0,0 +1,197 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+
4
+ import {
5
+ registerCli,
6
+ type CliApi,
7
+ type CliCommand,
8
+ type CliProgram,
9
+ } from "./cli.js";
10
+ import type { WearablesService } from "./wearables/service.js";
11
+
12
+ /**
13
+ * Host-CLI registration test for the wearables `fuse`/`fused` subcommands.
14
+ *
15
+ * The shared runner (`runWearablesCliCommand`) already handles fuse/fused, but
16
+ * the host CLI must ALSO register forwarders under `wearablesCmd` — otherwise
17
+ * commander rejects `remnic wearables fuse <date>` / `remnic wearables fused
18
+ * <date>` as unknown subcommands before the runner is ever reached (issue
19
+ * #1849). This drives `registerCli` with a recording program mock plus a stub
20
+ * orchestrator and asserts both that the subcommands are registered and that
21
+ * their actions dispatch into the runner (which calls the wearables service).
22
+ */
23
+
24
+ /** Recording commander stand-in: records the command tree + action callbacks. */
25
+ class RecCmd implements CliCommand {
26
+ readonly children = new Map<string, RecCmd>();
27
+ readonly options: string[] = [];
28
+ readonly args: string[] = [];
29
+ actionFn?: (...args: unknown[]) => unknown;
30
+ constructor(readonly rawName: string) {}
31
+ description(): CliCommand {
32
+ return this;
33
+ }
34
+ option(flags: string): CliCommand {
35
+ this.options.push(flags);
36
+ return this;
37
+ }
38
+ requiredOption(flags: string): CliCommand {
39
+ this.options.push(flags);
40
+ return this;
41
+ }
42
+ argument(name: string): CliCommand {
43
+ this.args.push(name);
44
+ return this;
45
+ }
46
+ action(fn: (...args: unknown[]) => Promise<void> | void): CliCommand {
47
+ this.actionFn = fn;
48
+ return this;
49
+ }
50
+ command(name: string): CliCommand {
51
+ const key = name.split(/\s+/)[0]!;
52
+ const child = new RecCmd(name);
53
+ this.children.set(key, child);
54
+ return child;
55
+ }
56
+ }
57
+
58
+ class RecProgram implements CliProgram {
59
+ readonly root = new RecCmd("__program__");
60
+ command(name: string): CliCommand {
61
+ return this.root.command(name);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Deterministic orchestrator stub. registerCli only reads `config.*` while
67
+ * building the command tree (feature flags gate subcommand registration;
68
+ * memoryDir is an option default), so every accessed field returns a real
69
+ * primitive — no Proxy ever reaches a string/number call site. The wearables
70
+ * forwarder is the only action that touches the stub, via getWearablesService.
71
+ */
72
+ function makeOrchestratorStub(
73
+ service: WearablesService,
74
+ ): Parameters<typeof registerCli>[1] {
75
+ const config = new Proxy(
76
+ { memoryDir: "/tmp/cli-wearables-fuse-test" } as Record<string, unknown>,
77
+ {
78
+ get(target, prop) {
79
+ if (typeof prop === "symbol") return undefined;
80
+ if (prop in target) return target[prop as string];
81
+ const key = prop as string;
82
+ if (
83
+ key.endsWith("WindowMs") ||
84
+ key.endsWith("SoftLimit") ||
85
+ key.endsWith("HardLimit")
86
+ ) {
87
+ return 0;
88
+ }
89
+ return true; // feature flags -> enabled (every subcommand registers)
90
+ },
91
+ },
92
+ );
93
+ return { config, getWearablesService: () => service } as unknown as Parameters<
94
+ typeof registerCli
95
+ >[1];
96
+ }
97
+
98
+ /** Spy service recording fuseDay/fusedConversations dispatch. */
99
+ function spyService(): WearablesService & {
100
+ calls: Array<[string, string]>;
101
+ } {
102
+ const calls: Array<[string, string]> = [];
103
+ const svc = {
104
+ fuseDay: async (date: string) => {
105
+ calls.push(["fuseDay", date]);
106
+ return {
107
+ date,
108
+ sources: ["limitless", "bee"],
109
+ conversationCount: 2,
110
+ contentHash: "h",
111
+ written: true,
112
+ };
113
+ },
114
+ fusedConversations: async (date: string) => {
115
+ calls.push(["fusedConversations", date]);
116
+ return [];
117
+ },
118
+ };
119
+ return { ...svc, calls } as unknown as WearablesService & {
120
+ calls: Array<[string, string]>;
121
+ };
122
+ }
123
+
124
+ function buildRegisteredTree(): {
125
+ program: RecProgram;
126
+ wearables: RecCmd;
127
+ service: WearablesService & { calls: Array<[string, string]> };
128
+ } {
129
+ const program = new RecProgram();
130
+ const service = spyService();
131
+ const api: CliApi = {
132
+ registerCli: (handler) => handler({ program }),
133
+ };
134
+ registerCli(api, makeOrchestratorStub(service));
135
+ const wearables = program.root.children.get("engram")?.children.get("wearables");
136
+ assert.ok(wearables, "wearables command registered under engram");
137
+ return { program, wearables: wearables!, service };
138
+ }
139
+
140
+ test("wearablesCmd registers fuse and fused subcommands (#1849)", () => {
141
+ const { wearables } = buildRegisteredTree();
142
+ // fuse/fused must be present alongside the previously-registered forwarders.
143
+ const subcommands = [...wearables.children.keys()];
144
+ for (const expected of [
145
+ "status",
146
+ "check",
147
+ "sync",
148
+ "transcript",
149
+ "search",
150
+ "memories",
151
+ "speakers",
152
+ "corrections",
153
+ "fuse",
154
+ "fused",
155
+ ]) {
156
+ assert.ok(
157
+ subcommands.includes(expected),
158
+ `wearables ${expected} subcommand registered`,
159
+ );
160
+ }
161
+
162
+ const fuse = wearables.children.get("fuse")!;
163
+ assert.ok(
164
+ fuse.rawName.includes("<date>"),
165
+ "fuse declares a <date> argument",
166
+ );
167
+ assert.ok(fuse.options.includes("--json"), "fuse forwards --json");
168
+
169
+ const fused = wearables.children.get("fused")!;
170
+ assert.ok(
171
+ fused.rawName.includes("<date>"),
172
+ "fused declares a <date> argument",
173
+ );
174
+ assert.ok(fused.options.includes("--json"), "fused forwards --json");
175
+ });
176
+
177
+ test("wearables fuse/fused forwarders dispatch to the runner, not rejected (#1849)", async () => {
178
+ const { wearables, service } = buildRegisteredTree();
179
+ const fuse = wearables.children.get("fuse")!;
180
+ const fused = wearables.children.get("fused")!;
181
+
182
+ // commander passes the <date> positional as arg[0] and the options object
183
+ // as the trailing arg — the same shape the `search <query>` forwarder uses.
184
+ await fuse.actionFn!("2026-06-10", { json: false });
185
+ assert.deepEqual(
186
+ service.calls[0],
187
+ ["fuseDay", "2026-06-10"],
188
+ "wearables fuse <date> dispatches into the runner -> service.fuseDay",
189
+ );
190
+
191
+ await fused.actionFn!("2026-06-10", { json: true });
192
+ assert.deepEqual(
193
+ service.calls[1],
194
+ ["fusedConversations", "2026-06-10"],
195
+ "wearables fused <date> dispatches into the runner -> service.fusedConversations",
196
+ );
197
+ });
package/src/cli.ts CHANGED
@@ -5376,6 +5376,31 @@ export function registerCli(
5376
5376
  .action(async (...args: unknown[]) =>
5377
5377
  forwardWearables(["corrections", "remove", String(args[0] ?? "")]),
5378
5378
  );
5379
+ wearablesCmd
5380
+ .command("fuse <date>")
5381
+ .description("Fuse (cross-source merge) wearable transcripts for a day")
5382
+ .option("--json", "JSON output")
5383
+ .action(async (...args: unknown[]) => {
5384
+ const options = (args[1] ?? {}) as Record<string, unknown>;
5385
+ await forwardWearables([
5386
+ "fuse",
5387
+ String(args[0] ?? ""),
5388
+ ...(options.json === true ? ["--json"] : []),
5389
+ ]);
5390
+ });
5391
+
5392
+ wearablesCmd
5393
+ .command("fused <date>")
5394
+ .description("List fused conversations for a day")
5395
+ .option("--json", "JSON output")
5396
+ .action(async (...args: unknown[]) => {
5397
+ const options = (args[1] ?? {}) as Record<string, unknown>;
5398
+ await forwardWearables([
5399
+ "fused",
5400
+ String(args[0] ?? ""),
5401
+ ...(options.json === true ? ["--json"] : []),
5402
+ ]);
5403
+ });
5379
5404
  }
5380
5405
 
5381
5406
  cmd
package/src/index.ts CHANGED
@@ -992,6 +992,20 @@ export {
992
992
  type TokenStore,
993
993
  } from "./tokens.js";
994
994
 
995
+ export {
996
+ TOKEN_CAPABILITIES_VERSION,
997
+ capabilityAllowsNamespace,
998
+ capabilityAllowsOp,
999
+ isCapabilityRestricted,
1000
+ isValidNamespaceValue,
1001
+ normalizeCapabilities,
1002
+ tokenCapabilityStore,
1003
+ validateCapabilitiesForMint,
1004
+ type TokenCapabilities,
1005
+ } from "./access-token-capabilities.js";
1006
+
1007
+ export { OPERATION_NAMES, type OperationName } from "./access-boundary.js";
1008
+
995
1009
  // ---------------------------------------------------------------------------
996
1010
  // Codex materializer (#378)
997
1011
  // ---------------------------------------------------------------------------
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Conservative allowlist of canonical MCP tool suffixes that are
3
+ * unambiguously read-only. Tools in this set are tagged with
4
+ * `annotations: { readOnlyHint: true }` so ChatGPT (and other MCP
5
+ * clients that honor the hint) can skip per-call confirmation.
6
+ *
7
+ * The list is suffix-based so it covers both the `remnic.*` and
8
+ * `engram.*` naming forms. Anything not on it stays unannotated:
9
+ * uncertainty is resolved as "might mutate".
10
+ *
11
+ * Excluded by construction: anything that writes, runs a pipeline,
12
+ * flushes, applies, records, imports, or destructively deletes.
13
+ */
14
+ export const MCP_READ_ONLY_TOOL_SUFFIXES: Readonly<Record<string, true>> = {
15
+ recall: true,
16
+ recall_explain: true,
17
+ recall_tier_explain: true,
18
+ recall_xray: true,
19
+ briefing: true,
20
+ wearables_status: true,
21
+ transcript_day: true,
22
+ transcript_search: true,
23
+ transcript_memories: true,
24
+ action_confidence: true,
25
+ capsule_list: true,
26
+ procedural_stats: true,
27
+ memory_get: true,
28
+ memory_timeline: true,
29
+ entity_get: true,
30
+ review_queue_list: true,
31
+ lcm_search: true,
32
+ continuity_incident_list: true,
33
+ identity_anchor_get: true,
34
+ memory_identity: true,
35
+ memory_search: true,
36
+ memory_profile: true,
37
+ memory_entities_list: true,
38
+ memory_questions: true,
39
+ memory_last_recall: true,
40
+ memory_intent_debug: true,
41
+ memory_qmd_debug: true,
42
+ memory_graph_explain: true,
43
+ graph_snapshot: true,
44
+ review_list: true,
45
+ profiling_report: true,
46
+ peer_list: true,
47
+ peer_get: true,
48
+ peer_profile_get: true,
49
+ console_state: true,
50
+ dreams_status: true,
51
+ codegraph_list_projects: true,
52
+ codegraph_index_status: true,
53
+ codegraph_search_graph: true,
54
+ codegraph_trace_path: true,
55
+ codegraph_detect_changes: true,
56
+ codegraph_query_graph: true,
57
+ codegraph_get_schema: true,
58
+ codegraph_get_snippet: true,
59
+ codegraph_get_architecture: true,
60
+ codegraph_search_code: true,
61
+ };
package/src/storage.ts CHANGED
@@ -23,6 +23,7 @@ import { serializeProvenanceFields, parseProvenanceSources, parseProvenanceTag,
23
23
  import { serializeFaithfulnessFields, parseFaithfulnessField } from "./extraction-faithfulness.js";
24
24
  import { createVersion as createPageVersion, type VersioningConfig, type VersionTrigger } from "./page-versioning.js";
25
25
  import { isValidTranscriptDate, WEARABLES_DIR_NAME } from "./wearables/day-store.js";
26
+ import { FusionArtifactStore } from "./wearables/fusion/index.js";
26
27
  import {
27
28
  SecureStoreLockedError,
28
29
  MAGIC_HEADER_SIZE,
@@ -2709,6 +2710,21 @@ export class StorageManager {
2709
2710
  );
2710
2711
  }
2711
2712
 
2713
+ private _fusionStore?: FusionArtifactStore;
2714
+
2715
+ /** Derived fusion-day IO; file IO lives in wearables/fusion (#1810). */
2716
+ fusionArtifactStore(): FusionArtifactStore {
2717
+ if (this._fusionStore) return this._fusionStore;
2718
+ return (this._fusionStore = new FusionArtifactStore(this.wearablesDir, this.baseDir, {
2719
+ writeFile: (p, c) => this.writeStorageSecureFile(p, c),
2720
+ readFile: (p) => readMaybeEncryptedFile(p, this._secureStoreKey, this.baseDir),
2721
+ readDir: (d) => readdir(d),
2722
+ deleteFile: (p) => unlink(p),
2723
+ realpath: (p) => realpath(p),
2724
+ lstat: (p) => lstat(p).then((st) => ({ isSymbolicLink: st.isSymbolicLink() })),
2725
+ }));
2726
+ }
2727
+
2712
2728
  /**
2713
2729
  * Locate a wearable-sourced memory by exact (trimmed) content,
2714
2730
  * ignoring the "[Attributes: ...]" suffix writeMemory appends for
package/src/tokens.ts CHANGED
@@ -9,11 +9,27 @@ import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { randomBytes, randomUUID } from "node:crypto";
11
11
  import { resolveHomeDir } from "./runtime/env.js";
12
+ import {
13
+ TOKEN_CAPABILITIES_VERSION,
14
+ normalizeCapabilities,
15
+ type TokenCapabilities,
16
+ } from "./access-token-capabilities.js";
12
17
 
13
18
  export interface TokenEntry {
14
19
  token: string;
15
20
  connector: string;
16
21
  createdAt: string;
22
+ /**
23
+ * Versioned capabilities record (issue #1837). PRESENT ⇒ the token was
24
+ * minted by the capability-aware path; its ops/namespaces axes are
25
+ * enforced (absent axis ⇒ unrestricted on that axis; empty array ⇒ deny
26
+ * all; non-empty ⇒ allow-list). ABSENT ⇒ a pre-feature legacy entry ⇒
27
+ * unrestricted (full access), purely for backward compatibility — this is
28
+ * the ONLY path by which full access is granted by omission. Every new
29
+ * token carries an explicit record (even `{ version: 1 }` for a deliberate
30
+ * unrestricted mint), so a new token can never silently gain full access.
31
+ */
32
+ capabilities?: TokenCapabilities;
17
33
  }
18
34
 
19
35
  export interface TokenStore {
@@ -75,6 +91,9 @@ function validateTokenEntry(raw: unknown, index: number): TokenEntry {
75
91
  if (typeof entry.connector !== "string" || entry.connector.length === 0) {
76
92
  throw new Error(`invalid token entry at index ${index}: connector must be a non-empty string`);
77
93
  }
94
+ const capabilities = entry.capabilities !== undefined
95
+ ? normalizeCapabilities(entry.capabilities)
96
+ : undefined;
78
97
  return {
79
98
  token: entry.token,
80
99
  connector: entry.connector,
@@ -82,6 +101,7 @@ function validateTokenEntry(raw: unknown, index: number): TokenEntry {
82
101
  typeof entry.createdAt === "string" && entry.createdAt.length > 0
83
102
  ? entry.createdAt
84
103
  : new Date(0).toISOString(),
104
+ ...(capabilities ? { capabilities } : {}),
85
105
  };
86
106
  }
87
107
 
@@ -201,13 +221,18 @@ export function saveTokenStore(store: TokenStore, tokensPath?: string): void {
201
221
  * dependent write (e.g. Hermes config.yaml) succeeds — see
202
222
  * commitTokenEntry() to persist the candidate.
203
223
  */
204
- export function buildTokenEntry(connector: string): TokenEntry {
224
+ export function buildTokenEntry(connector: string, capabilities?: TokenCapabilities): TokenEntry {
205
225
  const prefix = TOKEN_PREFIXES[connector] ?? "remnic_xx_";
206
226
  const token = prefix + randomBytes(24).toString("hex");
227
+ // Every newly-built entry carries an EXPLICIT capabilities record (issue
228
+ // #1837). When none is requested, record a deliberate unrestricted
229
+ // `{ version: 1 }` — never an omitted field — so "full access via omission"
230
+ // is reachable only by pre-feature legacy entries lacking the marker.
207
231
  return {
208
232
  token,
209
233
  connector,
210
234
  createdAt: new Date().toISOString(),
235
+ capabilities: capabilities ?? { version: TOKEN_CAPABILITIES_VERSION },
211
236
  };
212
237
  }
213
238
 
@@ -230,13 +255,17 @@ export function commitTokenEntry(entry: TokenEntry, tokensPath?: string): void {
230
255
  saveTokenStore(store, tokensPath);
231
256
  }
232
257
 
233
- export function generateToken(connector: string, tokensPath?: string): TokenEntry {
258
+ export function generateToken(
259
+ connector: string,
260
+ tokensPath?: string,
261
+ capabilities?: TokenCapabilities,
262
+ ): TokenEntry {
234
263
  const store = loadTokenStore(tokensPath);
235
264
 
236
265
  // Remove existing token for this connector
237
266
  store.tokens = store.tokens.filter((t) => t.connector !== connector);
238
267
 
239
- const entry = buildTokenEntry(connector);
268
+ const entry = buildTokenEntry(connector, capabilities);
240
269
  store.tokens.push(entry);
241
270
  saveTokenStore(store, tokensPath);
242
271
  return entry;
@@ -198,3 +198,105 @@ test("backend faults propagate instead of being swallowed as exit codes", async
198
198
  /disk exploded/,
199
199
  );
200
200
  });
201
+
202
+ test("fuse command requires a date and renders the fuseDay result", async () => {
203
+ const { io, out, err } = makeIo();
204
+ const service = stubService({
205
+ fuseDay: async () => ({
206
+ date: "2026-06-10",
207
+ sources: ["bee", "limitless"],
208
+ conversationCount: 2,
209
+ contentHash: "abc123",
210
+ written: true,
211
+ }),
212
+ });
213
+ assert.equal(await runWearablesCliCommand(service, ["fuse"], io), 1);
214
+ assert.match(err.join(""), /fuse requires a date/);
215
+
216
+ // Success path needs its own captured stdout.
217
+ const okIo = makeIo();
218
+ assert.equal(
219
+ await runWearablesCliCommand(service, ["fuse", "2026-06-10"], okIo.io),
220
+ 0,
221
+ );
222
+ assert.match(
223
+ okIo.out.join(""),
224
+ /Fused 2026-06-10: 2 conversations from 2 sources/,
225
+ );
226
+ });
227
+
228
+ test("fuse command prints a distinct skip line when fusion is skipped, not '(unchanged)'", async () => {
229
+ // written:false has two meanings (issue #1849): a skip (e.g. conflicting
230
+ // source timezones, which also clears any existing fused artifact) must
231
+ // NOT be mislabeled as an idempotent "(unchanged)" no-op.
232
+ const { io, out } = makeIo();
233
+ const service = stubService({
234
+ fuseDay: async () => ({
235
+ date: "2026-06-10",
236
+ sources: [],
237
+ conversationCount: 0,
238
+ contentHash: "",
239
+ written: false,
240
+ skipped: { reason: "conflicting-timezones" },
241
+ }),
242
+ });
243
+ assert.equal(await runWearablesCliCommand(service, ["fuse", "2026-06-10"], io), 0);
244
+ const text = out.join("");
245
+ assert.match(text, /Skipped fusing 2026-06-10: conflicting-timezones/);
246
+ assert.match(text, /cleared/);
247
+ assert.doesNotMatch(text, /unchanged/, "a skip must not look idempotent");
248
+ });
249
+
250
+ test("fuse command keeps '(unchanged)' for written:false without a skip reason", async () => {
251
+ const { io, out } = makeIo();
252
+ const service = stubService({
253
+ fuseDay: async () => ({
254
+ date: "2026-06-10",
255
+ sources: ["bee", "limitless"],
256
+ conversationCount: 2,
257
+ contentHash: "abc123",
258
+ written: false,
259
+ }),
260
+ });
261
+ assert.equal(await runWearablesCliCommand(service, ["fuse", "2026-06-10"], io), 0);
262
+ assert.match(out.join(""), /\(unchanged\)/);
263
+ });
264
+
265
+ test("fused command lists conversations and surfaces disagreements (--json)", async () => {
266
+ const { io, out } = makeIo();
267
+ const service = stubService({
268
+ fusedConversations: async () => [
269
+ {
270
+ id: "fusion-1",
271
+ date: "2026-06-10",
272
+ startIso: "2026-06-10T09:00:00.000Z",
273
+ sources: ["bee", "limitless"],
274
+ speakers: [],
275
+ segments: [
276
+ { speaker: "Me (you)", isSelf: true, text: "hi", confidence: 0.8, provenance: { source: "limitless", conversationId: "c1", sourceTrust: 0.8, reason: "only-source", alternatives: [] } },
277
+ ],
278
+ disagreements: [
279
+ { kind: "asr-text", subject: "2026-06-10T09:00:00.000Z", candidates: [{ source: "bee", value: "x" }, { source: "limitless", value: "y" }] },
280
+ ],
281
+ provenance: { contributions: [], proximityGapMs: 300000, windowToleranceMs: 30000, method: "time-proximity" },
282
+ },
283
+ ],
284
+ });
285
+ assert.equal(await runWearablesCliCommand(service, ["fused", "2026-06-10", "--json"], io), 0);
286
+ const parsed = JSON.parse(out.join(""));
287
+ assert.equal(parsed.date, "2026-06-10");
288
+ assert.equal(parsed.conversations.length, 1);
289
+ assert.equal(parsed.conversations[0].disagreements.length, 1);
290
+
291
+ // Plain-text path surfaces the disagreement count.
292
+ const text = makeIo();
293
+ assert.equal(await runWearablesCliCommand(service, ["fused", "2026-06-10"], text.io), 0);
294
+ assert.match(text.out.join(""), /disagreement \[asr-text\]/);
295
+ });
296
+
297
+ test("fused command reports nothing-fused gracefully", async () => {
298
+ const { io, out } = makeIo();
299
+ const service = stubService({ fusedConversations: async () => [] });
300
+ assert.equal(await runWearablesCliCommand(service, ["fused", "2026-06-10"], io), 0);
301
+ assert.match(out.join(""), /No fused conversations for 2026-06-10/);
302
+ });
@@ -33,6 +33,8 @@ Commands:
33
33
  --source <id> --from <date> --to <date> --limit <n>
34
34
  memories [options] List memories created from transcripts
35
35
  --source <id> --date <date> --limit <n>
36
+ fuse <YYYY-MM-DD> Fuse enabled sources' transcripts for a day
37
+ fused <YYYY-MM-DD> List fused conversations for a day
36
38
  speakers list Show the speaker registry
37
39
  speakers self <name> Set the wearer's display name
38
40
  speakers set <source> <key> <name> [--self]
@@ -419,6 +421,69 @@ export async function runWearablesCliCommand(
419
421
  `unknown corrections action '${action}' — expected list, add, or remove`,
420
422
  );
421
423
  }
424
+ case "fuse": {
425
+ const parsed = parseFlags(rest);
426
+ const date = flagString(parsed, "--date") ?? parsed.positional[0];
427
+ if (!date) {
428
+ throw new WearablesInputError(
429
+ "fuse requires a date (e.g. wearables fuse 2026-06-10)",
430
+ );
431
+ }
432
+ const result = await service.fuseDay(date);
433
+ if (parsed.flags.has("--json")) {
434
+ io.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
435
+ return 0;
436
+ }
437
+ // `written:false` has two distinct meanings (issue #1849): a genuine
438
+ // idempotent no-op (inputs unchanged) OR a deliberate SKIP — e.g.
439
+ // mixed source timezones — where fusion was refused and any
440
+ // previously-fused artifact for the day was cleared. The JSON path
441
+ // carries `skipped`, but the text path collapsed both into
442
+ // "(unchanged)", hiding a skip (and a possible artifact deletion)
443
+ // behind an idempotent-looking label. Check `skipped` first and emit
444
+ // a distinct skip line; reserve "(unchanged)" for written:false
445
+ // WITHOUT a skip reason. Mirrors the `skipped` field the JSON path
446
+ // serializes.
447
+ if (result.skipped) {
448
+ io.stdout.write(
449
+ `Skipped fusing ${date}: ${result.skipped.reason} — no fused artifact written; any existing fused artifact for the day was cleared.\n`,
450
+ );
451
+ } else {
452
+ io.stdout.write(
453
+ `Fused ${date}: ${result.conversationCount} conversation${result.conversationCount === 1 ? "" : "s"} from ${result.sources.length} source${result.sources.length === 1 ? "" : "s"}.${result.written ? "" : " (unchanged)"}\n`,
454
+ );
455
+ }
456
+ return 0;
457
+ }
458
+ case "fused": {
459
+ const parsed = parseFlags(rest);
460
+ const date = flagString(parsed, "--date") ?? parsed.positional[0];
461
+ if (!date) {
462
+ throw new WearablesInputError(
463
+ "fused requires a date (e.g. wearables fused 2026-06-10)",
464
+ );
465
+ }
466
+ const conversations = await service.fusedConversations(date);
467
+ if (parsed.flags.has("--json")) {
468
+ io.stdout.write(`${JSON.stringify({ date, conversations }, null, 2)}\n`);
469
+ return 0;
470
+ }
471
+ if (conversations.length === 0) {
472
+ io.stdout.write(`No fused conversations for ${date}.\n`);
473
+ return 0;
474
+ }
475
+ for (const conv of conversations) {
476
+ io.stdout.write(
477
+ `${conv.id} ${conv.startIso}${conv.endIso ? ` \u2013 ${conv.endIso}` : ""} (${conv.sources.join(", ")}, ${conv.segments.length} segment${conv.segments.length === 1 ? "" : "s"})\n`,
478
+ );
479
+ for (const disagreement of conv.disagreements) {
480
+ io.stdout.write(
481
+ ` disagreement [${disagreement.kind}] @ ${disagreement.subject}: ${disagreement.candidates.length} candidate${disagreement.candidates.length === 1 ? "" : "s"}\n`,
482
+ );
483
+ }
484
+ }
485
+ return 0;
486
+ }
422
487
  default:
423
488
  throw new WearablesInputError(
424
489
  `unknown wearables command '${command}'\n\n${USAGE}`,
@@ -245,3 +245,35 @@ test("a deep window narrower than the tick window is rejected, 0 disables", () =
245
245
  5,
246
246
  );
247
247
  });
248
+
249
+
250
+ test("fusion defaults to disabled with documented knobs", () => {
251
+ const parsed = parseWearablesConfig({});
252
+ assert.equal(parsed.fusion.enabled, false);
253
+ assert.equal(parsed.fusion.proximityGapMs, 300_000);
254
+ assert.equal(parsed.fusion.windowToleranceMs, 30_000);
255
+ });
256
+
257
+ test("fusion enabled flag and knobs parse and validate", () => {
258
+ const parsed = parseWearablesConfig({
259
+ fusion: { enabled: true, proximityGapMs: 120_000, windowToleranceMs: 15_000 },
260
+ });
261
+ assert.equal(parsed.fusion.enabled, true);
262
+ assert.equal(parsed.fusion.proximityGapMs, 120_000);
263
+ assert.equal(parsed.fusion.windowToleranceMs, 15_000);
264
+ });
265
+
266
+ test("fusion knobs reject non-positive integers", () => {
267
+ assert.throws(
268
+ () => parseWearablesConfig({ fusion: { proximityGapMs: 0 } }),
269
+ /proximityGapMs must be a positive integer/,
270
+ );
271
+ assert.throws(
272
+ () => parseWearablesConfig({ fusion: { windowToleranceMs: -5 } }),
273
+ /windowToleranceMs must be a positive integer/,
274
+ );
275
+ });
276
+
277
+ test("fusion block must be an object when present", () => {
278
+ assert.throws(() => parseWearablesConfig({ fusion: "on" }), /must be an object/);
279
+ });